commit a94cd3e89034c854453f0d0f2d9b6d9976efa67d Author: Qiu Date: Fri Jun 19 14:45:07 2026 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fdc60dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +public/posts +src/generated diff --git a/index.html b/index.html new file mode 100644 index 0000000..ff4242b --- /dev/null +++ b/index.html @@ -0,0 +1,15 @@ + + + + + + + + 知识指南 + + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..000d985 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,7423 @@ +{ + "name": "blog-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "blog-frontend", + "version": "1.0.0", + "dependencies": { + "highlight.js": "^11.11.1", + "marked": "^12.0.0", + "vite-ssg": "^28.3.0", + "vue": "^3.4.0", + "vue-router": "^4.3.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.0", + "sharp": "^0.34.5", + "vite": "^5.2.0", + "vite-plugin-pwa": "^1.3.0" + } + }, + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "license": "MIT" + }, + "node_modules/@apideck/better-ajv-errors": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.7.tgz", + "integrity": "sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsonpointer": "^5.0.1", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", + "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz", + "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz", + "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz", + "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz", + "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz", + "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz", + "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", + "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz", + "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", + "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/template": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz", + "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz", + "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz", + "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz", + "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", + "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz", + "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", + "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz", + "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz", + "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz", + "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz", + "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz", + "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", + "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz", + "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz", + "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.7.tgz", + "integrity": "sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz", + "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz", + "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", + "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.7.tgz", + "integrity": "sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", + "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", + "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz", + "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", + "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz", + "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz", + "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.7.tgz", + "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.29.7", + "@babel/plugin-syntax-import-attributes": "^7.29.7", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.29.7", + "@babel/plugin-transform-async-generator-functions": "^7.29.7", + "@babel/plugin-transform-async-to-generator": "^7.29.7", + "@babel/plugin-transform-block-scoped-functions": "^7.29.7", + "@babel/plugin-transform-block-scoping": "^7.29.7", + "@babel/plugin-transform-class-properties": "^7.29.7", + "@babel/plugin-transform-class-static-block": "^7.29.7", + "@babel/plugin-transform-classes": "^7.29.7", + "@babel/plugin-transform-computed-properties": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-dotall-regex": "^7.29.7", + "@babel/plugin-transform-duplicate-keys": "^7.29.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-dynamic-import": "^7.29.7", + "@babel/plugin-transform-explicit-resource-management": "^7.29.7", + "@babel/plugin-transform-exponentiation-operator": "^7.29.7", + "@babel/plugin-transform-export-namespace-from": "^7.29.7", + "@babel/plugin-transform-for-of": "^7.29.7", + "@babel/plugin-transform-function-name": "^7.29.7", + "@babel/plugin-transform-json-strings": "^7.29.7", + "@babel/plugin-transform-literals": "^7.29.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", + "@babel/plugin-transform-member-expression-literals": "^7.29.7", + "@babel/plugin-transform-modules-amd": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-modules-systemjs": "^7.29.7", + "@babel/plugin-transform-modules-umd": "^7.29.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-new-target": "^7.29.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", + "@babel/plugin-transform-numeric-separator": "^7.29.7", + "@babel/plugin-transform-object-rest-spread": "^7.29.7", + "@babel/plugin-transform-object-super": "^7.29.7", + "@babel/plugin-transform-optional-catch-binding": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/plugin-transform-private-methods": "^7.29.7", + "@babel/plugin-transform-private-property-in-object": "^7.29.7", + "@babel/plugin-transform-property-literals": "^7.29.7", + "@babel/plugin-transform-regenerator": "^7.29.7", + "@babel/plugin-transform-regexp-modifiers": "^7.29.7", + "@babel/plugin-transform-reserved-words": "^7.29.7", + "@babel/plugin-transform-shorthand-properties": "^7.29.7", + "@babel/plugin-transform-spread": "^7.29.7", + "@babel/plugin-transform-sticky-regex": "^7.29.7", + "@babel/plugin-transform-template-literals": "^7.29.7", + "@babel/plugin-transform-typeof-symbol": "^7.29.7", + "@babel/plugin-transform-unicode-escapes": "^7.29.7", + "@babel/plugin-transform-unicode-property-regex": "^7.29.7", + "@babel/plugin-transform-unicode-regex": "^7.29.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.1.tgz", + "integrity": "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.4.tgz", + "integrity": "sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz", + "integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz", + "integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz", + "integrity": "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "serialize-javascript": "^7.0.3", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@trickfilm400/rollup-plugin-off-main-thread": { + "version": "3.0.0-pre1", + "resolved": "https://registry.npmjs.org/@trickfilm400/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-3.0.0-pre1.tgz", + "integrity": "sha512-/67zpWDBLV+oYAEL682s1ktXL0HgqX76f6gaVGkGnVZlBbm1zd0v4Bz8MFF2GGhoX9rvfq3KSQHubFHwa6w6/Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ejs": "^3.1.10", + "json5": "^2.2.3", + "magic-string": "^0.30.21", + "string.prototype.matchall": "^4.0.12" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@unhead/dom": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-2.1.15.tgz", + "integrity": "sha512-3/qtu2uOVW0eyYCIljweQ9sRDiFpCasGv9A7LjbcqWR9cxEPDYiJBiK2lVqPJT68qonAeXhtiS08PTCWTau8SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "unhead": "2.1.15" + } + }, + "node_modules/@unhead/vue": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-2.1.15.tgz", + "integrity": "sha512-SSByXfEjhzPn8gXdEdgpYqpLMPSkLUH2HVE0GxZfOtNsJ0GgOHQs0g9T67ZZ1z0kTELLKdtOtYrzrbv9+ffF7g==", + "license": "MIT", + "dependencies": { + "hookable": "^6.0.1", + "unhead": "2.1.15" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "vue": ">=3.5.18" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.35.tgz", + "integrity": "sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/shared": "3.5.35", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.35.tgz", + "integrity": "sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.35.tgz", + "integrity": "sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/compiler-core": "3.5.35", + "@vue/compiler-dom": "3.5.35", + "@vue/compiler-ssr": "3.5.35", + "@vue/shared": "3.5.35", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.35.tgz", + "integrity": "sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.35.tgz", + "integrity": "sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.35.tgz", + "integrity": "sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.35.tgz", + "integrity": "sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.35", + "@vue/runtime-core": "3.5.35", + "@vue/shared": "3.5.35", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.35.tgz", + "integrity": "sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.35", + "@vue/shared": "3.5.35" + }, + "peerDependencies": { + "vue": "3.5.35" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.35.tgz", + "integrity": "sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansis": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.0.tgz", + "integrity": "sha512-44mvgtPvohuU/70DdY5Oz2AIrLJ9k6/5x4KmoSvPwO+5Moijo0+N9D0fKbbYZQWP1hNm5CpOf+E01jhxG/r8xg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "peer": true, + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.2.0.tgz", + "integrity": "sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.0.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.28", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cssstyle/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.363", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.363.tgz", + "integrity": "sha512-VjUKPyWzGnT1fujlkEGC/BvN70Hh70KXtAqcmniXviYlJC/ivcT+BWGPyxWVbJZLfvtKR6dqg1L7T7pgAMBtWA==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eta": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-4.6.0.tgz", + "integrity": "sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/bgub/eta?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hookable": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", + "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==", + "license": "MIT" + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-minifier-terser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "~5.3.2", + "commander": "^10.0.0", + "entities": "^4.4.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.15.1" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/html-minifier-terser/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/html5parser": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html5parser/-/html5parser-2.0.2.tgz", + "integrity": "sha512-L0y+IdTVxHsovmye8MBtFgBvWZnq1C9WnI/SmJszxoQjmUH1psX2uzDk21O5k5et6udxdGjwxkbmT9eVRoG05w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.2.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "undici": "^7.21.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/smob": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.2.tgz", + "integrity": "sha512-RQsvleCbF8cVHEv+xuDGaA4pOizFqJ0GgjtMSRo6oP8pnN7WsigHgVGey6aILRBKv4W2YOMHLqbKdnB6hpB9fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", + "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tldts": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.0.tgz", + "integrity": "sha512-yHBe+zVfzNZ3QfTPW/Z6KK1G2t340gFjMHqI/4KKSt/abzYydzuCnpqdaF5gCCABby+9Yfbj59oR5F2Fd5CBzg==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.0" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.1.tgz", + "integrity": "sha512-sc2nGvGbixlJRHwTh/qQdPXTxJU1UDJboGPQm4d/01YUJ9r/u6aeIulQvEaxUlvKDN7hb1qCLjax+jhVAPLa/g==", + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.26.0.tgz", + "integrity": "sha512-3O9Tf67pGhgOv9jM35AbhkXAKi13f3oy3aE4CSgr+TckGeY+/iu97ZXN+J7DpHPzLbVApFd1IFhcnBjREYXYcg==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unhead": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.15.tgz", + "integrity": "sha512-MCt5T90mCWyr3Z6pUCdM9lVRXoMoVBlL7z7U4CYVIiaDiuzad/UCfLuMqz5MeNmpZUgoBCQnrucJimU7EZR+XA==", + "license": "MIT", + "peer": true, + "dependencies": { + "hookable": "^6.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-pwa": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.3.0.tgz", + "integrity": "sha512-c5kMgN+ITrOtHXp8PAtk2uOIEea6XjP/unCGxOWWBzQ6qa65qj/awHg0wf+QF9E/2u9vh86LqxPwzEPNbM2r5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.6", + "pretty-bytes": "^6.1.1", + "tinyglobby": "^0.2.10", + "workbox-build": "^7.4.1", + "workbox-window": "^7.4.1" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vite-pwa/assets-generator": "^1.0.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "workbox-build": "^7.4.1", + "workbox-window": "^7.4.1" + }, + "peerDependenciesMeta": { + "@vite-pwa/assets-generator": { + "optional": true + } + } + }, + "node_modules/vite-ssg": { + "version": "28.3.0", + "resolved": "https://registry.npmjs.org/vite-ssg/-/vite-ssg-28.3.0.tgz", + "integrity": "sha512-dIUjv+scfhJTfYGwf83R0KGAmr/duIo9oln5ZsQOIZZACm3voAON//7oKhtEwaOTtD4QTTab+nhvyn0QiIaFMA==", + "license": "MIT", + "dependencies": { + "@unhead/dom": "^2.1.2", + "@unhead/vue": "^2.1.2", + "ansis": "^4.2.0", + "cac": "^6.7.14", + "html-minifier-terser": "^7.2.0", + "html5parser": "^2.0.2", + "jsdom": "^28.0.0" + }, + "bin": { + "vite-ssg": "bin/vite-ssg.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "beasties": "^0.3.5", + "prettier": "^3.3.0", + "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0-0 || ^8.0.0-0", + "vue": "^3.2.10", + "vue-router": "^4.0.1 || ^5.0.0-0" + }, + "peerDependenciesMeta": { + "beasties": { + "optional": true + }, + "prettier": { + "optional": true + }, + "vue-router": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.35.tgz", + "integrity": "sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.5.35", + "@vue/compiler-sfc": "3.5.35", + "@vue/runtime-dom": "3.5.35", + "@vue/server-renderer": "3.5.35", + "@vue/shared": "3.5.35" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.21.tgz", + "integrity": "sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/workbox-background-sync": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.1.tgz", + "integrity": "sha512-HhT7KE8tOWDm02wRNshXUnUPofMlhenF2DBdUnDPOubhizzPeItkYTmAB6td1Z2cjYPa98vzEiPLEuzn5hN66g==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.1.tgz", + "integrity": "sha512-uAlgslKLvbQY+suirIdnBCSYrcgBhjp81Nj4l1lj/Jmj0MJO2CJERnCJjT0GFVwmReV0N+zs78K6gqd5gr9/+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-build": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.1.tgz", + "integrity": "sha512-SDhxIvEAde9Gy/5w4Yo1Jh/M49Z0qE3q0oteyE8zGq0DScxFqVBcCtIXFuLtmtxRQZCMbf0prco4VyEu3KBQuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.24.4", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.3", + "@rollup/plugin-replace": "^6.0.3", + "@rollup/plugin-terser": "^1.0.0", + "@trickfilm400/rollup-plugin-off-main-thread": "^3.0.0-pre1", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "eta": "^4.5.1", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^11.0.1", + "pretty-bytes": "^5.3.0", + "rollup": "^4.53.3", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "7.4.1", + "workbox-broadcast-update": "7.4.1", + "workbox-cacheable-response": "7.4.1", + "workbox-core": "7.4.1", + "workbox-expiration": "7.4.1", + "workbox-google-analytics": "7.4.1", + "workbox-navigation-preload": "7.4.1", + "workbox-precaching": "7.4.1", + "workbox-range-requests": "7.4.1", + "workbox-recipes": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1", + "workbox-streams": "7.4.1", + "workbox-sw": "7.4.1", + "workbox-window": "7.4.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/workbox-build/node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.1.tgz", + "integrity": "sha512-8xaFoJdDc2OjrlbbL3gEeBO1WKcMwRqwLRupgqahYXu75yXajPLuwrbXMrIGZuWYXrQwk0xDjOxZ/ujCy/oJYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-core": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.1.tgz", + "integrity": "sha512-DT+vu46eh/2vRsSHTY4Xmc32Z1rr9PRlQUXr1Dx30ZuXRWwOsvZgGgcwxcasubQLQmbTNYZjv44LkBAQ4tT5tQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-expiration": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.1.tgz", + "integrity": "sha512-lRKUF7b+OGbeXkQk1s6MHXOa3d7Xxf7Of31W6c6hCfipfIyrtdWZ89stq21AHZMaoG7VNFoHply4Ox+rU31TWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-google-analytics": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.1.tgz", + "integrity": "sha512-Mks1JwLEt++ZAkF6sS1OpSh9RtAMIsiDgRpK+codiHGIPXeaUOgi4cPc3GFadUl8V5QPeypEk8Oxgl3HlwVzHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-background-sync": "7.4.1", + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.1.tgz", + "integrity": "sha512-C4KVsjPcYKJOhr631AxR9XoG2rLF3QiTk5aMv36MXOjtWvm8axwNFAtKUPGsWUwLXXAMgYM1En7fsvndaXeXRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-precaching": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.1.tgz", + "integrity": "sha512-cdr/9qByww7yzEp7zg/qI4ukUrrNjQLgN+ONQRpjy/VqGQXwkgHwr00KksGJK8v0VifwDXBb8a4cWNZH71jn3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" + } + }, + "node_modules/workbox-range-requests": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.1.tgz", + "integrity": "sha512-7i2oxAUE82gHdAJBCAQ04JzNOdRPqzuOzGfoUyJpFSmeqBNYGPrAH8GPoPjUQTfp+NycwrD2H68VtuF8qxv0vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-recipes": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.1.tgz", + "integrity": "sha512-gnbVfmV4/TtmQaM4x9AtuXhcdstJsep3XMVeztOrQVPT+R6+6DeBjGTCQ7fFCXm+4GEHUA5VEBTyi5+4gWGeog==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-cacheable-response": "7.4.1", + "workbox-core": "7.4.1", + "workbox-expiration": "7.4.1", + "workbox-precaching": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" + } + }, + "node_modules/workbox-routing": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.1.tgz", + "integrity": "sha512-yubJGErZOusuidAenaL5ypfhQOa7urxP/f8E0ws7FPb4039RiWXUWBAyUkmUoOL/BcQGen3h0J8872d51IYxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-strategies": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.1.tgz", + "integrity": "sha512-GZxpaw9NbmOelj7667uZ2kpk5BFpOGbO4X0qjwh5ls8XQ8C+Lha5LQchTiUzsTFSS+NlUpftYAyOVXvQUrcqOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-streams": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.1.tgz", + "integrity": "sha512-HWWtraKUbJknd9kgqGcpQ3G114HOPYvqs8HaJMDs2ebLNAimDkVDaWfAXE6Ybl+m8U6KsCE6pWyLYuigWmnAXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1" + } + }, + "node_modules/workbox-sw": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.1.tgz", + "integrity": "sha512-fez5f2DUlDJWTFYkCWQpY10N8gtztd849NswCbVFk0QlcSM4HT5A8x4g4ii650yem4I8tHY0R7JZahwp3ltIPw==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-window": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.1.tgz", + "integrity": "sha512-notZDH2u8VXaqyuD7xaqIfEFi6SRM4SUSd7ewe9PDsVqADuepxX2ZMY3uvuZGxzY5ZOsGC/vD3A/3smFtJt4/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "7.4.1" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..00776cd --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "blog-frontend", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "node scripts/copy-images.js && vite", + "build": "node scripts/copy-images.js && vite-ssg build", + "preview": "vite preview", + "copy-images": "node scripts/copy-images.js" + }, + "dependencies": { + "highlight.js": "^11.11.1", + "marked": "^12.0.0", + "vite-ssg": "^28.3.0", + "vue": "^3.4.0", + "vue-router": "^4.3.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.0", + "sharp": "^0.34.5", + "vite": "^5.2.0", + "vite-plugin-pwa": "^1.3.0" + } +} diff --git a/posts/blog/主页.md b/posts/blog/主页.md new file mode 100644 index 0000000..dbe23b6 --- /dev/null +++ b/posts/blog/主页.md @@ -0,0 +1,62 @@ +--- +title: 主页 +date: 2024-01-01 12:03:35 +tags: [主页] +--- +# 主页 + +你好Hexo + +- dfs + +> sdfdfdf +> +> dsf + +`fsdf` + +mark + +**fsf** + +* dfdsf + * sdf + * sdf + + + + + +i = 3 , j = 4; + + + +l = 1 , r = 12; + +n = 6; + +1,1 + + + +l = 6 , r = 12; + +n = 9; + +2,0 + + + +l = 6 , r = 9; + +n = 7; + +1,2 + + + +l = 6 , r = 7; + +n = 6; + +1,1 diff --git a/posts/blog/工作/安排/升级计划.md b/posts/blog/工作/安排/升级计划.md new file mode 100644 index 0000000..474a5d8 --- /dev/null +++ b/posts/blog/工作/安排/升级计划.md @@ -0,0 +1,19 @@ +--- +title: 升级计划 +date: 2025-09-18 11:59:06 +tags: [smt] +--- + +## SMT 管理软件 + +* 修改前端组件,不要加、减 + +![image-20250918120112380](升级计划/image-20250918120112380.png) + +* 修改优惠金额名称,改为“优惠/其他金额” + +![289e2835660f4a4cd62f72b4b5ea1050](升级计划/289e2835660f4a4cd62f72b4b5ea1050.png) + +## 查看客户订单,报错 + +![image-20250919205930175](升级计划/image-20250919205930175.png) diff --git a/posts/blog/工作/安排/升级计划/289e2835660f4a4cd62f72b4b5ea1050.png b/posts/blog/工作/安排/升级计划/289e2835660f4a4cd62f72b4b5ea1050.png new file mode 100644 index 0000000..393e3bb Binary files /dev/null and b/posts/blog/工作/安排/升级计划/289e2835660f4a4cd62f72b4b5ea1050.png differ diff --git a/posts/blog/工作/安排/升级计划/image-20250918120112380.png b/posts/blog/工作/安排/升级计划/image-20250918120112380.png new file mode 100644 index 0000000..101a316 Binary files /dev/null and b/posts/blog/工作/安排/升级计划/image-20250918120112380.png differ diff --git a/posts/blog/工作/安排/升级计划/image-20250919205930175.png b/posts/blog/工作/安排/升级计划/image-20250919205930175.png new file mode 100644 index 0000000..477fad6 Binary files /dev/null and b/posts/blog/工作/安排/升级计划/image-20250919205930175.png differ diff --git a/posts/blog/工作/安排/开发计划.md b/posts/blog/工作/安排/开发计划.md new file mode 100644 index 0000000..e6be8c2 --- /dev/null +++ b/posts/blog/工作/安排/开发计划.md @@ -0,0 +1,26 @@ +--- +title: 官网开发计划 +date: 2025-09-11 +tags: [vue,java] +--- + +# 开发计划 + +**9-11 周四** + +* 基础页面开发:定价、优惠价、订单查询、个人中心 +* 并进行审核 + +**9-12 周五** + +* 后端开发:完成数据库设计、增删改查任务 + +* 对接支付宝业务 + +**9-13 周六** + +* 对接管理员后台,并设计前端页面 + +**9-13 周日** + +* 后端对接,并进行测试 \ No newline at end of file diff --git a/posts/blog/工作/安排/泸州老窖.md b/posts/blog/工作/安排/泸州老窖.md new file mode 100644 index 0000000..2aed18c --- /dev/null +++ b/posts/blog/工作/安排/泸州老窖.md @@ -0,0 +1,19 @@ +月要货计划---srm---执行中 + +日要货计划(必须有月计划) + +告诉SRM什么时间要 + + + +人工创建采购计划 -- 审批 --> 采购单 --> 生成SAP订单 + +告诉SRM要什么 + + + +人工创建**日送货计划**关联多个采购单 + +对高梁/稻壳自动发起质检任务、自动下发到地磅 + +给地磅系统提供接口,称重 \ No newline at end of file diff --git a/posts/blog/工作/安排/面试.md b/posts/blog/工作/安排/面试.md new file mode 100644 index 0000000..f458225 --- /dev/null +++ b/posts/blog/工作/安排/面试.md @@ -0,0 +1,41 @@ +--- +title: 招聘 +date: 2025-09-12 +tags: [招聘] +--- + +# 面试 + +【深信服26届校园招聘启动】 +🙋A股上市,8000+员工规模,业务涵盖AI、网安、云计算三条高成长赛道! + +【热招岗位】 +🙋市场类:1000+offer(不卡学历 /不卡专业) + - 客户经理(不限专业,均可投递) + - 售前产品经理(限理工科) +⭐薪资:本科:22w+;硕士:25w+ + +🙋研发类:1000+offer(工作地点:80%在深圳,20%在长沙) + - 开发岗:C/C++、Python、Go、Java开发工程师 + - 人工智能岗:AI工程师 +⭐薪资:SP/SSP:40w-100w +【为非计算机专业学生定向提供500+offer】 + +🔥【加入深信服的4个理由】 +✅赛道前景更广阔:AI+云计算+网络安全,共同参与数字产业浪潮 +✅成长更迅速:专属培养通道、破格提拔、提供实习机会,更多一份培养与成长 +✅工作氛围更简单:简单、务实、平等的氛围,内部不允许有“xxx总”的称呼, +✅高成长高收入:工作回报更丰富,行业领先的薪资、股权激励机会 + +内推码:NTAhG13 +投递地址:https://hr.sangfor.com/campuszp +注:秋招岗位数量有限,投递页面可点击“职位”选择“应届生”跳转详情页,超多优质秋招实习岗位,静候你的加入! +26届秋招合集#小程序://一键投递小助手/kZ8x2O8B5A2QV8r,也可点击小程序一键投递!更快投递中意岗位 + + + + + + + +[【武汉 Java开发(校招,武汉)招聘】-广东亿迅科技有限公司武汉招聘信息-猎聘](https://www.liepin.com/lptjob/76892667?d_sfrom=search_job_comp_prime_pc&d_ckId=cbc6095d07bd888997eb831f4d572690&d_curPage=0&d_pageSize=6&d_headId=8ff9fe89e31006ee71776d706a5ab55f&d_posi=3&pgRef=c_pc_company_home_page%3Ac_pc_company_home_job_listcard%406_76892667%3A1%3Agw.dc7f9588-1864046902) diff --git a/posts/blog/工作/待办/学校待办.md b/posts/blog/工作/待办/学校待办.md new file mode 100644 index 0000000..fddf80a --- /dev/null +++ b/posts/blog/工作/待办/学校待办.md @@ -0,0 +1,19 @@ +--- +title: 学校待办 +date: 2024-12-31 +categories: [工作, 待办] +tags: [待办] +--- + + +- [ ] nio、netty、juc学习完 +- [ ] javaweb、spring、springmvc、mybatys、springgboot、redis +- [ ] 设计模式 +- [ ] 中间件:rabbitMQ、elasticsearch +- [ ] 微服务: +- [ ] AiTest项目,复习文件断点问题 +- [ ] 改善湖师云项目 + + + + \ No newline at end of file diff --git a/posts/blog/工作/项目/DeviceTransfer.md b/posts/blog/工作/项目/DeviceTransfer.md new file mode 100644 index 0000000..723b3b8 --- /dev/null +++ b/posts/blog/工作/项目/DeviceTransfer.md @@ -0,0 +1,218 @@ +--- +title: DeviceTransfer +date: 2025-04-07 +categories: [工作, 项目] +tags: [项目] +--- + + +```Plain +请为我生成一个基于 Spring Boot 的后端项目,项目名称为 "DeviceTransfer",实现设备间消息和文件传输功能,使用 Redis、RabbitMQ 和 MySQL 作为辅助技术。以下是具体要求: + +1. **项目结构**: + - 包名:`com.example.devicetransfer` + - 结构: + - `Application.java`:Spring Boot 启动类 + - `config/`: + - `WebSocketConfig.java`:配置 WebSocket,支持 STOMP 协议 + - `RabbitMQConfig.java`:配置 RabbitMQ 交换机和队列 + - `RedisConfig.java`:配置 Redis 客户端 + - `MyBatisConfig.java`:配置 MyBatis 数据访问层 + - `controller/`: + - `FileController.java`:处理 HTTP 文件上传、下载及传输控制 + - `websocket/`: + - `NotificationController.java`:处理 WebSocket 实时通知 + - `service/`: + - `DeviceService.java`:管理设备状态 + - `FileService.java`:处理文件传输逻辑(包括分片、断点续传、暂停) + - `MessageService.java`:处理消息发送和通知 + - `mq/`: + - `producer/FileMessageProducer.java`:发送消息到 RabbitMQ + - `consumer/FileMessageConsumer.java`:消费 RabbitMQ 消息 + - `repository/`: + - `DeviceRepository.java`:使用 Redis 存储设备状态 + - `mapper/FileMetadataMapper.java`:使用 MyBatis 操作 MySQL 文件元数据 + - `model/`: + - `Device.java`:设备实体类 + - `FileMetadata.java`:文件元数据类(映射到 MySQL) + - `util/`: + - `FileChunkUtil.java`:大文件分片和断点续传工具类 + - `resources/`: + - `application.yml`:配置文件 + - `mapper/FileMetadataMapper.xml`:MyBatis SQL 映射文件 + +2. **功能需求**: + - **文件传输**: + - HTTP POST `/upload`:支持大文件分片上传,保存到 `uploads/` 目录,返回文件 ID。 + - 参数:`file`(分片内容)、`fileId`(文件唯一标识)、`chunkIndex`(分片索引)、`totalChunks`(总分片数)。 + - 支持断点续传:记录已上传分片,客户端可续传未完成部分。 + - 支持中途暂停/取消:HTTP DELETE `/upload/cancel/{fileId}` 取消上传并清理分片。 + - HTTP GET `/download/{fileId}`:根据文件 ID 下载,支持分片下载和断点续传。 + - 参数:`Range` 头支持范围请求(如 `Range: bytes=0-1048575`)。 + - 支持暂停:客户端可随时中断请求。 + - **实时通知**: + - WebSocket 端点 `/ws`,订阅 `/topic/messages`,发送消息到 `/app/send`。 + - 在线设备通过 WebSocket 接收通知,离线设备消息存入 RabbitMQ。 + - **设备管理**: + - 设备通过 WebSocket 注册(发送 userId 和 deviceId)。 + - Redis 存储用户设备状态(键格式:`user:{userId}:devices`)。 + - **离线支持**: + - 文件上传后,若目标设备离线,消息发送到 RabbitMQ 的 `file-queue`。 + - 设备上线时,消费队列消息并通过 WebSocket 推送。 + - **数据持久化**: + - 使用 MySQL 存储文件元数据,表名 `file_metadata`,字段: + - `id` (主键)、`file_id` (唯一标识)、`file_name`、`file_path`、`user_id`、`total_chunks`、`uploaded_chunks`、`status` (上传状态:uploading/completed/canceled)、`upload_time`。 + - MyBatis 操作数据库,定义增删改查 SQL。 + +3. **技术要求**: + - 使用 Spring Boot 2.x 或 3.x。 + - Redis 用于存储设备状态(键格式:`user:{userId}:devices`)和分片进度(键格式:`file:{fileId}:chunks`)。 + - RabbitMQ 使用 Direct 交换机(`file-exchange`)、队列(`file-queue`)、路由键(`file.routing.key`)。 + - MySQL 通过 MyBatis 持久化文件元数据。 + - 文件存储在本地 `uploads/` 目录,分片文件命名格式:`{fileId}_{chunkIndex}`,完成后合并为 `{fileId}`。 + - 不配置 TLS(HTTP 和 WebSocket 使用非加密协议)。 + +4. **依赖**: + - `spring-boot-starter-web` + - `spring-boot-starter-websocket` + - `spring-boot-starter-data-redis` + - `spring-boot-starter-amqp` + - `mybatis-spring-boot-starter` + - `mysql-connector-java` + +5. **其他说明**: + - 提供基本的错误处理和日志记录(使用 SLF4J)。 + - 代码注释清晰,说明每个类的作用。 + - 生成完整的 Maven `pom.xml` 文件。 + - **大文件分片和断点续传**: + - 客户端上传分片,服务端记录进度到 Redis(`file:{fileId}:chunks` 存储已上传分片索引)。 + - 续传时,客户端查询已上传分片(GET `/upload/status/{fileId}`),跳过已完成部分。 + - 暂停/取消:DELETE `/upload/cancel/{fileId}` 删除分片并更新状态为 canceled。 + - **下载支持**: + - 服务端响应 `Range` 头,返回指定范围的分片。 + - 客户端可暂停下载,续传时通过 `Range` 请求剩余部分。 + +请根据以上要求生成完整的项目代码,确保结构清晰、可运行,并支持未来扩展(如引入 Netty 或替换 RabbitMQ 为 Kafka)。 +``` + + + +请为我生成一个基于 Spring Boot 的后端项目,项目名称为 "DeviceTransfer",实现设备间消息和文件传输功能,使用 Redis、RabbitMQ 和 MySQL 作为辅助技术。以下是具体要求: + +1. **项目结构**: + - 包名:`com.qgs.devicetransfer` + - 结构: + - `Application.java`:Spring Boot 启动类 + - `config/`: + - `WebSocketConfig.java`:配置 WebSocket,支持 STOMP 协议 + - `RabbitMQConfig.java`:配置 RabbitMQ 交换机和队列 + - `RedisConfig.java`:配置 Redis 客户端 + - `controller/`: + - `FileController.java`:处理 HTTP 文件上传、下载及传输控制 + - `websocket/`: + - `NotificationController.java`:处理 WebSocket 实时通知 + - `service/`: + - `DeviceService.java`:管理设备状态 + - `FileService.java`:处理文件传输逻辑(包括分片、断点续传、暂停) + - `MessageService.java`:处理消息发送和通知 + - `mq/`: + - `producer/FileMessageProducer.java`:发送消息到 RabbitMQ + - `consumer/FileMessageConsumer.java`:消费 RabbitMQ 消息 + - `repository/`: + - `DeviceRepository.java`:使用 Redis 存储设备状态 + - `mapper/FileMetadataMapper.java`:使用 MyBatis 操作 MySQL 文件元数据 + - `model/`: + - `Device.java`:设备实体类 + - `FileMetadata.java`:文件元数据类(映射到 MySQL) + - `util/`: + - `FileChunkUtil.java`:大文件分片和断点续传工具类 + - `resources/`: + - `application.yml`:配置文件 + - `mapper/FileMetadataMapper.xml`:MyBatis SQL 映射文件 +2. **功能需求**: + - **文件传输**: + - HTTP POST `/upload`:支持大文件分片上传,保存到 `uploads/` 目录,返回文件 ID。 + - 参数:`file`(分片内容)、`fileId`(文件唯一标识)、`chunkIndex`(分片索引)、`totalChunks`(总分片数)。 + - 支持断点续传:记录已上传分片,客户端可续传未完成部分。 + - 支持中途暂停/取消:HTTP DELETE `/upload/cancel/{fileId}` 取消上传并清理分片。 + - HTTP GET `/download/{fileId}`:根据文件 ID 下载,支持分片下载和断点续传。 + - 参数:`Range` 头支持范围请求(如 `Range: bytes=0-1048575`)。 + - 支持暂停:客户端可随时中断请求。 + - **实时通知**: + - WebSocket 端点 `/ws`,订阅 `/topic/messages`,发送消息到 `/app/send`。 + - 在线设备通过 WebSocket 接收通知,离线设备消息存入 RabbitMQ。 + - **设备管理**: + - 设备通过 WebSocket 注册(发送 userId 和 deviceId)。 + - Redis 存储用户设备状态(键格式:`user:{userId}:devices`)。 + - **离线支持**: + - 文件上传后,若目标设备离线,消息发送到 RabbitMQ 的 `file-queue`。 + - 设备上线时,消费队列消息并通过 WebSocket 推送。 + - **数据持久化**: + - 使用 MySQL 存储文件元数据,表名 `file_metadata`,字段: + - `id` (主键)、`file_id` (唯一标识)、`file_name`、`file_path`、`user_id`、`total_chunks`、`uploaded_chunks`、`status` (上传状态:uploading/completed/canceled)、`upload_time`。 + - MyBatis 操作数据库,定义增删改查 SQL。 +3. **技术要求**: + - 使用 Spring Boot 3.4.4 + - Redis 用于存储设备状态(键格式:`user:{userId}:devices`)和分片进度(键格式:`file:{fileId}:chunks`)。 + - RabbitMQ 使用 Direct 交换机(`file-exchange`)、队列(`file-queue`)、路由键(`file.routing.key`)。 + - MySQL 通过 MyBatis 持久化文件元数据。 + - 文件存储在本地 `uploads/` 目录,分片文件命名格式:`{fileId}_{chunkIndex}`,完成后合并为 `{fileId}`。 + - 不配置 TLS(HTTP 和 WebSocket 使用非加密协议)。 +4. **其他说明**: + - 提供基本的错误处理和日志记录(使用 SLF4J)。 + - 代码注释清晰,说明每个类的作用。 + - **大文件分片和断点续传**: + - 客户端上传分片,服务端记录进度到 Redis(`file:{fileId}:chunks` 存储已上传分片索引)。 + - 续传时,客户端查询已上传分片(GET `/upload/status/{fileId}`),跳过已完成部分。 + - 暂停/取消:DELETE `/upload/cancel/{fileId}` 删除分片并更新状态为 canceled。 + - **下载支持**: + - 服务端响应 `Range` 头,返回指定范围的分片。 + - 客户端可暂停下载,续传时通过 `Range` 请求剩余部分。 + +请根据以上要求生成完整的项目代码,确保结构清晰、可运行,并支持未来扩展(如引入 Netty 或替换 RabbitMQ 为 Kafka)。 + + + +```Java +CREATE TABLE file_metadata ( +id BIGINT AUTO_INCREMENT PRIMARY KEY, +file_id VARCHAR(64) NOT NULL UNIQUE, +file_name VARCHAR(255) NOT NULL, +file_path VARCHAR(255) NOT NULL, +user_id VARCHAR(64) NOT NULL, +total_chunks INT NOT NULL, +uploaded_chunks INT NOT NULL, +status VARCHAR(20) NOT NULL, +upload_time VARCHAR(30) NOT NULL, +INDEX idx_file_id (file_id), +INDEX idx_user_id (user_id) +); +``` + + + +登录 + +- http → 登录获得token + +ws请求、连接都携带token + +连接后、上线: + +- 查看历史消息(可限制天数、已发送消息)、redis查询离线消息,进行返回; + 推送消息:客户端接收离线消息并返回确认,服务器接收确认消息,更新sql状态、redis状态 + +发送消息: + +- 接收消息,**基础校验,防止恶意请求**,写入消息队列, + - 消费队列处理信息,**深度校验(防止越权、内容监控、广告)** + - 写入MySQL; + - 判断是否在线,在线则进行消息推送 + - 不在线保存到redis中 + + + +推送机制 + +- 推送成功,修改数据库内容 +- 推送失败,重试 \ No newline at end of file diff --git a/posts/blog/工作/项目/LocalTransfer.md b/posts/blog/工作/项目/LocalTransfer.md new file mode 100644 index 0000000..2211565 --- /dev/null +++ b/posts/blog/工作/项目/LocalTransfer.md @@ -0,0 +1,66 @@ +--- +title: LocalTransfer +date: 2024-12-19 +categories: [工作, 项目] +tags: [项目] +--- + + +文件传输器 + +- 电脑互传 + - 自定义协议 + - 魔数:Q + - 类型:1 + - 正文长度: + - 消息正文: + - 请求 + - 文字 —> ack + - 文件 —> ack + - 分片、断点 + + + +请求 + +- 上传 + - 文件名:123.mp4 + - 分片:1 + - 起始位:0 + - 长度:1024 + - 内容:10101001 +- 断点续传(断开时,记录传输传输状态) + - 文件名:123.mp4 + + + + +使用Java编写一个文件传输软件,网络通信使用netty,用于2台设备之间文件传输,文件传输满足多线程分片上传、断点续传;使用上,只需要输入对方IP,就可以进行连接,并且设备在接收文件的同时也可以发送文件,并且有进度条显示 + + + +1个文件 + +1个连接 + +按线程分片 + + + +使用JavaFX编写界面,要求支持多文件进度显示(每秒的下载速度、大小)并且可以暂停,在接收文件的同时,可以上传文件给对方;界面要求简洁、美观、符合当下审美 + + + + + +- 跨设备互传 + - 扫码或输入地址,后端响应界面 + - 对文件的预览,请求 + - 上传 + - 下载 + + + + + +- 功能描述:电脑开启后端程序,选择一个开放文件夹,手机在浏览器输入地址,后端响应页面给手机,手机上可以对开放文件夹进行预览、下载;手机还可以上传文件到开放文件夹;前端通过HTML、css、js、Bootstrap、jQuery进行开发,后端使用springboot进行处理 \ No newline at end of file diff --git a/posts/blog/工作/项目/QDrop.md b/posts/blog/工作/项目/QDrop.md new file mode 100644 index 0000000..c7af1f1 --- /dev/null +++ b/posts/blog/工作/项目/QDrop.md @@ -0,0 +1,66 @@ +--- +title: QDrop +date: 2024-12-18 +categories: [工作, 项目] +tags: [项目] +--- + + +想法 + +- 打开软件,对设备进行互相认识 + - 可以删除设备 +- 发送消息,发送文件 + - 同一网络直接发送 + - 文本直接受 + - 文件直接保存 + - 不同网络则不待办 + - 连接网络,则直接发送 + + + +开发一个软件,功能如下 + +1、打开软件,同一局域网的设备进行添加、也可以删除设备 + +2、向设备之间发送消息,发送文件;同一局域网直接发送;不同网络则为待办任务,连接同一网络,则直接发送 + +技术栈 + +1、不使用(依赖)服务器 + +2、使用java语言、网络通信框架使用netty + +先以控制台为前端,主要开发后端功能 + + + + + +设备发现: + +- 使用UDP广播在局域网内发现设备 +- 维护在线设备列表 +- 定期检查设备在线状态 + +消息通信: + +- 使用Netty框架建立TCP连接 +- 支持文本消息和文件传输 +- 离线消息存储和重发机制 + +控制台界面: + +- 显示在线设备列表 +- 发送消息和文件的命令行接口 +- 查看消息历史记录 + + + +修改端口或设备名称 + +文件加密、文件压缩 + + + +使用javaFX编写前端代码,暂且不用和后端相结合,主界面分为三部分,上边是一排建立连接的设备,显示每个设备的圆框头像、名称,头像对应设备类型,头像地址在resource/images/下,并且在线设备的圆框是被亮光包围,最右侧是一个加号按钮,点击提供其他功能;中间是通信内容,要求通信的消息标签类似telegram、whatsAPP,自己发送的消息是淡绿色背景、黑色字体、带时间;下面部分是一个选择本地文件的按钮、输入框、发送按钮 \ No newline at end of file diff --git a/posts/blog/工作/项目/ruoyi项目.md b/posts/blog/工作/项目/ruoyi项目.md new file mode 100644 index 0000000..17a03b5 --- /dev/null +++ b/posts/blog/工作/项目/ruoyi项目.md @@ -0,0 +1,105 @@ +--- +title: ruoyi项目 +date: 2025-01-22 +categories: [工作, 项目] +tags: [项目] +--- + + +Redis环境安装 + +- [https://www.runoob.com/redis/redis-install.html](https://www.runoob.com/redis/redis-install.html) + + ```Shell + redis-server --service-install redis.windows.conf + ``` + + +产品管理 + +- 产品名称、产品价格、产品图片、数量 + +员工管理 + +- 姓名、电话、邮箱、qq、微信、职位、入职时间、职位 + +客户管理 + +- 姓名、联系方式 + +订单管理 + +- 订单号、客户ID、客户名称、订单状态、备注、下单时间、交付时间 +- 负责人、职位 +- 负责人、职位 + + + +```SQL +产品管理:产品名称、产品价格、产品图片、数量 +员工管理:姓名、电话、邮箱、qq、微信、职位、入职时间、职位 +客户管理:姓名、联系方式 +订单管理:订单号、客户ID、客户名称、订单状态、备注、下单时间、交付时间、负责人(多个)、职位 +``` + +## 若依框架 + +### **权限管理** + +- 用户管理(sys_user) +- 角色管理(sys_role) +- 菜单管理(sys_menu) + + 菜单类型 + + - 目录菜单:点击只有展开效果,无页面显示 + - 页面菜单:显示页面效果 + - 按钮菜单:一个点击事件 + +实现流程: + +创建菜单 —> 创建角色 —> 分配用户 + +### **数据字典** + +- 静态数据管理 +- 功能:字典类型管理、字典数据管理 +- 字典类型表(sys_dict_type) +- 字典数据表(sys_dict_data) +- 特点:修改时,将表中的属性值改为字典键 + +### 参数设置 + +- 验证码是否打开 +- 注册功能是否打开 + +### 通知公告(自行开发) + +### 日志管理 + + + +## 创建新的后台管理项目 + +### 新建子模块 + +- 子模块pom文件导入ruoyi核心依赖 + + ```XML + + + com.ruoyi + ruoyi-framework + + ``` + +- 主模块pom文件导入子模块 + + ```XML + + + com.ruoyi + ruoyi-smt + ${ruoyi.version} + + ``` \ No newline at end of file diff --git a/posts/blog/工作/项目/smt项目.md b/posts/blog/工作/项目/smt项目.md new file mode 100644 index 0000000..4da0906 --- /dev/null +++ b/posts/blog/工作/项目/smt项目.md @@ -0,0 +1,656 @@ +--- +title: smt项目 +date: 2025-02-04 +categories: [工作, 项目] +tags: [项目] +--- + + +## 数据库设计 + +- 功能表(权限表) + - 手动输入权限 +- 角色表 + - 自己创建角色、选择权限 +- 角色和权限表 +- 用户表 +- 用户和角色表 + + + +## 权限管理 + +![image 43.png](smt项目/image43.png) + + + +## 业务模块 + +- 员工管理 + - 员工信息 +- 客户管理 + - 客户信息 + - 账号管理 +- 订单管理 + +- 商品管理 + - 商品信息 + + + + +权限管理核心部分 + +不需要Security + +- beforeRouter对请求进行拦截 + + + + + + + +## 简述 + +目前是vue3+ element-plus + pinia +axios 的项目环境,请编写一个公司官网的前端界面,该公司提供的主要服务是smt贴片、电子元器件售卖服务;官网功能至少有公司介绍、贴片下单、产品售卖等服务(可自行拓展),而且要求页面符合现代审美、简洁大气;并适配电脑、手机设备端;并编写readme文档,记录项目实现进度、接口请求(使用RestFul风格) + + + + + + + + + + + +产品优势: + +**各位老板,你们是不是还在为小批量加工费而发愁:担心费用太高,价格不透明,手工贴片质量没法保障...这些都是因为传统加工计价标准不透明导致的,厂家没有看到实物是绝对不会给您报价的。** + +**说好一个点是0.013元的,可自己估算的加工费远远小于厂家的实际费用。原因是因为你所有物料都是按实际点算,而厂家对于二三极管以及芯片都是按多个点来算的。这就是你们的估价与厂家不一样的原因** + +**公司服务原则:** + +**你的满意是我们最大的追求!!!** + +**开机费,** +**无最低消费,无最小数量,专业生产加工。** + +## 优势 + +# **打破行业爆利.让利给客户。** + +**公司目前是全自动化流程:** + +**目前的设备有:** + +**自动上板机,全自动印刷机,自动贴片机,自动下板机。波峰焊机。** + + + + + +为解决以上报价难的问题,我公司采取了公正,透明,客观的报价体系。具体方法如下: + +报价分两部份组成:工程费 + 加片点数的数费用。 + +这两个参数客户都可以自己核算的。 + +# **工程费标准:** + +以下收费标准是有铅,单面贴片的标准,如是双面贴片,则第二面工程费是第一面的半价,也就是下面标准的1.5倍。 + +物料种类以贴片料种类为准,(双面的就是以两种物料种类之和为准),插件料种类不计入工程标准中。所以希望大家在提供BOM时把插件料和贴片费的种类分开,以免统计出错。 + +1-5种: 单面工程费:150元; + +6-10种:单面工程费:200元; + +11-25种:单面工程费:250元; + +26-30种:单面工程费:300元; + +31-35种:单面工程费:350元; + +36-40种:单面工程费:400元; + +41-45种:单面工程费:450元; + +46-50种:单面工程费:500元; + +51-55种:单面工程费:550元; + +56-60种:单面工程费:600元; + +61-65种:单面工程费:650元; + +66-70种:单面工程费:700元; + +71-75种:单面工程费:750元; + +76-80种:单面工程费:800元; + +81-85种:单面工程费:850元; + +86-90种:单面工程费:900元; + +91-95种:单面工程费:950元; + +96-100种:单面工程费:1000元; + +以后往后退:每增加5种物料,工程费加50元。 + +**焊盘机贴费用标准:**   + +加工点数:不任焊盘大小,元件引脚的大小,每个焊盘的单价是:0.0065.例如:电阻是两个脚,二极管也是两件引脚,大的252封的引脚也是算3个脚; IC每个引脚也算一个焊盘。 + +编带,管装,托盘类的元件都能机贴。如不能机贴的则按手放元件的标准计价, + +**焊盘手放费用标准:**   + +如果元件不能机贴的,则需要手放,手放的单价是0.02元一个焊盘。建议客户尽量买编带,管装,托盘类的元件。 + +**插件费用标准:**   + +人工焊接标准: + +每个孔0.05元,元件种类不计贴片元件种类中。 + +波峰焊标准: + +每个孔0.045,,封胶纸留,一条胶纸算一个孔位. + +注意事项是:只限于单面贴片,而且贴片和插件在同一面. + +以上报价标准都是按有铅的标准 来报的,红胶工艺与有铅工艺报价一样,无铅报价是在有铅的基础上的总价上上涨10%。 + +# **重要信息:** + +# **当一款板的焊盘总数*0.0065的费用超过2000元,可以免相应这款板的工程费。** + +# **单款贴片费用超过五千元(不包括插件费),加工费有返点。** + +# **注意事项:** + +**1.元件损耗问题: 小批量生产,如是样品料,阻容物料:0603,0805以上的了物料,都需要比实际用量多200个,贵重物料可以按需发货,或多一定数量,下单时告诉我公司跟单人员。如物料没有达到上面的要求,有可能会产生物料整理费(200-500元),具体是多少要看物料种类的多少。** + +**2. 最小生产面积:由于生产线都是全自动化流水作业,所以建议客户在生产PCB时要拼板和加工艺边。拼板后面积在15至20CM比较合适。** + +**3.钢网: 本公司代开,75元张(只限3747,其它规格另报),也可以贵公司直接提供,大小在3747以上的规格。** + +**4.交货周期: 物料齐后,无插件定单,3-5天。有插件定单:5-7天。具体时间可以与工作人员沟通。** + +**5.产品品质:正常情况下是目测,如客户品质要求比较高的,可以联系工作人员,进入AOI检测,由于AOI检测需要做程序,可能会产生产一定的费用。批量生产时免费过AOI 。** + +**6.售后问题:由于计价都比较客观,公正,透明,如在计价过程中有出入的支持多退少补。如生产品质有问题,客户可以发顺丰到付件到公司进行相应的程序。生产加工原本没有什么利润了,公司没有理由不去把客户的产品做来,因为没有做好,公司还要亏快递费呢。** + +**下单流程:** + +**报价-->物料-->钢网-->电子档资料--> 公司安排生产** + +   **1.报价:** + +**客户提供BOM,告诉我们是单面还是双面贴片就行了,不需要PCB资料就要吧报价.由于本公司报价方案比较透明,客观,客户也可以按网上标准自己来核价,如有不明白的地方可以随时联系我们。需要代购物料,要提供BOM及参数,我们会报价给贵公司** + +**2.物料:** + +**贵公司把物料快递过来,或其它方式送到我公司,如是我公司代购的。这个步骤可能省掉** + +**3.钢网.** + +**贵公司可以自己提供,在3747(带外框)以上的规格,我公司也可代开(需要提供开网资料)** + +**4.电子档资料.** + +**BOM表,单板PCB或坐标资料,需要我公司代开钢网的要提供开网资料。** + +**5.公司安排生产:** + +**来料清点,安排上机,QC,打包,发货** + +## AI + +非常好,样式我非常满意, + + + +现在改进一下smt页面,根据这段文字"为解决以上报价难的问题,我公司采取了公正,透明,客观的报价体系。具体方法如下: + +报价分两部份组成:工程费 + 加片点数的数费用。 + +这两个参数客户都可以自己核算的。 + +# **工程费标准:** + +以下收费标准是有铅,单面贴片的标准,如是双面贴片,则第二面工程费是第一面的半价,也就是下面标准的1.5倍。 + +物料种类以贴片料种类为准,(双面的就是以两种物料种类之和为准),插件料种类不计入工程标准中。所以希望大家在提供BOM时把插件料和贴片费的种类分开,以免统计出错。 + +1-5种: 单面工程费:150元; + +6-10种:单面工程费:200元; + +11-25种:单面工程费:250元; + +26-30种:单面工程费:300元; + +31-35种:单面工程费:350元; + +36-40种:单面工程费:400元; + +41-45种:单面工程费:450元; + +46-50种:单面工程费:500元; + +51-55种:单面工程费:550元; + +56-60种:单面工程费:600元; + +61-65种:单面工程费:650元; + +66-70种:单面工程费:700元; + +71-75种:单面工程费:750元; + +76-80种:单面工程费:800元; + +81-85种:单面工程费:850元; + +86-90种:单面工程费:900元; + +91-95种:单面工程费:950元; + +96-100种:单面工程费:1000元; + +以后往后退:每增加5种物料,工程费加50元。 + +- *焊盘机贴费用标准:** + +加工点数:不任焊盘大小,元件引脚的大小,每个焊盘的单价是:0.0065.例如:电阻是两个脚,二极管也是两件引脚,大的252封的引脚也是算3个脚; IC每个引脚也算一个焊盘。 + +编带,管装,托盘类的元件都能机贴。如不能机贴的则按手放元件的标准计价, + +- *焊盘手放费用标准:** + +如果元件不能机贴的,则需要手放,手放的单价是0.02元一个焊盘。建议客户尽量买编带,管装,托盘类的元件。 + +- *插件费用标准:** + +人工焊接标准: + +每个孔0.05元,元件种类不计贴片元件种类中。 + +波峰焊标准: + +每个孔0.045,,封胶纸留,一条胶纸算一个孔位. + +注意事项是:只限于单面贴片,而且贴片和插件在同一面. + +以上报价标准都是按有铅的标准 来报的,红胶工艺与有铅工艺报价一样,无铅报价是在有铅的基础上的总价上上涨10%。 + +# **重要信息:** + +# **当一款板的焊盘总数*0.0065的费用超过2000元,可以免相应这款板的工程费。** + +# **单款贴片费用超过五千元(不包括插件费),加工费有返点。** + +# **注意事项:** + +- *1.元件损耗问题: 小批量生产,如是样品料,阻容物料:0603,0805以上的了物料,都需要比实际用量多200个,贵重物料可以按需发货,或多一定数量,下单时告诉我公司跟单人员。如物料没有达到上面的要求,有可能会产生物料整理费(200-500元),具体是多少要看物料种类的多少。** +- *2. 最小生产面积:由于生产线都是全自动化流水作业,所以建议客户在生产PCB时要拼板和加工艺边。拼板后面积在15至20CM比较合适。** +- *3.钢网: 本公司代开,75元张(只限3747,其它规格另报),也可以贵公司直接提供,大小在3747以上的规格。** +- *4.交货周期: 物料齐后,无插件定单,3-5天。有插件定单:5-7天。具体时间可以与工作人员沟通。** +- *5.产品品质:正常情况下是目测,如客户品质要求比较高的,可以联系工作人员,进入AOI检测,由于AOI检测需要做程序,可能会产生产一定的费用。批量生产时免费过AOI 。** +- *6.售后问题:由于计价都比较客观,公正,透明,如在计价过程中有出入的支持多退少补。如生产品质有问题,客户可以发顺丰到付件到公司进行相应的程序。生产加工原本没有什么利润了,公司没有理由不去把客户的产品做来,因为没有做好,公司还要亏快递费呢。** +- *下单流程:** +- *报价-->物料-->钢网-->电子档资料--> 公司安排生产** + +**1.报价:** + +- *客户提供BOM,告诉我们是单面还是双面贴片就行了,不需要PCB资料就要吧报价.由于本公司报价方案比较透明,客观,客户也可以按网上标准自己来核价,如有不明白的地方可以随时联系我们。需要代购物料,要提供BOM及参数,我们会报价给贵公司** +- *2.物料:** +- *贵公司把物料快递过来,或其它方式送到我公司,如是我公司代购的。这个步骤可能省掉** +- *3.钢网.** +- *贵公司可以自己提供,在3747(带外框)以上的规格,我公司也可代开(需要提供开网资料)** +- *4.电子档资料.** +- *BOM表,单板PCB或坐标资料,需要我公司代开钢网的要提供开网资料。** +- *5.公司安排生产:** +- *来料清点,安排上机,QC,打包,发货**“修改smt页面的服务介绍、流程、设备数据,并修改下单表单的内容,并添加注意事项、定价规则等内容(样式自行发挥) + + + + + + + + + +## 跨域 + +- 代码 + + ```Java + //不使用security的跨域设置 + @Configuration + public class CorsConfig { + @Bean + public CorsFilter corsFilter(){ + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration corsConfiguration = new CorsConfiguration(); + // corsConfiguration.addAllowedOrigin("*"); + corsConfiguration.addAllowedOrigin("http://localhost:8081"); // 指定前端来源 + corsConfiguration.addAllowedHeader("*"); + corsConfiguration.addAllowedMethod("*"); + source.registerCorsConfiguration("/**",corsConfiguration); + return new CorsFilter(source); + } + + } + ``` + + + + + + + + +## 订单支付 + +9021000145688875 + +### 下单流程 + +**定价类** + +用户报价 —> + +通知后台审核 —> + +通过前台更新订单 —> + +支付金额更新订单,通知后台制作 —> + +等待快递 —> + + + +待审核、待支付、待收货、制作中、已发货、已完成 + +**商品类** + +下单支付 —> + +通知后台 —> + +等待收货即可 + + + +## 定时任务 + +- 不需要导包,springboot包含了 + +将配送中、超过的订单7天自动完成 + + + + + + + +订单 + +基础信息(订单基本+用户信息) + +- 订单编号 +- 创建时间 +- 客户名称 +- 客户电话 +- 订单总金额 +- 当前状态 + + + +订单详情(订单基本+用户信息) + +- 订单配置 + + | | | + |---|---| + |项目|值| + |贴片类型|单面贴片| + |工艺要求|有铅工艺| + |贴片物料种类|12| + |手工焊盘数|12| + +- 订单地址 +- 客户备注信息 + + + +付款信息(订单基本+支付信息) + +- 支付方式 +- 支付金额金额 +- 支付时间 + + + +收货信息 + +- 收货人 +- 收货快照 +- 收货时间 + + + +加工信息 + +- 当前进度 +- 环节、生产负责人(多环节不同负责人) + + + +发货信息 + +- 物流信息(物流公司、运单号) +- 发货时间 +- 收货地址 +- 快递费 + + + +完成信息 + +- 相关单据(发票等) +- 完成时间 + + + +## 功能 + +全部 + +- 查看详情 + +待审核 + +- 审核通过 +- 驳回 +- 修改订单:金额 + +待付款 + +- 通知用户 +- 已付款(设置付款方式) +- 取消订单 + +待收货 + +- 公司代购 +- 已收货(上传照片) + +制作中 + +- 配置流水线 +- 制作完成 + +待发货 + +- 填写快递单号(信息) +- 自提 + +已发货 + +- 已签收 + + + + + + + +## 显示 + +- 显示内容: + - 订单商品清单(品名、规格、数量、单价) + - 客户备注信息 +- 操作按钮: + - 审核通过 + - 驳回 + - 修改订单 + +待付款 + +- 显示内容: + - 订单商品清单 + - 应付金额 + - 支付截止时间 +- 操作按钮: + - 确认付款 + - 取消订单 + - 修改支付方式 + +待收货 + +- 显示内容: + - 原材料清单 + - 预计到货时间 + - 供应商信息 +- 操作按钮: + - 确认收货 + - 延迟到货 + - 查看物流 + +加工中 + +- 显示内容: + - 生产进度 + - 预计完成时间 + - 生产负责人 + - 质检记录 +- 操作按钮: + - 更新进度 + - 质检记录 + - 异常报备 + +已发货 + +- 显示内容: + - 物流信息(物流公司、运单号) + - 发货时间 + - 预计送达时间 + - 收货地址 +- 操作按钮: + - 查看物流 + - 确认送达 + +完成 + +- 显示内容: + - 完成时间 + - 验收记录 + - 评价信息 + - 相关单据(发票等) +- 操作按钮: + - 查看评价 + - 开具发票 + - 申请售后 + + + +## 数据库 + +```Plain + const types = ['已完成', '待审核', '待付款', '待收货', '制作中', '待发货', '已发货']; +``` + +数据库表 + +- smt_order_desc 规格表 +- smt_order_delivery 收货表 +- smt_order_pay 支付表 +- smt_order_process 负责人表 +- smt_order_shipping 物流表 + + + + + +使用vue3(js)+element-plus,编写一个订单、项目表格,表格头部显示订单号、订单名称、流水线、操作;操作那列有配置流水线,其中流水线那列比较特别,要显示多条消息,流水线项目名、状态、操作(已完成按钮),但是按钮是否可点击,取决于流水线项目状态是否为进行 + + + +```Plain + + + +``` + + + +官网请求 + +- 查看商品 +- 查看商品详情 +- 加入购物车、移除购物车 +- 地址管理 + - 增加、删、改、查 +- 消息管理 + - 查看、已读、删除、生成 + + + +- 登录、退出登录 +- 个人信息修改 + + + + + +- 订单结算界面:立即购买商品:购物车结算商品 + - 付款 —> 创建订单、自动跳转支付宝付款 +- 在线定价 +- 订单管理 + - 查看、付款 + + + +| | | | | | | +|---|---|---|---|---|---| +||X|chatGpt|claude|通义|得分| +|qodes|7|7|7|6|27| +|qodix|6|6|6|4|22| +|qodedev|5|5|4|5|19| +|qodis|4|2|5|7|18| +|qoade|3|4|1|3|11| +|qohd|2|1|2|1|6| +|qoyd|1|3|3|2|9| \ No newline at end of file diff --git a/posts/blog/工作/项目/smt项目/image43.png b/posts/blog/工作/项目/smt项目/image43.png new file mode 100644 index 0000000..d06ad1f Binary files /dev/null and b/posts/blog/工作/项目/smt项目/image43.png differ diff --git a/posts/blog/工作/项目/加密数据中心.md b/posts/blog/工作/项目/加密数据中心.md new file mode 100644 index 0000000..5936676 --- /dev/null +++ b/posts/blog/工作/项目/加密数据中心.md @@ -0,0 +1,87 @@ +--- +title: 加密数据中心 +date: 2025-04-23 +categories: [工作, 项目] +tags: [项目] +--- + + +# 📦 安全数据中心 — 端对端加密存储系统 + +本项目是一个**端对端加密的数据中心平台**,支持存储**文字、文件**等多类型数据。所有数据加密、解密过程均在客户端完成,服务器仅存储密文,保障用户隐私安全。 + +--- + +## ✨ 功能特点 + +- 📑 支持文字、文件加密存储 +- 🔒 每个用户独立 RSA 密钥对 +- 🗝️ 私钥本地解锁、自定义密钥派生 AES 密钥 +- 📁 全端对端加密,服务器零明文存储 +- ⚙️ 多重加密机制,保障数据安全 + +--- + +## 🔐 加密流程 + +### 📌 私钥生成与存储 + +1. 用户注册时,客户端生成一对 RSA 公钥 / 私钥。 +2. 用户设置自定义**解锁密钥**。 +3. 使用 PBKDF2(Password-Based Key Derivation Function 2)算法,基于解锁密钥派生 AES 密钥。 +4. 使用该 AES 密钥对用户私钥进行加密,将密文私钥上传服务器保存。 + +--- + +### 📌 数据加密 + +1. 客户端生成**随机 AES 密钥**。 +2. 使用随机 AES 密钥对数据(文字 / 文件)进行加密。 +3. 使用用户 RSA 公钥,对随机 AES 密钥进行加密。 +4. 将**加密后的数据**、**加密后的 AES 密钥**上传服务器。 + +--- + +## 🔓 解密流程 + +1. 用户输入自定义**解锁密钥**。 +2. 使用 PBKDF2 派生 AES 密钥。 +3. 使用该 AES 密钥解密服务器存储的**私钥密文**,获得用户私钥。 +4. 使用私钥解密服务器返回的**随机 AES 密钥密文**。 +5. 使用解密得到的 AES 密钥,解密对应数据。 + +--- + +## 📦 技术栈 + +- 📜 前端:Vue 3 + Element Plus +- ☁️ 服务端:Spring Boot(或其他支持 HTTPS 的后端) +- 🔐 加密算法:RSA、AES、PBKDF2 +- 📚 密钥管理:端到端加密,服务器不保存明文私钥及解锁密钥 + +--- + +## 📌 安全说明 + +- 所有私钥、解锁密钥不在服务端存储 +- 数据加解密操作全在客户端执行 +- 服务端仅存储密文数据和加密后的 AES 密钥 +- 多重对称 + 非对称加密,保障数据链路和静态数据双重安全 + +--- + +## 📷 项目截图 + +> 📌 此处可放网站首页、上传界面、数据管理界面截图 + +--- + +## 📖 License + +MIT License + +--- + +## 📬 联系我 + +如有任何问题或建议,欢迎联系开发者 👋 \ No newline at end of file diff --git a/posts/blog/工作/项目/啥都评.md b/posts/blog/工作/项目/啥都评.md new file mode 100644 index 0000000..4a008d6 --- /dev/null +++ b/posts/blog/工作/项目/啥都评.md @@ -0,0 +1,341 @@ +--- +title: 啥都评 +date: 2025-04-30 +categories: [工作, 项目] +tags: [项目] +--- + + +**10天开发计划**(7-15天区间里,10天足够完成可运行版本,预留时间可调度缓冲或提前做优化)。按你的技术栈:Vue 3 + Spring Boot + MySQL,目标是**开发出一个可部署、可使用的“啥都评”基础版**。 + +--- + +## 📅 10天开发计划(第一阶段) + +--- + +- [ ] 📌 第 1 天:环境搭建 & 项目初始化 + +- 详情 + - 搭建开发环境(JDK、MySQL、IDEA、Node、Nginx) + - 新建 Spring Boot 项目,配置好 dev 配置、数据库连接、JWT 安全框架 + - 新建 Vue 3 项目,安装 Element Plus / Tailwind CSS + - 搭建基础路由、Axios 封装、前后端联调代理配置 + +👉 **输出**:能跑起来的空白前后端项目+数据库连接成功 + +--- + +- [ ] 📌 第 2 天:用户系统开发 + +- 详情 + + **后端**: + + - 用户注册、登录、登出接口(JWT token) + - 用户表、token 验证、权限拦截 + + **前端**: + + - 登录/注册页面、表单校验、token 持久化 + - 登录态前端路由守卫 + +👉 **输出**:登录注册功能能跑,登录后进入主页 + +--- + +- [ ] 📌 第 3 天:正经评 - 条目管理与展示 + +- 详情 + + **后端**: + + - 创建 `evaluation_item` 表,新增/获取条目接口 + - 获取评分综合、维度分接口 + + **前端**: + + - 正经评页面,展示条目列表、详情页 + - 发起评价页面(只管理员可发) + +👉 **输出**:正经评页面展示+详情页基本完成 + +--- + +- [ ] 📌 第 4 天:正经评 - 评分与评论 + +- 详情 + + **后端**: + + - `user_score` 表,提交评分接口、评论接口、获取评论列表接口 + - 评论点赞、举报接口 + + **前端**: + + - 条目评分组件(多维度打分) + - 评论展示、发布、点赞、举报功能 + +👉 **输出**:正经评评分+评论功能上线 + +--- + +- [ ] 📌 第 5 天:趣味评 - 发帖与展示 + +- 详情 + + **后端**: + + - `fun_topics` 表,发布趣味评接口、获取列表接口、详情接口 + + **前端**: + + - 趣味评广场页面,展示帖子列表 + - 发帖页面,文字/图片/是否开启评论/投票选项 + +👉 **输出**:趣味评发帖+广场浏览功能完成 + +--- + +- [ ] 📌 第 6 天:趣味评 - 评论与互动 + +- 详情 + + **后端**: + + - `fun_comments` 表,发布评论、点赞、举报、设精选接口 + - 评论回复机制(仅一级) + + **前端**: + + - 趣味评帖子详情页,评论发布、回复、点赞、举报 + - 精选评论标记(发帖人可设) + + + + +👉 **输出**:趣味评评论互动功能上线 + +--- + +- [ ] 📌 第 7 天:搜索、榜单、推荐功能 + +- 详情 + + **后端**: + + - 条目、帖子关键词模糊搜索接口 + - 按热度、点赞数、评论数排行接口 + + **前端**: + + - 搜索框联想、结果展示页 + - 首页榜单区域、推荐话题展示 + +👉 **输出**:搜索与榜单功能上线 + +--- + +- [ ] 📌 第 8 天:管理后台(简易版) + +- 详情 + + **后端**: + + - 用户管理、举报内容列表、违规处理接口 + + **前端**: + + - 简易管理后台页面(可进首页手动跳转) + +👉 **输出**:基本管理功能可用 + +--- + +- [ ] 📌 第 9 天:前端界面美化、细节完善 + +- 详情 + - Tailwind / Element Plus 样式统一调整 + - 评分动画、按钮 loading、响应式适配 + - 图片上传预览、发帖限制校验 + - 评论时间格式化、美化样式 + +👉 **输出**:整体前端页面清爽、可交互、无重大 bug + +--- + +- [ ] 📌 第 10 天:联调测试 & 部署打包 + +- 详情 + - 前后端联调,修复接口返回问题 + - 前端打包 + Spring Boot 打包 + - Docker/Nginx 配置部署测试 + - 生成部署文档 + +👉 **输出**:项目部署运行,进入试用阶段 + +--- + +## 📊 最终效果: + +- 用户能注册、登录、发帖、评分、评论 +- 正经评+趣味评+搜索+榜单功能可用 +- 管理后台能查看用户、举报、封禁 +- 支持 PC + 手机访问,界面基础美化 +- 可部署的正式版本,后续可升级优化 + +--- + +## 📦 项目目录结构草稿 + +### 后端(Spring Boot) + +```Plain +shadouping-server/ +├── src/main/java/com/shadouping/ +│ ├── config/ // 配置类 +│ ├── controller/ // 控制器层 +│ ├── service/ // 服务层 +│ ├── dao/ // Mapper接口 +│ ├── entity/ // 实体类 +│ ├── security/ // 安全配置 +│ ├── util/ // 工具类 +│ └── ShadoupingApplication.java +├── resources/ +│ ├── application.yml +│ └── static/ +└── Dockerfile +``` + +### 前端(Vue 3 + Vite) + +```Plain +shadouping-web/ +├── src/ +│ ├── api/ // axios 接口封装 +│ ├── assets/ // 静态资源 +│ ├── components/ // 通用组件 +│ ├── pages/ // 页面组件 +│ ├── router/ // 路由配置 +│ ├── store/ // pinia 状态管理 +│ ├── utils/ // 工具方法 +│ └── main.js +├── public/ +└── vite.config.js +``` + +--- + +## 🗄️ 数据库建表 SQL + +```SQL +-- 用户表 +CREATE TABLE user ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(100) NOT NULL, + avatar VARCHAR(255), + role TINYINT DEFAULT 0, -- 0普通 1管理员 + status TINYINT DEFAULT 0, + create_time DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- 正经评条目表 +CREATE TABLE evaluation_item ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + title VARCHAR(100) NOT NULL, + category VARCHAR(50), + description TEXT, + cover_img VARCHAR(255), + create_time DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- 正经评分记录表 +CREATE TABLE user_score ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT, + item_id BIGINT, + score DECIMAL(3,1), + comment TEXT, + create_time DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- 正经评评论点赞表 +CREATE TABLE score_comment_like ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT, + score_id BIGINT +); + +-- 趣味评话题表 +CREATE TABLE fun_topic ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT, + title VARCHAR(100) NOT NULL, + content TEXT, + img VARCHAR(255), + allow_comment TINYINT DEFAULT 1, + create_time DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- 趣味评评论表 +CREATE TABLE fun_comment ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + topic_id BIGINT, + user_id BIGINT, + content TEXT, + parent_id BIGINT DEFAULT 0, + is_selected TINYINT DEFAULT 0, + create_time DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- 举报表 +CREATE TABLE report ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + reporter_id BIGINT, + type VARCHAR(50), -- topic/comment/score + target_id BIGINT, + reason VARCHAR(255), + create_time DATETIME DEFAULT CURRENT_TIMESTAMP +); +``` + +--- + +## 📑 前后端接口清单 + +### 📌 用户接口 + +- POST /api/user/register 注册 +- POST /api/user/login 登录 +- GET /api/user/info 查询个人信息 +- GET /api/user/list 管理员查用户列表 + +### 📌 正经评接口 + +- GET /api/item/list 条目列表 +- GET /api/item/{id} 条目详情 +- POST /api/item/add 新增条目 +- POST /api/item/score 提交评分 +- GET /api/item/score/list 某条目评论 +- POST /api/item/score/like 点赞评论 + +### 📌 趣味评接口 + +- GET /api/topic/list 话题列表 +- GET /api/topic/{id} 话题详情 +- POST /api/topic/add 发布话题 +- POST /api/topic/comment 发布评论 +- POST /api/topic/comment/like 点赞评论 +- POST /api/topic/comment/select 精选评论 + +### 📌 举报接口 + +- POST /api/report 举报 +- GET /api/report/list 管理员查举报记录 + +### 📌 搜索 & 榜单接口 + +- GET /api/search 搜索条目/话题 +- GET /api/rank/item 正经评榜单 +- GET /api/rank/topic 趣味评榜单 \ No newline at end of file diff --git a/posts/blog/工作/项目/外卖项目.md b/posts/blog/工作/项目/外卖项目.md new file mode 100644 index 0000000..7b74c9a --- /dev/null +++ b/posts/blog/工作/项目/外卖项目.md @@ -0,0 +1,81 @@ +--- +title: 外卖项目 +date: 2025-02-11 +categories: [工作, 项目] +tags: [项目] +--- + + +## 开发流程 + +**需求分析** + +- 需求规格说明书、产品原型 + +**设计** + +- UI设计、数据库设计、接口设计 + +**编码** + +- 项目代码、单元测试 + +**测试** + +- 测试用例、测试报告 + +**上线运维** + +- 软件环境安装、配置 + +### 角色分工 + +- 项目经理:对整个项目负责,任务分配、把控进度 +- 产品经理:进行需求调研,输出需求调研文档、产品原型等 +- UI设计师:根据产品原型输出界面效果图 +- 架构师:项目整体架构设计、技术选型等 +- 开发工程师:代码实现 +- 测试工程师:编写测试用例,输出测试报告 +- 运维工程师:软件环境搭建、项目上线 + +### 软件环境 + +- 开发环境(development):开发人员在开发阶段使用的环境,一般外部用户无法访问 +- 测试环境(testing):专门给测试人员使用的环境,用于测试项目,一般外部用户无法访问 +- 生产环境(production):即线上环境,正式提供对外服务的环境 + + + +## 项目介绍 + +架构图 + +- 管理端 + - 员工管理 + - 官网管理 + - 商品管理 + - 订单管理 + - 用户管理 + - 数据统计 + - 来单提示 +- 用户端 + - 微信登录 + - 商品浏览 + - 购物车 + - 用户下单 + - 微信支付 + - 历史订单 + - 地址管理 + - 用户催单 + +**技术选型** + +![image 42.png](外卖项目/image42.png) + +**整体价格** + +![image 1 24.png](外卖项目/image124.png) + + + +![image 2 16.png](外卖项目/image216.png) \ No newline at end of file diff --git a/posts/blog/工作/项目/外卖项目/image124.png b/posts/blog/工作/项目/外卖项目/image124.png new file mode 100644 index 0000000..d346451 Binary files /dev/null and b/posts/blog/工作/项目/外卖项目/image124.png differ diff --git a/posts/blog/工作/项目/外卖项目/image216.png b/posts/blog/工作/项目/外卖项目/image216.png new file mode 100644 index 0000000..7779782 Binary files /dev/null and b/posts/blog/工作/项目/外卖项目/image216.png differ diff --git a/posts/blog/工作/项目/外卖项目/image42.png b/posts/blog/工作/项目/外卖项目/image42.png new file mode 100644 index 0000000..7c4019f Binary files /dev/null and b/posts/blog/工作/项目/外卖项目/image42.png differ diff --git a/posts/blog/工作/项目/文件共享系统.md b/posts/blog/工作/项目/文件共享系统.md new file mode 100644 index 0000000..8c33a01 --- /dev/null +++ b/posts/blog/工作/项目/文件共享系统.md @@ -0,0 +1,327 @@ +--- +title: 文件共享系统 +date: 2024-12-20 +categories: [工作, 项目] +tags: [项目] +--- + + +这个项目描述的目标是创建一个简单的文件共享和管理系统,允许通过用户浏览器访问电脑上开放的文件夹,进行文件预览、下载和上传。后端使用Spring Boot进行处理,前端使用HTML、CSS、JavaScript、Bootstrap和jQuery进行开发。接下来,我们将从前端和后端的角度来分析该项目的设计。 + +### **项目概述** + +1. **后端**:后端程序运行在电脑上,负责处理浏览器的请求。它提供文件夹预览、文件下载、文件上传等功能,并将这些功能以API的形式暴露给前端。后端使用Spring Boot作为框架,可以快速实现RESTful API。 +2. **前端**:前端运行在浏览器上,用户通过浏览器输入地址,访问并与后端交互。前端使用HTML、CSS、JavaScript、Bootstrap和jQuery构建用户界面,实现文件夹预览、文件上传、文件下载等功能。 + +--- + +### **1. 项目功能分析** + +### **文件夹选择与开放(后端处理)** + +- 用户通过电脑上的后端程序选择一个文件夹进行共享,后端程序需要监听该文件夹,并提供接口以供手机浏览器访问。 +- 后端需要提供一个文件夹路径的管理接口,允许用户动态选择不同的文件夹进行开放。 + +### **浏览器端访问(前端页面)** + +- 手机用户在浏览器中输入地址,后端响应一个文件浏览器页面,该页面展示开放文件夹中的文件列表,并提供文件预览、下载和上传功能。 +- 通过**Bootstrap**和**jQuery**,前端可以快速实现响应式布局和交互式界面。 + +### **文件预览(前端展示)** + +- 前端展示文件夹中的文件列表,可能包括文件名、文件类型(如图片、文本等)、文件大小等。 +- 对于图片文件,前端可以使用``标签进行预览,其他类型的文件则可以提供下载链接或提示。 +- 使用**Bootstrap**的卡片组件或表格展示文件列表。 + +### **文件下载(前端请求)** + +- 当手机用户点击某个文件时,前端通过Ajax向后端发起请求,后端返回文件内容。 +- 后端使用Spring Boot的文件流功能将文件传输给前端,浏览器触发文件下载。 + +### **文件上传(前端交互)** + +- 前端提供一个文件上传表单,用户可以选择文件并点击上传按钮。 +- 上传功能通过HTML表单实现,文件通过POST请求提交给后端。 +- 后端接收上传的文件并保存到开放的文件夹中,可以使用Spring Boot的文件上传功能(如`MultipartFile`)。 +- **jQuery**用于简化表单提交,可能还会用到Ajax进行无刷新上传。 + +### **文件操作安全性** + +- 确保文件上传和下载的路径安全,避免恶意文件的执行。 +- 可能需要对上传的文件类型进行限制,确保只允许上传特定类型的文件(如图片、文档等)。 + +--- + +### **2. 技术栈分析** + +### **前端技术** + +- **HTML/CSS**:用于构建页面结构和样式,创建一个清晰直观的界面。CSS将帮助你实现响应式设计,确保手机端的良好展示。 +- **Bootstrap**:提供响应式布局和UI组件(如表格、卡片、按钮、导航栏等)。Bootstrap的网格系统将确保前端页面在不同设备上良好的显示效果。 +- **jQuery**:简化DOM操作和Ajax请求,特别是在处理用户交互(如点击按钮、文件选择、文件上传等)时,可以有效减少JavaScript的复杂性。 +- **JavaScript**:处理用户交互和Ajax请求,如发送文件下载请求、提交文件上传等。 + +### **后端技术** + +- **HTTP服务器**: + - 提供静态资源(HTML、CSS、JavaScript等)。 + - 响应设备的连接请求,分发必要的页面和资源。 +- **WebRTC信令服务器**: + - 使用WebSocket实现信令通道,负责交换WebRTC的SDP和候选者信息,建立P2P连接。 +- **文件操作接口**: + - 提供REST API,支持文件目录预览、文件下载、文件上传操作。 + - API 基于开放文件夹动态生成,确保实时性。 + +--- + +### **3. 系统交互流程** + +1. **初始化与文件夹选择**: + - 后端程序启动后,提供一个接口供用户选择要共享的文件夹。后端将记录此文件夹路径,并保持该路径的文件开放状态。 + - 后端提供一个REST API接口,前端通过GET请求获取文件夹中所有文件的列表。 +2. **前端展示文件夹内容**: + - 手机浏览器输入后端程序的地址后,后端返回一个包含文件列表的页面,文件列表展示在Bootstrap的表格或卡片组件中。 + - 前端通过Ajax请求后端获取文件夹内容(如文件名、大小、类型等),并在页面中展示。 +3. **文件预览**: + - 对于图片文件,前端直接展示图像。 + - 对于其他类型的文件(如PDF、文本文件等),前端提供下载链接或简单的文本预览功能。 +4. **文件下载**: + - 当用户点击某个文件的下载链接时,前端通过Ajax请求后端,并获取文件内容。 + - 后端通过文件流返回文件,浏览器提示用户下载该文件。 +5. **文件上传**: + - 前端提供一个文件上传表单,允许用户选择文件并提交。文件通过POST请求传递给后端。 + - 后端接收到文件后,将其存储到开放的文件夹中,确保文件的安全存储和处理。 + +--- + +### **4. 安全性与权限控制** + +- **文件路径限制**:后端应确保用户不能上传到系统敏感目录或访问其他用户的文件。 +- **文件类型验证**:为了防止恶意文件的上传,后端应验证上传文件的类型,如仅允许图片、PDF等常见类型。 +- **文件访问权限**:可能需要根据用户角色或会话控制文件的上传、下载权限,确保只有授权用户可以访问文件夹内容。 + +--- + +### **5. 性能与优化** + +- **异步处理**:对于文件上传和下载,前端可以使用Ajax异步请求,不阻塞页面的其他操作。 +- **大文件传输**:对于较大的文件,后端可以实现分块传输或支持流式下载,避免一次性加载导致性能问题。 + +--- + +### **总结** + +这个项目涉及的技术栈和功能实现相对简单,但也需要考虑文件管理、安全性和性能等问题。通过Spring Boot实现后端API,通过HTML、CSS、JavaScript、Bootstrap和jQuery开发前端页面,可以构建一个基本的文件共享系统,允许用户在手机浏览器中访问和操作电脑上的共享文件夹 + + + + + +分析一个该项目,不需要给我代码,项目描述:电脑安装应用程序,选择一个开放文件夹,其他设备通过浏览器输入地址,远程服务器响应页面给设备,设备与电脑通过WebRTC进行p2p连接,连接成功后,该设备可以对开放文件夹进行预览、下载、上传文件到开放文件夹;前端通过HTML、css、js、Bootstrap、jQuery进行开发,后端使用springboot进行处理,不需要数据库存储信息 + + + + + +```SQL +上传文件描述包 +------------------ +头部(16字节): +1字节:消息类型 +4字节:文件ID +4字节:通道索引 +4字节:数据部分长度 +------------------ +数据部分: +文件名称 +文件地址 +分片大小 +分片个数 +------------------ +``` + + + +```SQL +上传文件数据包 +------------------ +头部(16字节): +1字节:消息类型 +4字节:文件ID +4字节:分片索引 +4字节:分片大小 +------------------ +数据部分: + 10101010101001 +------------------ +``` + +```SQL +上传文件收到确认包 +------------------ +头部(16字节): +1字节:消息类型 +4字节:文件ID +``` + + + + + +```SQL +下载文件描述包 +------------------ +头部(16字节): +1字节:消息类型 +4字节:文件ID +4字节:批量下载 +4字节:文件消息长度 +------------------ +数据部分: +文件地址 +------------------ +``` + +```SQL +下载文件描述包 +------------------ +头部(16字节): +1字节:消息类型 +4字节:文件ID +4字节:批量下载 +4字节:文件消息长度 +------------------ +数据部分: +文件名称 +文件大小 +分片大小 +分片个数 +是否批量下载 +------------------ +``` + +```SQL +下载数据包 +------------------ +头部(16字节): +1字节:消息类型 +4字节:文件ID +4字节:分片索引 +4字节:分片大小 +------------------ +数据部分: + 10101010101001 +------------------ +``` + + + + + + + +`**${}**`:在模板字面量中,`${}` 语法用来插入 JavaScript 表达式的结果。 + + +优化 + +- [ ] 添加多个下载管道 +- [ ] 添加加载动画 +- [ ] 对正在加载的资源进行管理,减少反复加载 +- [ ] 客户端切换网络自动重连 +- [ ] 浏览器端添加状态通知 +- [ ] 前端使用vue进行重写 +- [ ] 对数据进行加密 +- [ ] 对房间管理进行优化 + - [ ] 创建房间时判断,判断是否有效 + + + + + + + +### 见习调研报告 + +### 调研背景 + +本次见习活动的主要内容是参观中科光芯公司,了解其在高新技术领域的运营服务及技术应用。作为一家专业从事光电子器件制造、5G通信技术、量子计算、云计算和物联网等领域的企业,中科光芯在信息技术产业链中扮演着重要角色。通过此次见习,我希望能更深入地理解这些技术在实际中的应用,尤其是与计算机科学与技术相关的交叉领域。 + +### 公司介绍 + +中科光芯公司是一家以科技研发为核心的高新技术企业,业务涵盖以下几个方面: + +1. **光电子器件制造与销售**:公司专注于光电子器件的研发和生产,这些器件是现代通信技术(如光纤通信)不可或缺的核心组件。 +2. **5G通信技术服务**:公司致力于提供高效的5G通信解决方案,支持通信网络的搭建与优化。 +3. **量子计算技术服务**:量子计算是未来计算机科学的重要方向之一,公司通过研究量子算法和硬件架构推动这一技术的应用。 +4. **云计算与物联网技术服务**:公司为企业和个人用户提供先进的云计算设备及相关技术支持,同时拓展物联网在智能家居、智能交通等领域的应用。 +5. **数字技术服务与信息技术咨询**:针对行业客户的需求,公司提供定制化的数字技术支持和咨询服务。 + +### 技术与应用分析 + +**1. 光电子器件与计算机科学的结合** + +光电子器件是现代网络通信中的核心技术,而计算机科学在这一领域的应用主要集中于数据传输协议、信号处理算法和器件控制系统的设计。例如,光电子器件的信号处理需要通过高效的算法和软件支持,这对于计算机专业的学生来说,是一个非常有潜力的研究方向。 + +**2. 5G与边缘计算** + +5G通信技术的高速、低延迟特性,为边缘计算的发展提供了强有力的支持。在参观过程中,我们了解到5G基站和设备的研发流程。这些基站需要运行高度优化的软件系统,来实现快速数据处理和传输。这让我认识到,计算机科学在优化网络协议、开发高效算法和实现实时数据处理方面的重要性。 + +**3. 云计算与大数据** + +云计算技术的核心在于分布式计算和存储架构,而大数据技术的重点是如何高效地存储和分析海量数据。公司展示了一些云计算设备的运行原理和技术特点,例如虚拟化技术和容器化部署。这让我更加明确了未来在云计算相关领域的职业发展方向。 + +**4. 量子计算的挑战与机遇** + +量子计算是计算机科学的一个前沿方向。公司在这一领域的研究让我认识到,虽然量子计算目前尚处于实验阶段,但其在解决复杂算法和优化问题上的潜力是巨大的。这一领域需要扎实的计算机科学基础,并结合量子物理的原理进行跨学科研究。 + +### 调研结论 + +通过本次调研,我认识到计算机科学与其他技术领域之间的深度融合正在塑造未来的信息技术产业。这些技术的开发与应用需要从算法设计到硬件支持的全方位配合,同时也需要具备解决复杂工程问题的能力。未来,我将更加注重计算机基础知识与实践能力的结合,尤其是在算法优化和高性能计算方面深入学习。 + +--- + +### 见习总结 + +### 总结背景 + +本次见习活动以参观中科光芯公司为主,通过对其生产、研发和服务的实地了解,我深刻体会到理论知识与实际技术之间的紧密联系,同时对计算机科学与其他技术领域的结合有了更全面的认识。 + +### 见习内容回顾 + +1. **了解企业文化和工作环境** + + 参观过程中,公司工作人员为我们详细介绍了企业的发展历程、核心价值观及团队协作模式。从中我了解到,中科光芯非常重视创新与合作,员工的专业背景涵盖了计算机科学、光电子、通信工程等多个领域。这样多学科融合的团队结构是解决复杂技术问题的基础。 + +2. **技术学习与实践体验** + + 公司为我们展示了光电子器件的制造过程及其在通信领域的应用。通过实地观察,我对光电子技术的复杂性及其对现代通信的重要性有了更深的理解。此外,在5G基站研发和云计算设备演示环节,公司技术人员详细讲解了相关设备的运行原理,并分享了研发过程中的挑战与解决方案。这让我更加体会到扎实的专业知识对于技术创新的重要性。 + +3. **职业规划的启发** + + 见习过程中,我了解到,计算机科学与光电子、量子计算、物联网等技术的结合是未来的发展趋势。例如,量子计算的研究需要深入的算法优化能力,而物联网则要求对分布式系统和实时数据处理有较强的把握。这些信息让我对未来的学习方向和职业规划有了更明确的目标。 + + +### 个人收获与感悟 + +**1. 理论与实践结合的重要性** + +此次见习让我认识到,单纯的理论知识无法完全满足实际工作的需要,必须通过实践来加深对技术的理解。例如,在光电子器件的信号处理和控制系统开发中,需要将编程技巧与电子学原理相结合,这对我的学习方式提出了新的要求。 + +**2. 跨学科知识的重要性** + +计算机科学不仅仅是一个独立的学科,它与其他技术领域有着千丝万缕的联系。例如,光电子器件的高效运行需要优化的算法支持,而量子计算则需要跨越计算机科学和物理学的界限进行研究。这让我更加重视跨学科知识的学习。 + +**3. 团队协作能力的培养** + +在公司参观中,我看到不同背景的工程师如何在同一个项目中高效协作,这让我认识到团队协作能力的重要性。无论是在研发还是实施阶段,计算机科学专业的学生都需要具备与他人沟通和合作的能力。 + +### 总结展望 + +本次见习不仅让我对专业知识有了更深刻的理解,也让我明确了未来的努力方向。在未来的学习中,我将更加注重以下几点: + +1. 深入学习与实践结合的技术,尤其是在算法设计和系统优化方面。 +2. 拓展跨学科知识,例如量子计算和物联网领域的前沿技术。 +3. 提升团队协作能力,与不同专业背景的同学和同事更好地合作。 + +通过这次见习,我更加确信,计算机科学的未来充满挑战和机遇。只有不断学习、勇于实践,才能在这一领域有所建树。 \ No newline at end of file diff --git a/posts/blog/工作/项目/网盘项目.md b/posts/blog/工作/项目/网盘项目.md new file mode 100644 index 0000000..d7395c9 --- /dev/null +++ b/posts/blog/工作/项目/网盘项目.md @@ -0,0 +1,926 @@ +--- +title: 网盘项目 +date: 2024-10-07 +categories: [工作, 项目] +tags: [项目] +--- + + +- 描述· + - 单人云盘、team云盘、局域网使用 +- 存储 + - 文件信息:MySQL + - 文件存储:磁盘 +- 模式 + - 局域网使用 + - 仅开放一个文件夹,提供用户使用,只允许上传和查看 + - 互联网使用 + - 允许多个用户一起使用 +- 基础功能 + - 登录 + - root用户 + - 普通用户 + - 查询文件 + - 简单查询 + - 条件程序(SQL) + - 上传文件 + - 下载文件 + - 删除文件 + - 链接分享 + - 用户管理(root用户) + - 创建用户 + - 修改用户文件权限 + - 删除用户 + +命令表 + +|命令|描述|使用| +|---|---|---| +|ls|当前目录下的文件|ls| +|pwd|当前位置|pwd| +|show|查看文件结构|show| +|stat 文件名|查看文件具体信息|stat xxx| +|cd /x/|切换目录|2条| +|mkdir xx|创建目录|2条| +|rm xxx|删除文件、文件夹|2条| +|mv xx xx|移动|三条| +|rename xx xx|重命名|三条| +|up xx xx|上传文件|三条| +|down xx xx|下载文件|三条| + + + +```SQL +myproject/ +├── src/ +│ └── com/ +│ └── example/ +│ ├── Main.java +│ └── Helper.java +└── bin/ +``` + +![ff3c9978-5098-479a-921b-ba655bced945.png](网盘项目/ff3c9978-5098-479a-921b-ba655bced945.png) + + + +```SQL +联网判断 +cd /xx/xx +cd ../xx +cd ./xx + +本地判断 +cd .. +cd . +cd / +``` + +![image 41.png](网盘项目/image41.png) + +![image 1 23.png](网盘项目/image123.png) + +![image 2 15.png](网盘项目/image215.png) + +```SQL +/ +├── src/ +│ └── com/ +│ └── example/ +│ ├── Main.java +│ └── Helper.java +└── bin/ + + +/ +|--- 我的资源 +| +``` + +通信协议 + +```SQL + +ls +cd 目录 +mv 目录 目录 +up 文件 目录 +down 文件 目录 + +ls +长度 +id;目录; + +LTV 文件形式(1+8) +属性长度 byte +(命令)属性 +行为 byte +文件长度 long +内容 byte[length] + + +请求头读完了 +请求头没读完 +buffer满了 +读事件结束了 +客户端关闭(基本不可能,因为还需要写操作) +异常断开 +情况: +12 121221323 +128 12323231231232132132321121321321321321322121 + +短的分析 +网络好 +12 121221323 +网络不好 +12 121 +2 +21323 + +长的分析 +网络好 +128 123232312312321321323 +21121321321321321322121 +网络不好 +128 12323 +23 +123123 +2132 +132321121321321321321322121 +``` + +1. **返回值为正数**: + - 表示成功读取了数据,返回值是实际读取的字节数。 + - 例如,如果读取了 10 个字节,返回值为 10。 +2. **返回值为 0**: + - 表示没有数据可读,但连接仍然处于打开状态。 + - 这种情况通常发生在对端还没有发送数据,或者数据已经全部读取完毕但对端还没有关闭连接。 +3. **返回值为 -1**: + - 表示对端已经关闭了连接,没有更多的数据可读。 + - 这是判断对端已经断开连接的关键标志。 + + + +transferTo和transferFrom都是用于通道间直接传输数据,效率高(可能使用零拷贝): + +```Java +// transferTo: 从source通道传输到target通道 +sourceChannel.transferTo(position, count, targetChannel); + +// transferFrom: 从source通道接收数据到target通道 +targetChannel.transferFrom(sourceChannel, position, count); +``` + +主要特点: + +1. 适用于FileChannel、SocketChannel等 +2. position指定开始位置 +3. count指定最大传输字节 +4. 返回实际传输字节数 +5. SocketChannel传输可能不完整,需要循环传输 + +需要代码示例吗? + +**请求结构** + +```JavaScript +下载请求 +长度:76(1) +类型:10(1) +用户id:533909ec-2b8f-4b90-87ec-77c81cbc6fa3(36) +文件地址:/我的资源/DJI_20241028195835_0071_D.MP4(39) +``` + +```JavaScript +下载响应 +长度:44(1) +类型:0(1) +请求长度:20(4) +文件实际地址:E:/视频/DJI_20241028195835_0071_D.MP4(39) +``` + + + +```JavaScript +下载请求 +长度:44(1) +类型:11(1) +第几份:0(4) +文件地址:E:/视频/DJI_20241028195835_0071_D.MP4(39) +``` + +```JavaScript +下载响应 +长度:13(1) +类型:0(1) +起始位:0(8) +文件长度:1024*1024*100(4) +--------------------------------- +文件内容:100101010 +``` + +```JavaScript +上传请求 +长度:76(1) +类型:20(1) +用户id:533909ec-2b8f-4b90-87ec-77c81cbc6fa3(36) +文件地址:/我的资源/DJI_20241028195835_0071_D.MP4(39) +``` + +```JavaScript +上传响应 +长度:25(1) +类型:0(1) +文件保存地址:/java/data/DJI_20241028195835_0071_D.MP4(24) +``` + + + +```JavaScript +上传请求 +长度:37(1) +类型:10(1) +起始位:0(8) +长度:1024*1024*10(4) +文件保存地址:/java/data/DJI_20241028195835_0071_D.MP4(24) +-------------------------------- +文件内容:100101010 +``` + +```JavaScript +上传响应 +长度:1(1) +类型:0(1) +``` + + + +--- + +其他请求 + +```JavaScript +查看请求:show 文件 +长度:37(1) +类型:7(1) +用户id:533909ec-2b8f-4b90-87ec-77c81cbc6fa3(36) +文件保存地址:E:/视频/result/DJI.MP4(24) +``` + + + +```JavaScript +下载请求 +长度:76(1) +类型:10(1) +用户id:533909ec-2b8f-4b90-87ec-77c81cbc6fa3(36) +文件地址:/我的资源/DJI_20241028195835_0071_D.MP4(39) +``` + +```JavaScript +下载响应 +长度:48(1) +类型:0(1) +文件长度:2027481740(8) +文件保存地址:/java/data/DJI_20241028195835_0071_D.MP4(24) +``` + + + +```JavaScript +下载请求 +长度:52(1) +类型:11(1) +起始位:0(8) +文件长度:1024*1024*100(4) +文件地址:/java/data/DJI_20241028195835_0071_D.MP4(24) +``` + +```JavaScript +下载响应 +长度:1(1) +类型:0(1) +--------------------------------- +文件内容:100101010 +``` + +```JavaScript +上传请求 +长度:76(1) +类型:20(1) +用户id:533909ec-2b8f-4b90-87ec-77c81cbc6fa3(36) +文件地址:/我的资源/DJI_20241028195835_0071_D.MP4(39) +``` + +```JavaScript +上传响应 +长度:25(1) +类型:0(1) +文件保存地址:/java/data/DJI_20241028195835_0071_D.MP4(24) +``` + +```JavaScript +上传请求 +长度:37(1) +类型:21(1) +起始位:0(8) +长度:1024*1024*10(4) +文件保存地址:/java/data/DJI_20241028195835_0071_D.MP4(24) +-------------------------------- +文件内容:100101010 +``` + +```JavaScript +上传响应 +长度:1(1) +类型:0(1) +``` + + + +### **如何将tinyint值NULL更改为默认的'0‘** + +- 默认处可以输入,如果无法修改,先设置为可以为null再操作 + + ![image 3 14.png](网盘项目/image314.png) + + + + +使用netty开发一个分片上传、下载系统,客户端发出下载请求,请求包含文件必要信息(文件名、起始位、文件长度等),服务端接收消息进行响应,响应内容保存文件必要信息、文件内容,客户端接收响应,根据响应保存文件;上传逻辑类似 + + + +```Java +public void channelRead(ChannelHandlerContext ctx, Object o) throws Exception { + ByteBuf buf=(ByteBuf)o; + if (!receivingFile) { + // 处理元数据 + metadata = FileMetadata.fromBytes(buf); + System.out.println("客户端接受了消息: "+metadata); + prepareFileReceiving(metadata); + receivingFile = true; + ctx.fireChannelRead(buf); + return; + } + + buf = buf.unwrap(); + //分了两次 + if(buf.readableBytes()==0) { + //需要继续处理 + ctx.fireChannelRead(buf); + return; + } + System.out.println("==========================文件处理!"); + int read = fileChannel.write(buf.nioBuffer()); + System.out.println("====================="+read); + + } +``` + + + +发送byte请求 + +- 传入bytebuf,对象自己转成byte + + + +接收byte请求 + +- 传入byteBuf,返回对象 + + + +移除handle,系统会将剩余的handle都处理了! + + + + + +```JavaScript +下载请求 +是否有文件:false +起始位:0 +长度:1024 +服务器文件地址:/java/data/DJI.MP4 +用户文件地址:E:/result/Df.MP4 +``` + +```JavaScript +下载响应 +是否有文件:true +起始位:0 +长度:1024 +服务器文件地址:/java/data/DJI.MP4 +用户文件地址:E:/result/Df.MP4 +--------------------------------- +文件内容:100101010 +``` + +```JavaScript +上传请求 +是否有文件:true +起始位:0 +长度:1024 +服务器文件地址:/java/data/DJI.MP4 +用户文件地址:E:/result/Df.MP4 +--------------------------------- +文件内容:100101010 +``` + +```JavaScript +上传响应 +状态:true +消息:保存成功 +``` + + + +分片请求 + +检查是否有分片 + +```JavaScript +下载请求 +是否有文件:false +起始位:0 +长度:1024 +服务器文件地址:/java/data/DJI.MP4 +用户文件地址:E:/result/Df.MP4 +``` + + + + + + + +```JavaScript +下载响应 +是否有文件:true +起始位:0 +长度:1024 +服务器文件地址:/java/data/DJI.MP4 +用户文件地址:E:/result/Df.MP4 +--------------------------------- +文件内容:100101010 +``` + + + +```JavaScript +上传请求 +是否有文件:true +起始位(第几片):1 +长度:1024 +服务器文件地址:/tmp/md5/1 +用户文件地址:E:/result/Df.MP4 +--------------------------------- +文件内容:100101010 +``` + +```JavaScript +上传响应 +状态:true +消息:保存成功 +``` + +```JavaScript +合并请求 +是否有文件:false +起始位(总片数):18 +长度:1024 +服务器文件地址:/tmp/md5/1 +用户文件地址:E:/result/Df.MP4 +``` + +```JavaScript +上传响应 +状态:true +消息:保存成功 +``` + + + + + + + + + +- 明天功能 + - ~~下载指定id~~ + - ~~优化结构~~ + - ~~生成idTree~~ +- 分享功能(下周做) + - 指定id,生成链接 + + +数据库设计 + + + +发送文件 + +```JavaScript +命令:send file +ip:192.168.1.100 +测试成功! +输入本地文件地址: +``` + + + + + +接收文件 + +```JavaScript +命令:receive file +ip:192.168.1.100 +(回车关闭) +``` + + + +以下是一个典型网盘程序的8个功能模块,它们涵盖了大多数网盘应用的核心功能: + +**1. 用户管理模块** + +- **功能描述**:提供用户账户注册、登录功能 +- **具体功能**: + - 用户注册和登录 + - 用户信息修改 + - 查看空间 + - 修改保存地址配置 + - 密码找回(❓) + +**2. 文件/夹管理模块** + +- **功能描述**:创建、修改、删除文件夹 +- **具体功能**: + - 创建文件夹 + - 移动、删除、重命名文件/夹 + +3. **文件查看与信息模块** + +- **功能描述**:通过命令行显示文件的基本信息、文件结构属性 +- **具体功能**: + - 列出文件夹树、列表结构 + - 查看文件的详细信息(如大小、修改时间、类型等) + +**4. 搜索模块** + +- **功能描述**:支持用户在网盘中搜索文件 +- **具体功能**: + - 文件名搜索(模糊查询) + - 按类型(图片、视频、文档等)搜索 + +**5. 文件上传、下载模块** + +- **功能描述**:关于文件上传、下载 +- **具体功能**: + - 自定义通信协议 + - 支持文件、文件夹上传下载 + - 提供下载速度、进度显示 + - md5校验避免重复文件 + - 大文件分片传输 + - ~~文件加密~~ + +**6. 文件分享模块** + +- **功能描述**:允许用户将文件或文件夹分享给其他用户或生成共享链接。 +- **具体功能**: + - 生成共享链接(可设置有效期) + - 分享文件的管理(取消分享) + - 通过链接进行保存、下载 + +**7.管理员管理模块** + +- **功能描述**:提供管理员对整个网盘系统的全面控制,包括用户管理、删除文件的恢复、存储空间分配和其他高级管理任务。 +- **具体功能**: + - 管理所有用户账号:创建、删除和修改用户账号 + - 管理删除文件:查看和恢复用户删除的文件 + - 分配和调整用户的存储空间配额 + - 对异常操作进行干预(如锁定账号或限制操作) + - ~~查看和统计所有用户的存储使用情况~~ + - ~~审查用户的操作日志(如文件的上传、删除、分享等操作)~~ + +**8.局域网文件传输模块** + +- **功能描述**:提供用户在同一局域网内的直接文件传输功能,支持快速的面对面文件共享,避免互联网依赖。 +- **具体功能**: + - 支持pc客户端半双工通信(便于接收文件),基于自定义协议,文件上传效率搞 + - 支持跨设备的局域网通信,基于http协议,上传效率底 + +**总结** + +这8个功能模块涵盖了网盘应用的各个方面,从文件操作到安全保护,提供了一个完整的网盘系统功能集合。每个模块都可以根据具体需求进行扩展和定制,以满足不同用户群体的使用需求。 + + + +下载速度显示 + +~~速度显示~~ + +分享 + +- 查看分享表 +- 取消分享 +- 设置分享时间 +- 设置密码 + +注册,以及空间显示 + +完成分享测试 + + + +管理员 + +前端界面优化(ai) + +- 选择上传 +- 速度显示 + + + + + +pc、手机传输 + +- 手机端 + - 上传:扫码选择上传 + - 下载: +- pc端 + - 上传 + - 下载 + + + + + +```Java + _ _ ____ _ _ _ _ .-~~~-. +| | | | _ \| \ | | | | | .-~~' __ `~~-. +| |__| | |_) | \| | | | | / .-~ ~-. \ +| __ | _ <| . ` | | | | | .-' `-. | +| | | | |_) | |\ | |__| | \ / \ / +|_| |_|____/|_| \_|\____/ `-.~ ._ .,., ,..~-' + + .--.-. + ( ( )__ + (_, \ ) ,_) + '-'--`--' + + .-~~~-. + .-~~' __ `~~-. + / .-~ ~-. \ +| .-' `-. | + \ / \ / + `-.~ ._ .,., ,..~-' + +``` + + + +对服务端异常进行处理,将序列化错误、连接中断 + + + +处理服务器端异常 + +![image 4 13.png](网盘项目/image413.png) + +连接异常 + +![image 5 12.png](网盘项目/image512.png) + +登录报错 + +![image 6 10.png](网盘项目/image610.png) + +```Java +# 登录mysql,在默认安装时如果没有让我们设置密码,则直接回车就能登录成功。 +sudo mysql -uroot -p +# 设置密码 mysql8.0 +ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '新密码'; +ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'hbnu'; +# 设置密码 mysql5.7 +set password=password('新密码'); +# 配置IP 5.7 +grant all privileges on *.* to root@"%" identified by "密码"; +# 刷新缓存 +flush privileges; +``` + + + +题目描述 +假设系统中有A、B、C三类资源,且有五个并发进程,要求输入系统空闲资源数Avaiable,以及每个进程运行所需的资源总量Caim、已经分配得到的资源量Alocation,利用银行家算法计算资源总量Resource和需求矩阵Need。 +输入格式 +程序要求输入六行,以回车符号作为分隔,第一行是三个整数,整数之间以空格作为分隔,表示当的系统A、B、C三类空闲资源数Avallable,下面的五行分别表示每个进程运行所需的资源总量Caim和已经分配得到的资源量Alocatin;每行有7个数据,以空格作为分隔。首先输入一个字符串(长度小于等于10),为进程名;第2、3、4个数掘类型为整型,表示相应进程运行所需A、B、C三种资源总量Caim;第5、6、7个数据类型为整型,表示相应进程已经分配得到的A、B、C三种资源量Alocation。 +输出格式 +输出六行,以回车符号作为分隔,第一行是三个整数,整数之间以空格作为分隔,表示系统的A、B、C三类资源总量Resource,下面的五行分别表示每个进程运行还需要的资源量:每行有4个数据,以空格作为分隔。首先输出一个字符串(长度小于等于10),为进程名;第2、3、4个数据类型为整型,表示相应进程运行还需的A、B、C三种资源量。 +输入样例 +3 3 2 +P0 7 5 3 0 1 0 +P1 3 2 2 2 0 0 +P2 9 0 2 3 0 2 +P3 2 2 2 2 1 1 +P4 4 3 3 0 0 2 +输出样例 +10 5 7 +P0 7 4 3 +P1 1 2 2 +P2 6 0 0 +P3 0 1 1 +P4 4 3 1 + +![QQ_1733367586038.png](网盘项目/QQ_1733367586038.png) + + + +题目描述 +假设系统中有A、B、C三类资源,且有四个并发进程,要求输入资源总量Resource,以及每个进程运行所需的资源总量Ciaim和已经分配得到的资源量Alocation,利用银行家算法判断当前状态是否为安全状态,若为安全状态则给出一个安全序列。 +输入格式 +程序要求输入五行,以回车符号作为分隔,第一行是三个整数,整数之间以空格作为分隔,表示A、B、C三类资源的总量,下面的四行分别表示每个进程运行所需的资源总量Claim和已经分配得到的资源量Alocation;每行有7个数据,以空格作为分限。首先输入一个字符串(长度小于等于10),为进程名;第2、3、4个数据类型为整型,表示相 +应进程运行所需A、B、C三种资源总量Claim;第5、6、7个数据类型为整型,表示相应进程已经分配得到的A、B、C三种资源量Alocation。 +输出格式 +输出一个字符串。若当前为不安全状态则输出为"false”(不含双引号,所有字母皆为小写);若当前为安全状态则输出一个安全序列,进程名之间用空格作为分隔。 +输入样例 +9 5 7 +P1 5 3 4 2 1 3 +P2 9 5 2 2 1 1 +P3 3 2 2 2 2 1 +P4 6 4 1 1 1 1 +输出样例 + +p3 p1 p4 p2 + + + +题目描述 +当采用可变分区分配方案对512KB的内存进行管理时,要求输入多个进程已经占用分区信息,多个进程内存回收信息,输出显示空闲分区的个数。 +输入格式 +程序要求输入3行,以回车符号作为分隔,第一行是一个整数n(10>n>0),表示n个进程已经占用分区;第二行是2n个整数,以空格作为分隔,依次按地址递增对应第一行n +个进程(进程编号依次为p1、p2、p3….pn,p为小写字母)已经占用分区的起始地址和存储容量(单位为K8)。第三行是一个字符串,用进程编号表示所回收的进程,编号之间用空格作为分隔符。 +输出格式 +输出一个整数表示回收后空闲分区的个数,若有回收的进程不存在,则输出“false”(不含双引号,所有字母皆为小写) +输入样例 +2 +0 60 100 220 +p1 +输出样例 + +2 + + + + + + + +题目描述 +分页式存储系统中利用位示图对8*8块的存储器进行管理(块下标从0到63)。若已有块号为1、3、5、7、9、11、13、15、17、19的十个内存块被占用。现有四个进程要申请内存空间(每个进程最多为54个页面,编号为0到53也即申请的内存空间最多为54块)以装入。若系统有足够的空间能满足进程所申请的空间则技照从上到下,从左到右的顺序扫描位示图选择空块将其装入,否则进程在外存等待。 +输入格式 +程序要求输入2行,以回车符号作为分隔,第一行是四个整型数,以空格作为分隔,分别表示四个进程(进程编号依次为1、2、3、4)所申请的内存块数。第二行是2个整 +数,以空格作为分隔。第一个整数N(1<=N<=4)表示所输入的是进程编号为N,第二个是整数M(0<-M<=53),表示进程编号为N的M号页面。 +输出格式 +若进程N装入了内存且页号M合理,则输出为一个整数,表示该页面所装入的内存块号:若进程N装入了内存但页号M有错,则输出字符串"eror”(不含双引号,所有字母皆为小写);若进程N在外存等待,则输出字符串“wait"(不含双引号,所有字母皆为小写) +输入样例 +16 16 16 16 +4 1 +输出样例 +wait + + + +题目描述 + +要求输入一个柱面访问请求序列以及当前磁头所在柱面号和移动方向,输出采用电梯调度算法时移动臂响应的柱面访问序列。 +输入格式 +程序要求输入3行,以回车符号作为分隔,第一行是2个整型数n,m,之间用空格隔开,n表示当前磁头所在的柱面号,m表示第二行输入m个数;第二行是m个整数,数之间以空格作为分隔,表示柱面访问请求序列;第三行是数字-1或1,当为-1时表示移动臂向柱面号减小方向移动,当为1时表示移动暋向桂而号增大方向移动。 +输出格式 +输出m个整数,数之间以空格作为分隔,采用电梯调度算法时移动臂响应的柱面访问序列。 +输入样例 +15 10 +24 38 2 110 43 36 5 11 6 180 +-1 +输出样例 +11 6 5 2 24 36 38 43 110 180 + + + +======================================== + +题目描述 +要求输入4个进程的信息,若进程调度采用短作业优先SJF调度算法(若所需运行时间一样,则按照输入顺序执行),请输出各个进程的周转时间 +输入格式 +程序要求输入4行,以回车符号作为分隔,每行有3个数据,以空格作为分隔。首先输入一个字符串(长度小于等于10),为进程名,第2个数据类型为整型,表示进程到达的时刻,第3个数据类型为整型,表示进程所需运行的时间 +输出格式 +按照输入的顺序依次输出各个进程的周转时间,各个整数之间用一个空格作为分隔。 +输入样例 +P1 0 9 +P2 0 4 +P3 0 10 +P4 0 8 +输出样例 +21 4 31 12 + + + +题目描述 +要求输入4个进程的信息,若进程调度采用最短剩余时间优先SRTF调度算法(若所需运行时间一样,则技照输入顺序执行。),请按照执行过程的顺序输出各个进程的名称 +输入格式 +程序要求输入4行,以回车符号作为分隔,每行有3个数据,以空格作为分观,首先输入一个字符串(长度小于等于10),为进程名,第2个数据类型为整型,表示进程到达的时刻,第3个数据类型为整型,表示进程所需运行的时间, +输出格式 +按照执行过程的顺序输出各个进程的名称即一个字符串,进程名称之间用一个空格作为分隔。 +输入样例 +P1 0 9 +P2 0 4 +P3 0 10 +P4 0 8 +输出样例 +P2 P4 P1 P3 + + + + + +题目描述 +分页式存储系统中利用位示图对8*8块的存储器进行管理(块下标从0到63)。若已有块号为1、3、5、7、9、11、13、15、17、19的十个内存块被占用。现有四个进程要申请内存空间(每个进程最多为54个页面,编号为0到53也即申请的内存空间最多为54块)以装入。若系统有足够的空间能满足进程所申请的空间则技照从上到下,从左到右的顺序扫描位示图选择空块将其装入,否则进程在外存等待。 +输入格式 +程序要求输入2行,以回车符号作为分隔,第一行是四个整型数,以空格作为分隔,分别表示四个进程(进程编号依次为1、2、3、4)所申请的内存块数。第二行是2个整数,以空格作为分隔。第一个整数N(1<=N<=4)表示所输入的是进程编号为N,第二个是整数M(0<=M<=53),表示进程编号为N的M号页面。 +输出格式 +若进程N装入了内存且页号M合理,则输出为一个整数,表示该页面所装入的内存块号:若进程N装入了内存但页号M有错,则输出字符串"eror”(不含双引号,所有字母皆为小写);若进程N在外存等待,则输出字符串“wait"(不含双引号,所有字母皆为小写) +输入样例 +16 16 16 16 +4 1 +输出样例 +wait + + + +管理员功能 + +- 用户 + - 查看用户 ls user + - id 名称 注册时间 上次登录 使用空间 总空间 是否注销 + - 封号 close id + - 恢复 start id + - 彻底删除用户 delete user id + - 用户下的所有文件都删除 + - 增加空间 space add id XG +- 文件 + - 查看所有用户删除的文件 ls file + - fid 用户名称 文件名称 类型 + - 查看服务器所有无用文件 ls sfile + - fid 文件名称 类型 大小 + - 删除所有用户删除文件 delete fileall + - 删除所有无用文件 delete sfile + - 获得所有文件的地址,数据库将记录去除,通知服务器将文件全部清除 + - 恢复用户文件夹 rollback uid + + + +```Java +FROM openjdk:17 +WORKDIR /app +COPY FileServer.jar /app/FileServer.jar +EXPOSE 8972 +CMD ["java", "-jar", "FileServer.jar"] + + +# 在Dockerfile所在目录执行 +docker build -t FileServer:1.0 . + + +curl https://www.baidu.com +``` + + + +回滚数据判断文件夹名 + + + +写一个下载、上传工具 + +- 上传API +- 下载API +- 分片上传 +- 断点续传 + + + + + +前端 + +:发送文件md5,检查分片进度请求(判断上传了几个),返回值为已经上传几个分片 + +:分片上传(上传未上传部分) + +:整合请求 \ No newline at end of file diff --git a/posts/blog/工作/项目/网盘项目/QQ_1733367586038.png b/posts/blog/工作/项目/网盘项目/QQ_1733367586038.png new file mode 100644 index 0000000..178202c Binary files /dev/null and b/posts/blog/工作/项目/网盘项目/QQ_1733367586038.png differ diff --git a/posts/blog/工作/项目/网盘项目/ff3c9978-5098-479a-921b-ba655bced945.png b/posts/blog/工作/项目/网盘项目/ff3c9978-5098-479a-921b-ba655bced945.png new file mode 100644 index 0000000..620f6a0 Binary files /dev/null and b/posts/blog/工作/项目/网盘项目/ff3c9978-5098-479a-921b-ba655bced945.png differ diff --git a/posts/blog/工作/项目/网盘项目/image123.png b/posts/blog/工作/项目/网盘项目/image123.png new file mode 100644 index 0000000..8f94fd9 Binary files /dev/null and b/posts/blog/工作/项目/网盘项目/image123.png differ diff --git a/posts/blog/工作/项目/网盘项目/image215.png b/posts/blog/工作/项目/网盘项目/image215.png new file mode 100644 index 0000000..9be3cc1 Binary files /dev/null and b/posts/blog/工作/项目/网盘项目/image215.png differ diff --git a/posts/blog/工作/项目/网盘项目/image314.png b/posts/blog/工作/项目/网盘项目/image314.png new file mode 100644 index 0000000..bf18dd5 Binary files /dev/null and b/posts/blog/工作/项目/网盘项目/image314.png differ diff --git a/posts/blog/工作/项目/网盘项目/image41.png b/posts/blog/工作/项目/网盘项目/image41.png new file mode 100644 index 0000000..b927b6b Binary files /dev/null and b/posts/blog/工作/项目/网盘项目/image41.png differ diff --git a/posts/blog/工作/项目/网盘项目/image413.png b/posts/blog/工作/项目/网盘项目/image413.png new file mode 100644 index 0000000..0a5fcff Binary files /dev/null and b/posts/blog/工作/项目/网盘项目/image413.png differ diff --git a/posts/blog/工作/项目/网盘项目/image512.png b/posts/blog/工作/项目/网盘项目/image512.png new file mode 100644 index 0000000..3d41493 Binary files /dev/null and b/posts/blog/工作/项目/网盘项目/image512.png differ diff --git a/posts/blog/工作/项目/网盘项目/image610.png b/posts/blog/工作/项目/网盘项目/image610.png new file mode 100644 index 0000000..faa0104 Binary files /dev/null and b/posts/blog/工作/项目/网盘项目/image610.png differ diff --git a/posts/blog/工作/项目/通用权限管理项目.md b/posts/blog/工作/项目/通用权限管理项目.md new file mode 100644 index 0000000..5225f78 --- /dev/null +++ b/posts/blog/工作/项目/通用权限管理项目.md @@ -0,0 +1,106 @@ +--- +title: 通用权限管理项目 +date: 2025-02-21 +categories: [工作, 项目] +tags: [项目] +--- + + +## 前端 + +- 封装菜单、菜单项目,动态路由 +- 封装http请求 +- 封装弹窗 +- 封装提示框 + +数据持久化插件 + +[开始 | Pinia Plugin Persistedstate](https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/) + +动态路由 + +![image 44.png](通用权限管理项目/image44.png) + +动态菜单 + + + + + + + +## 后端 + +角色 + +- 简单的增删改查 + +用户 + +- 简单的增删改查 +- 和角色的一个联查 + +菜单 + +- 简单的增删改查 +- 将菜单进行手动拼接成Tree + +## 简单的增删改查 + +### 增 + +- 将数据库表映射成对象 +- 编写通用增加接口: int add(T t) +- 动态sql + +### 改 + +- 编写通用修改接口: int edit(T t) +- 动态Sql + +### 删 + +- 通过id删除:int delete(int id) + +### 查 + +- 普通查询 +- 分页:pagehelper + + + +对于菜单分配的设计问题 + +角色查看分配菜单 + +方案一(role_id) + +- 返回角色对应的菜单id(表示选中) +- 直接返回==所有菜单==(树结构) +- 优点:简单 +- 缺点:用户可以看到所有权限,并可以进行修改 + +方案二(user_id,role_id) + +- 返回角色对应的菜单id(表示选中) +- 返回==用户对应的菜单====(树结构)== +- 优点:用户只可以看见自己的权限,并且修改也只可以在此基础上 +- 缺点:需要设置是否是为管理员字段(保证至少一个显示完整菜单) + +## 后端进阶 + +- 验证码图片生成 + + + + + +持久化数据 + +- 持久化token、持久化userId、userName + + + +### SpringSecurity + +基于一种维持会话的方式(session、jwt)进行防止直接访问后端请求 \ No newline at end of file diff --git a/posts/blog/工作/项目/通用权限管理项目/image44.png b/posts/blog/工作/项目/通用权限管理项目/image44.png new file mode 100644 index 0000000..e48b163 Binary files /dev/null and b/posts/blog/工作/项目/通用权限管理项目/image44.png differ diff --git a/posts/blog/效率与工具/博客/hexo/hexo与md完美结合.md b/posts/blog/效率与工具/博客/hexo/hexo与md完美结合.md new file mode 100644 index 0000000..7c7af39 --- /dev/null +++ b/posts/blog/效率与工具/博客/hexo/hexo与md完美结合.md @@ -0,0 +1,58 @@ +--- +title: hexo与md完美结合 +date: 2025-09-15 04:57:10 +tags: [hexo] +--- + +## 前言 + +想搭建一个博客,看了很多如halo、hexo、hugo、mkdocs、Jekyll等多种博客;都不满足我的需求 + +**我的需求如下** + +- 完美支持md,目前主流语法 +- 占用内存下,小服务器能跑 +- 颜值不要太难看 + +## 搭建hexo + +### 安装nodejs + +自行安装 + +设置url按标题生成 + +在 `hexo/_config.yml` 里加: + +```yaml +permalink: :title.html +``` + +**为了较为完美的实现md语法**,自定义脚本实现链接 + +脚本存储位置:blog\scripts\md-link-fix.js + +```js +hexo.extend.filter.register('before_post_render', function (data) { + if (!data.content) return data; + // 处理 md 链接 + data.content = data.content.replace( + /\[([^\]]+)\]\(([^)]+)\.md\)/g, + function (match, text, path) { + return `[${text}](${path}.html)`; + } + ); + + // 处理图片 (匹配 ![alt](文件夹/文件.png) → 不动,只确保不拼 .html) + data.content = data.content.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, + (match, alt, path) => { + return `![${alt}](${path})`; + }); + +// console.log("过滤器已运行:", data.source); + + return data; +}); +``` + +目前图片链接处理可能还是会导致异常,问题在于会修稿所有链接,所以可能会误伤其他链接 diff --git a/posts/blog/效率与工具/博客/hexo/hexo测试.md b/posts/blog/效率与工具/博客/hexo/hexo测试.md new file mode 100644 index 0000000..b072dd7 --- /dev/null +++ b/posts/blog/效率与工具/博客/hexo/hexo测试.md @@ -0,0 +1,12 @@ +--- +title: hexo测试 +date: 2025-10-11 19:57:10 +tags: [hexo] +--- + +![image-20251011202431088](hexo测试/image-20251011202431088.png) + +[链接测试](./hexo部署.md) + +[链接测试](../../../编程技术/java/JavaSE/IO流/装饰器设计模式(Decorator Pattern).md) + diff --git a/posts/blog/效率与工具/博客/hexo/hexo测试/image-20251011202431088.png b/posts/blog/效率与工具/博客/hexo/hexo测试/image-20251011202431088.png new file mode 100644 index 0000000..f11da8c Binary files /dev/null and b/posts/blog/效率与工具/博客/hexo/hexo测试/image-20251011202431088.png differ diff --git a/posts/blog/效率与工具/博客/hexo/hexo简化操作.md b/posts/blog/效率与工具/博客/hexo/hexo简化操作.md new file mode 100644 index 0000000..d09b992 --- /dev/null +++ b/posts/blog/效率与工具/博客/hexo/hexo简化操作.md @@ -0,0 +1,29 @@ +--- +title: hexo简化操作 +date: 2025-09-15 04:53:57 +tags: [hexo] +--- + +### 简化运行操作 + +**1、一条命令运行 `hexo clean、hexo g、hexo s`** +在 `package.json` 里加一个脚本命令: + +```json +{ + "scripts": { + "dev": "hexo clean && hexo g && hexo s" + } +} +``` + +以后直接执行: + +``` +npm run dev +``` + +就等同于 `hexo clean && hexo g && hexo s`。 +(或者在 `bash/zsh` 下自己写 alias:`alias hcs="hexo clean && hexo g && hexo s"`) + +### 自动生成 Front-matter diff --git a/posts/blog/效率与工具/博客/hexo/hexo设置脚本自动创建元数据.md b/posts/blog/效率与工具/博客/hexo/hexo设置脚本自动创建元数据.md new file mode 100644 index 0000000..08b90fe --- /dev/null +++ b/posts/blog/效率与工具/博客/hexo/hexo设置脚本自动创建元数据.md @@ -0,0 +1,26 @@ +--- +title: hexo设置脚本自动创建元数据 +date: 2025-09-15 04:50:51 +tags: [hexo] +--- +# hexo设置脚本自动创建元数据 + +## 步骤如下 + +### 编写创建脚本 + +查看操作系统编码格式,中文操作系统,编码为GBK + +### 写入注册表 + +注册表编码格式为UTF-16 LE with BOM + +查看图标位置 + +``` +计算机\HKEY_CLASSES_ROOT\Typora.Typora 1.3.6中文直装版.md\DefaultIcon +``` + +## 展示效果 + +右键鼠标 —> 选中“创建hexo文章” —> 弹出弹窗,输入名称 diff --git a/posts/blog/效率与工具/博客/hexo/hexo部署.md b/posts/blog/效率与工具/博客/hexo/hexo部署.md new file mode 100644 index 0000000..c8fcad9 --- /dev/null +++ b/posts/blog/效率与工具/博客/hexo/hexo部署.md @@ -0,0 +1,41 @@ +--- +title: hexo部署 +date: 2025-09-14 +tags: [hexo] +--- + + +**1️⃣ Hexo Admin 在服务器上直接公网编写文章** +- 可行,但要注意安全: + - Hexo Admin 本质是一个本地后台,默认是运行在 Node.js 服务器上的。 + - 如果你把它部署在公网服务器: + - 你可以直接在浏览器访问 http://你的服务器IP:端口/admin,可视化编写文章。 + - 写完文章后点击发布,它会生成静态文件(public/)。 + - 你只需要在服务器上配置好 Nginx/Apache 指向 public/ 就能上线。 +- 安全注意事项: + - 默认 Hexo Admin 用户名/密码可能简单,需要设置强密码。 + - 最好用 反向代理 + HTTPS + IP 限制 或 VPN 保护后台。 + - 如果直接暴露在公网,存在被攻击风险。 + +✅ 总结:Hexo Admin 可以在服务器上实现公网编辑和直接生成,但需要严格安全配置。 + +**2️⃣ 利用 GitHub 平台,个人仓库 + 自动部署** +- 完全可以: + - GitHub 上可以创建个人仓库存放 Hexo 博客源文件(source/_posts 等)。 + - 编写文章后: + - 本地 hexo g 生成静态文件 + - hexo d 可以配置自动推送到 GitHub Pages(仓库的 gh-pages 分支) + 或者通过 CI/CD 自动把 public/ 部署到你自己的服务器。 + + - 好处: + - GitHub 保存一份 Markdown 原始内容(版本控制) + - 同时可以自动上线到你的网站(GitHub Pages 或 VPS) +- 流程示例: + - 本地写文章 → 保存到仓库 + - GitHub 自动触发 Actions: + - 生成 Hexo 静态文件 + - 推送到部署服务器 / GitHub Pages + + - 网站更新完成,访问即见效果 + +✅ 总结:GitHub 仓库可以个人使用,同时实现“保存一份 + 自动上线”。 \ No newline at end of file diff --git a/posts/blog/效率与工具/工具/IDE/开发IDE.md b/posts/blog/效率与工具/工具/IDE/开发IDE.md new file mode 100644 index 0000000..53d022e --- /dev/null +++ b/posts/blog/效率与工具/工具/IDE/开发IDE.md @@ -0,0 +1,24 @@ +--- +title: 开发IDE +date: 2025-06-13 +tags: [IDE] +--- + + +## IDEA + +### JRebel + +启动报错 + +![image 10.png](开发IDE/image10.png) + +解决方案 + +![image 1 7.png](开发IDE/image17.png) + +出处:[https://stackoverflow.com/questions/54191079/jrebel-jvmti-error-when-running-spring-server](https://stackoverflow.com/questions/54191079/jrebel-jvmti-error-when-running-spring-server) + + + +## VScode \ No newline at end of file diff --git a/posts/blog/效率与工具/工具/IDE/开发IDE/image10.png b/posts/blog/效率与工具/工具/IDE/开发IDE/image10.png new file mode 100644 index 0000000..26222fb Binary files /dev/null and b/posts/blog/效率与工具/工具/IDE/开发IDE/image10.png differ diff --git a/posts/blog/效率与工具/工具/IDE/开发IDE/image17.png b/posts/blog/效率与工具/工具/IDE/开发IDE/image17.png new file mode 100644 index 0000000..61d13d5 Binary files /dev/null and b/posts/blog/效率与工具/工具/IDE/开发IDE/image17.png differ diff --git a/posts/blog/效率与工具/工具/git/git基本命令.md b/posts/blog/效率与工具/工具/git/git基本命令.md new file mode 100644 index 0000000..d0a9c95 --- /dev/null +++ b/posts/blog/效率与工具/工具/git/git基本命令.md @@ -0,0 +1,176 @@ +--- +title: git基本命令 +date: 2024-12-10 +tags: [git] +--- + + +## 基本操作 + +1、初始化:`git init` + +2、添加(全部文件到)暂存区:`git add .` + +- 相当于交给git管理文件 + +撤销暂存区:`git reset` + +3、添加到本地仓库:`git commit -m 'msg'` + +4、添加到远程仓库:`git push -u origin "master"` + +- 前提是添加了远程仓库,如下 + + `git remote add origin` `[https://gitee.com/QGS_Code/file-system.git](https://gitee.com/QGS_Code/file-system.git)` + + + + +取消git管理 + +- 删除.git + +更换远程仓库 + +- git remote -v +- git remote set-url origin [https://github.com/yourusername/new-repository.git](https://github.com/yourusername/new-repository.git) + + + + + +### 可以设置 `git status` 忽略未关心目录里的改动 + +比如前端不关心 backend 改动,可以临时忽略: + +```Shell +git update-index --assume-unchanged backend/* +``` + +恢复: + +```Shell +git update-index --no-assume-unchanged backend/* +``` + +反过来一样。 + +--- + + + +git版本控制 + +本地记录 + +- master分支:A — B — C +- 其他分支:A — B — C — D — E + +远程记录 + +- 远程master:A— B — C +- 其他远程分支:A — B — C — D — E + +### git fetch + +这会更新本地的**远程跟踪分支** + +### git **merge** master + +把master的内容**合并**到当前分支,生成一个新版本,一般都是将其他分支合(新功能)并到主分支 + +- 图解 + + 👉 `M` 是 merge commit,表示 `comment_dev` 把 `master` 的 C 合进来了。 + + ```Java + A---B---C (master) + \ + D---E (comment_dev) + + A---B---C (master) + \ \ + D---E---M (comment_dev) + 更新内容 + A---B---C (master) + \ + D---E (comment_dev) + + A---B---C---M (master) + \ \ + D---E (comment_dev) + ``` + + +### git pull + +=**git fetch + git merge** + + + +### git **rebase** master + +基于最新版本开发(可能要解决冲突),**变基,**合并代码的另一种方式 + +- 将 `**comment_dev**` 的提交 `**D**` 和 `**E**` 暂存起来。 +- 将 `**comment_dev**` 的指针移动到 `**master**` 的最新提交 `**G**`。 +- 尝试将 `**D**` 和 `**E**` 重新应用到 `**G**` 之上。 + +```Java +A---B---C (master) + \ + D---E (comment_dev) + +A---B---C (master) + \ + D---E (comment_dev) +``` + +## 📌 注意 ⚠️ + +- **rebase 之后必须强推(push -f)** + + 因为提交历史改了,普通 push 会被拒绝。 + + +```Shell +git push -f origin feature-branch +``` + +- 不要对**公共分支**(比如 dev、master)执行 rebase,会影响其他人。 + + + +### 本地 `master` 回退到你想要的版本 + +比如: + +```Shell +git checkout master +git reset --hard 4e5f6g7 # 你要回去的那个 commit +``` + +--- + +### 📊 2️⃣ 强制推送覆盖远程 master + +```Shell +git push -f origin master +``` + +查看提交记录(q 退出) + +```Shell +git log --oneline --graph --decorate +``` + + + +## 📦 总结: + +| | | | | +|---|---|---|---| +|操作|远程新提交|本地 `A`|本地 `origin/A`| +|`fetch`|获取到了|不变|更新到最新| +|`merge origin/A`|会合并|和 `origin/A` 合并|-| +|`pull`(默认 `fetch+merge`)|获取并合并|会更新|同步| \ No newline at end of file diff --git a/posts/blog/效率与工具/计算机使用/常用软件.md b/posts/blog/效率与工具/计算机使用/常用软件.md new file mode 100644 index 0000000..634834e --- /dev/null +++ b/posts/blog/效率与工具/计算机使用/常用软件.md @@ -0,0 +1,40 @@ +--- +title: 常用软件 +date: 2025-08-17 +tags: [计算机使用] +--- + + +## 虚拟机 + +VMware Workstation Pro + + + +## 文档编辑器 + +typora 处理md + +sublime text 任何格式编辑器 + + + +## IDE + +idea + +> [!info] JETBRA.IN CHECKER | IPFS +> +> [https://3.jetbra.in/](https://3.jetbra.in/) + +查看readme + +[https://www.jetbrains.com/idea/download/](https://www.jetbrains.com/idea/download/) + +下载历史版本 + +## 图形化界面 + +navicat (mysql) + +resp (redis) \ No newline at end of file diff --git a/posts/blog/效率与工具/计算机使用/操作系统重装.md b/posts/blog/效率与工具/计算机使用/操作系统重装.md new file mode 100644 index 0000000..f62f281 --- /dev/null +++ b/posts/blog/效率与工具/计算机使用/操作系统重装.md @@ -0,0 +1,140 @@ +--- +title: 操作系统重装 +date: 2024-11-06 +tags: [计算机使用] +--- + + +## 必要软件 + +- Xshell +- Finamshell +- telegram +- clash +- qq +- StarUML +- ioDraw +- typora +- notion +- navicat +- visual studio code +- chrome +- edge +- idea +- wireshark +- apifox +- resp +- onenote + + + + + +### 制作启动盘 + +一个16G硬盘,下载下面内容制作成启动pan + +[下载 Windows 10](https://www.microsoft.com/zh-cn/software-download/windows10?msockid=3c106fe98cf1616a01e17ac78dd16015) + +电脑启动,进入BIOS,并选择U盘启动,使用**UEFI 启动模式** + +磁盘分区需要时GPT,如果是**MBR 分区表,进行格式化转格式** + +📌 方法①:把硬盘从 MBR 转成 GPT ✅(推荐)📌 操作: + +1. 在 Win10 安装界面,按下 **Shift + F10** 打开命令行 +2. 输入 `diskpart` → 回车 +3. 输入 `list disk` → 回车 +4. 找到你的系统盘编号(比如 `Disk 0`) +5. 输入: + + ```Shell + select disk 0 + clean + convert gpt + exit + ``` + +6. 关闭命令行窗口 +7. 回到安装界面,点击【刷新】或新建分区继续安装 + +✔️ 这样就是 GPT 分区表,UEFI 就能正常装了 + +⚠️ 注意:`clean` 会清空整块硬盘所有分区,数据务必提前备份 + + + + + +### 激活系统 + + + + + +### 激活office + + + + + +## 绑定类型 + +📌 📖 Windows 和 Office 激活方式是否**绑定机器** or **绑定微软账号**,取决于激活类型 + +--- + +### 📦 📌 一、Windows 激活绑定规则 + +|激活类型|绑定对象|说明| +|---|---|---| +|**数字许可证 (数字权利)**|📌 绑定**硬件设备 ID** 和(可选)微软账号|硬件 ID 改变激活失效,但绑定微软账号后可通过账户迁移| +|**OEM 激活**|📌 绑定**主板 BIOS**|出厂预装的 Windows,换主板必定失效| +|**KMS 批量授权**|📌 不绑定账号,只绑定**KMS服务器和机器**|180天续期,靠局域网或公网KMS服务器激活| +|**MAK 批量激活**|📌 绑定**机器硬件ID**|激活码次数有限,换硬件需重新激活| + +### 📌 查看自己是否有数字许可证绑定账号 + +设置 → 更新和安全 → 激活 + +- 如果显示【使用数字许可证与您的 Microsoft 帐户关联】,说明是账号+设备双绑定 + +--- + +### 📦 📌 二、Office 激活绑定规则 + +|激活类型|绑定对象|说明| +|---|---|---| +|**Office 365 / Microsoft 365**|📌 必须绑定**微软账号**|在线激活,账号随设备登录即可,重装设备用账号激活| +|**零售版 (Retail)**|📌 可选绑定**微软账号**,一般绑定机器|通常建议绑定,便于重装迁移。激活次数有限制| +|**批量授权版 (VL/KMS)**|📌 不绑定账号,只绑定机器激活状态|180天 KMS 激活,靠服务器续期,不关联账号| +|**MAK 批量激活**|📌 绑定机器硬件ID|激活次数有限,换硬件需重新激活| + +### 📌 查看 Office 是否绑定账号 + +打开 Word / Excel → 【账户】 + +- 如果右上角头像显示微软账号,说明已绑定 +- 如果显示“未登录”,就是本地激活型(如KMS) + +--- + +### 📌 📣 总结一句 + +|项目|绑定账号|绑定机器| +|---|---|---| +|Windows 数字许可证|✅|✅(硬件ID)| +|Windows KMS|❌|✅| +|Office 365|✅|❌| +|Office 零售版|可选|通常绑定| +|Office KMS|❌|✅| + +--- + +### 📦 📌 最后提醒 + +👉 **数字许可证**:绑定硬件ID + 微软账号,重装系统后只要硬件不变,联网+账号就能自动激活 + +👉 **Office 365**:完全依赖微软账号,设备随便换 + +👉 **KMS 批量版**:跟账号无关,靠局域网或公网 KMS 服务器续期 \ No newline at end of file diff --git a/posts/blog/生活记录/Read.md b/posts/blog/生活记录/Read.md new file mode 100644 index 0000000..79c671e --- /dev/null +++ b/posts/blog/生活记录/Read.md @@ -0,0 +1,33 @@ +--- +title: Read +date: 2024-08-25 +tags: [生活记录] +--- + + +## 书签 + +- [ ] [[multithread]] + + [[multithread]] + +- [ ] [https://javaguide.cn/javaguide/contribution-guideline.html](https://javaguide.cn/javaguide/contribution-guideline.html) + + > [!info] 贡献指南 + > 欢迎参与 JavaGuide 的维护工作,这是一件非常有意义的事情。详细信息请看:JavaGuide 贡献指南 。 你可以从下面几个方向来做贡献: 修改错别字,毕竟内容基本都是手敲,难免会有笔误。 对原有内容进行修改完善,例如对某个面试问题的答案进行完善、对某篇文章的内容进行完善。 新增内容,例如新增面试常问的问题、添加重要知识点的详解。 目前的贡献奖. + > [https://javaguide.cn/javaguide/contribution-guideline.html](https://javaguide.cn/javaguide/contribution-guideline.html) + +- [ ] [https://crossoverjie.top/JCSprout/#/collections/HashSet](https://crossoverjie.top/JCSprout/#/collections/HashSet) + + > [!info] JCSprout + > Description + > [https://crossoverjie.top/JCSprout/#/collections/HashSet](https://crossoverjie.top/JCSprout/#/collections/HashSet) + +- [ ] [https://developers.pub/wiki/1002310/1016256](https://developers.pub/wiki/1002310/1016256) + + > [!info] 开发者客栈-帮助开发者面试的平台-顽强网络 + > 一个帮助开发者面试的平台。开发者客栈集成各类面试经验分享和面试题专业解答、免费简历生成和下载、一线互联网公司内推等多功能多元化平台,致力于构建一个专业度高、知识共享、内容丰富的有爱社区。 + > [https://developers.pub/wiki/1002310/1016256](https://developers.pub/wiki/1002310/1016256) + + +## 已读 \ No newline at end of file diff --git a/posts/blog/生活记录/失地证/失地证信息.md b/posts/blog/生活记录/失地证/失地证信息.md new file mode 100644 index 0000000..4dad3a7 --- /dev/null +++ b/posts/blog/生活记录/失地证/失地证信息.md @@ -0,0 +1,47 @@ +--- +title: 失地证信息 +date: 2024-09-10 +tags: [失地证] +--- + + +- 打印申请表 + - 填表需知:此表一式一份。 + + 1、申请需如实填写以上信息; + + 2、学历栏在对应栏内打“√”; + + 3、学校证明栏需加盖学校公章; + + 4、字迹清楚,不得涂改。 + +- 提供材料 + - [x] 学生**社保卡**账号复印件; + - [x] 学生**身份证**复印件; + - [x] 学费**缴款**凭证原件。 + - [x] 录取通知书复印件或学籍证明(附件1、附件2); + - [x] 失地农民证复印件; +- 数据 + - 失地证号:**赣蓉K21010021** +- 参考图片 + + ![封面.png](失地证信息/封面.png) + + 封面 + + ![公章.png](失地证信息/公章.png) + + 公章 + + ![信息.png](失地证信息/信息.png) + + 信息 + + ![参考.png](失地证信息/参考.png) + + 参考 + + ![参考1.png](失地证信息/参考1.png) + + 参考 \ No newline at end of file diff --git a/posts/blog/生活记录/失地证/失地证信息/信息.png b/posts/blog/生活记录/失地证/失地证信息/信息.png new file mode 100644 index 0000000..cf5df88 Binary files /dev/null and b/posts/blog/生活记录/失地证/失地证信息/信息.png differ diff --git a/posts/blog/生活记录/失地证/失地证信息/公章.png b/posts/blog/生活记录/失地证/失地证信息/公章.png new file mode 100644 index 0000000..e74c105 Binary files /dev/null and b/posts/blog/生活记录/失地证/失地证信息/公章.png differ diff --git a/posts/blog/生活记录/失地证/失地证信息/参考.png b/posts/blog/生活记录/失地证/失地证信息/参考.png new file mode 100644 index 0000000..a075df5 Binary files /dev/null and b/posts/blog/生活记录/失地证/失地证信息/参考.png differ diff --git a/posts/blog/生活记录/失地证/失地证信息/参考1.png b/posts/blog/生活记录/失地证/失地证信息/参考1.png new file mode 100644 index 0000000..241a6f9 Binary files /dev/null and b/posts/blog/生活记录/失地证/失地证信息/参考1.png differ diff --git a/posts/blog/生活记录/失地证/失地证信息/封面.png b/posts/blog/生活记录/失地证/失地证信息/封面.png new file mode 100644 index 0000000..2b26d67 Binary files /dev/null and b/posts/blog/生活记录/失地证/失地证信息/封面.png differ diff --git a/posts/blog/生活记录/学校/加快发展新质生产力的路径与实践探讨.md b/posts/blog/生活记录/学校/加快发展新质生产力的路径与实践探讨.md new file mode 100644 index 0000000..7687d73 --- /dev/null +++ b/posts/blog/生活记录/学校/加快发展新质生产力的路径与实践探讨.md @@ -0,0 +1,100 @@ +--- +title: 加快发展新质生产力的路径与实践探讨 +date: 2024-9-15 +tags: [学校] +--- + + +摘要 + +新质生产力是推动我国经济高质量发展的核心动力,也是全面建设社会主义现代化强国的重要支撑。本文围绕加快发展新质生产力的主题,探讨了新质生产力的内涵及其对经济发展的重要意义,分析了当前制约其发展的主要问题,并从政策支持、科技创新、产业结构优化和人才培养等角度提出了具体路径与实践策略。通过对理论与实践的结合分析,本文旨在为促进我国新质生产力的全面发展提供借鉴和参考。 + +新质生产力;高质量发展;科技创新;产业升级;现代化 + +正文 + +引言 + +关键词 + +在当前全球化加速、科技革命不断迭代的背景下,新质生产力已成为全球经济竞争的重要领域。作为经济增长的新动能,新质生产力不仅包括技术进步和生产方式的创新,还涉及到经济结构转型、资源配置效率提升以及生态友好型发展的综合体系。如何加快发展新质生产力,已成为我国实现经济高质量发展和迈向现代化的必由之路。 + +一、新质生产力的内涵与意义 + +1. 新质生产力的内涵 + +新质生产力相较于传统生产力更强调以下几个方面: + +- 科技驱动:以人工智能、大数据、区块链等新兴技术为核心驱动力。 +- 绿色发展:注重资源节约和生态保护,实现可持续发展。 +- 价值链提升:推动从低端加工制造向高端智能制造转型,提升产品和服务的附加值。 + +1. 发展新质生产力的意义 + +- 推动经济转型升级:新质生产力能够突破传统产业的瓶颈,为经济注入新活力。 +- 提升国际竞争力:增强我国在全球产业链、供应链中的核心地位。 +- 实现可持续发展目标:通过技术创新与绿色发展路径,促进人与自然的和谐共生。 + +二、当前制约新质生产力发展的主要问题 + +尽管我国在新质生产力发展方面取得了显著进步,但仍面临一些制约因素: + +1. 科技创新能力不足 + +尽管我国在高铁、5G等领域处于世界领先地位,但在核心技术研发和原创性科技成果方面仍有待突破。 + +1. 产业结构优化不足 + +我国产业链存在低端过剩、高端不足的问题,传统产业转型升级相对缓慢。 + +1. 人才供给与培养不平衡 + +高端创新型人才缺乏,技术工人队伍建设有待加强,特别是在新兴领域的人才储备尚显不足。 + +1. 政策支持与市场环境不完善 + +在政策设计和市场环境上,部分地区和行业还存在创新投入不足、制度创新滞后的问题。 + +三、加快发展新质生产力的路径与实践 + +1. 强化科技创新驱动 + +- 加大科研投入:提升国家对基础研究和关键技术研发的资金投入比例,鼓励企业加大研发力度。 +- 构建创新平台:搭建产学研一体化的创新平台,促进技术研发与产业应用的深度融合。 +- 完善知识产权保护:通过法律和政策手段保障创新主体的合法权益,激发创新活力。 + +1. 推动产业结构优化升级 + +- 大力发展高端制造业:聚焦航空航天、新能源汽车、集成电路等领域,打造全球领先的产业集群。 +- 培育战略性新兴产业:发展新一代信息技术、绿色环保、新材料等新兴产业,提升经济增长韧性。 +- 推动传统产业智能化改造:通过数字化、智能化手段提高传统产业的效率和竞争力。 + +1. 加强人才培养与引进 + +- 完善人才培养体系:加大对工科教育和职业技术教育的支持力度,打造一支技术过硬、创新能力强的专业人才队伍。 +- 优化人才引进政策:通过全球招聘和高端人才引进计划吸引优秀海外人才,为新质生产力提供智力支持。 +- 营造创新创业环境:完善配套政策和服务体系,激发高校、科研机构和企业的创新潜力。 + +1. 优化政策支持与市场环境 + +- 加强政策协调性:制定与新质生产力发展相适应的政策体系,涵盖金融支持、税收优惠和产业引导等方面。 +- 构建良好市场环境:破除垄断和行业壁垒,鼓励公平竞争,激发中小企业的创新活力。 +- 强化区域协同发展:推动东部发达地区与中西部地区的联动发展,共同打造区域性新质生产力增长极。 + +四、案例分析与实践经验 + +1. 国内经验:深圳创新经济的启示 + +深圳作为我国科技创新的标杆城市,通过营造良好的政策环境和市场机制,吸引了大量高科技企业和人才集聚,成功构建了涵盖基础研究、技术开发和产业应用的创新生态体系。 + +1. 国际经验:德国工业4.0的借鉴 + +德国通过实施“工业4.0”战略,全面推动制造业数字化、智能化转型,为我国传统产业升级提供了有益的参考经验。 + +五、结论与展望 + +加快发展新质生产力是我国实现经济高质量发展和现代化建设的重要途径。在未来的实践中,我们需要持续深化科技创新、优化产业结构、完善政策支持,并加强国际合作,积极吸取全球先进经验。通过全社会的共同努力,我国必将实现从传统经济大国向创新经济强国的转型,为实现中华民族伟大复兴的中国梦提供坚实支撑。 + +参考文献 + + \ No newline at end of file diff --git a/posts/blog/生活记录/学校/发言稿.md b/posts/blog/生活记录/学校/发言稿.md new file mode 100644 index 0000000..57e74ca --- /dev/null +++ b/posts/blog/生活记录/学校/发言稿.md @@ -0,0 +1,24 @@ +--- +title: 发言稿 +date: 2024-9-15 +tags: [学校] +--- + + +同学们好!我叫邱国帅,专科就读于文化学院 计算机应用技术专业,专升本英语成绩为85分,专业课105分,最终上岸湖北师范大学,计算机科学与技术专业, + +相信大部分人选择专升本的目的就是提升学历,我和大家也一样,我是从去年9月开始备考,当时就以湖北师范大学为目标,虽然有一定考取难度,但我专业课有一定的基础,加上我相信新东方对我的英语的有一定保障,所以便坚定的走下去 + +关于英语的学习,我认为词汇是重中之重,这个只有靠自己没有什么捷径可言,个人推荐使用不背单词app,我在上面累计背诵时长1w6k多分钟,语法方面老师就显的就比较关键,不同老师讲的语法效果真的很不一样,像郭璁老师语法讲的就通俗易懂、详略得当,特别感谢助教吴雨桐老师,每次遇到语法问题都会为我耐心解答,还有翻译夏斌老师、写作刘爽老师、阅读韩勤勤老师等宝藏老师,都对我的英语有很大的帮助 + +最后,希望学弟学妹们,可以坚持走下去,相信我,每天坚持学习,你就已经战胜了90%的对手,加油! + + + +同学们好!我叫邱国帅,专科就读于文化学院 计算机应用技术专业,专升本英语成绩为85分,专业课105分,最终上岸湖北师范大学,计算机科学与技术专业, + +相信大多数同学选择专升本的目的都是为了提升学历,我也不例外。从去年9月开始,我便以湖北师范大学为目标,尽管考取有一定难度,但我对自己的专业基础有信心,同时也相信新东方的英语课程会为我的英语学习保驾护航。所以便坚定的走下去 + +对于这次专升本的英语成绩,虽然分数不是特别高,但达到了我的预期,因此我还是感到比较满意的。关于英语学习,我认为词汇是最重要的基础,这部分没有捷径,完全靠自己的积累。我个人推荐使用“不背单词”APP,我在这款软件上累计背诵时长超过16000分钟。至于语法学习,老师的讲解就显得至关重要了,不同的老师教学效果确实有差异。比如新东方郭璁老师的语法课,讲解得通俗易懂、重点突出,非常感谢他的帮助。同时,我也特别想感谢助教吴雨桐老师,她每次都耐心解答我遇到的语法问题。还有翻译课的夏斌老师、写作课的刘爽老师、阅读课的韩勤勤老师等,他们都是帮助我提升英语水平的宝藏老师。 + +最后,我想对学弟学妹们说,坚持就是胜利。每天保持学习的习惯,你已经战胜了90%的对手。相信自己,加油! \ No newline at end of file diff --git a/posts/blog/生活记录/学校/论文.md b/posts/blog/生活记录/学校/论文.md new file mode 100644 index 0000000..857850d --- /dev/null +++ b/posts/blog/生活记录/学校/论文.md @@ -0,0 +1,103 @@ +--- +title: 论文 +date: 2024-9-15 +categories: [生活记录, 学校] +tags: [学校] +--- + + + + +论文题目:加快发展新质生产力的路径与实践探讨 + +摘要 + +新质生产力是推动我国经济高质量发展的核心动力,也是全面建设社会主义现代化强国的重要支撑。本文围绕加快发展新质生产力的主题,探讨了新质生产力的内涵及其对经济发展的重要意义,分析了当前制约其发展的主要问题,并从政策支持、科技创新、产业结构优化和人才培养等角度提出了具体路径与实践策略。通过对理论与实践的结合分析,本文旨在为促进我国新质生产力的全面发展提供借鉴和参考。 + +关键词 + +新质生产力;高质量发展;科技创新;产业升级;现代化 + +正文 + +引言 + +在当前全球化加速、科技革命不断迭代的背景下,新质生产力已成为全球经济竞争的重要领域。作为经济增长的新动能,新质生产力不仅包括技术进步和生产方式的创新,还涉及到经济结构转型、资源配置效率提升以及生态友好型发展的综合体系。如何加快发展新质生产力,已成为我国实现经济高质量发展和迈向现代化的必由之路。 + +一、新质生产力的内涵与意义 + +1. 新质生产力的内涵 + +新质生产力相较于传统生产力更强调以下几个方面: + +- 科技驱动:以人工智能、大数据、区块链等新兴技术为核心驱动力。 +- 绿色发展:注重资源节约和生态保护,实现可持续发展。 +- 价值链提升:推动从低端加工制造向高端智能制造转型,提升产品和服务的附加值。 + +1. 发展新质生产力的意义 + +- 推动经济转型升级:新质生产力能够突破传统产业的瓶颈,为经济注入新活力。 +- 提升国际竞争力:增强我国在全球产业链、供应链中的核心地位。 +- 实现可持续发展目标:通过技术创新与绿色发展路径,促进人与自然的和谐共生。 + +二、当前制约新质生产力发展的主要问题 + +尽管我国在新质生产力发展方面取得了显著进步,但仍面临一些制约因素: + +1. 科技创新能力不足 + +尽管我国在高铁、5G等领域处于世界领先地位,但在核心技术研发和原创性科技成果方面仍有待突破。 + +1. 产业结构优化不足 + +我国产业链存在低端过剩、高端不足的问题,传统产业转型升级相对缓慢。 + +1. 人才供给与培养不平衡 + +高端创新型人才缺乏,技术工人队伍建设有待加强,特别是在新兴领域的人才储备尚显不足。 + +1. 政策支持与市场环境不完善 + +在政策设计和市场环境上,部分地区和行业还存在创新投入不足、制度创新滞后的问题。 + +三、加快发展新质生产力的路径与实践 + +1. 强化科技创新驱动 + +- 加大科研投入:提升国家对基础研究和关键技术研发的资金投入比例,鼓励企业加大研发力度。 +- 构建创新平台:搭建产学研一体化的创新平台,促进技术研发与产业应用的深度融合。 +- 完善知识产权保护:通过法律和政策手段保障创新主体的合法权益,激发创新活力。 + +1. 推动产业结构优化升级 + +- 大力发展高端制造业:聚焦航空航天、新能源汽车、集成电路等领域,打造全球领先的产业集群。 +- 培育战略性新兴产业:发展新一代信息技术、绿色环保、新材料等新兴产业,提升经济增长韧性。 +- 推动传统产业智能化改造:通过数字化、智能化手段提高传统产业的效率和竞争力。 + +1. 加强人才培养与引进 + +- 完善人才培养体系:加大对工科教育和职业技术教育的支持力度,打造一支技术过硬、创新能力强的专业人才队伍。 +- 优化人才引进政策:通过全球招聘和高端人才引进计划吸引优秀海外人才,为新质生产力提供智力支持。 +- 营造创新创业环境:完善配套政策和服务体系,激发高校、科研机构和企业的创新潜力。 + +1. 优化政策支持与市场环境 + +- 加强政策协调性:制定与新质生产力发展相适应的政策体系,涵盖金融支持、税收优惠和产业引导等方面。 +- 构建良好市场环境:破除垄断和行业壁垒,鼓励公平竞争,激发中小企业的创新活力。 +- 强化区域协同发展:推动东部发达地区与中西部地区的联动发展,共同打造区域性新质生产力增长极。 + +四、案例分析与实践经验 + +1. 国内经验:深圳创新经济的启示 + +深圳作为我国科技创新的标杆城市,通过营造良好的政策环境和市场机制,吸引了大量高科技企业和人才集聚,成功构建了涵盖基础研究、技术开发和产业应用的创新生态体系。 + +1. 国际经验:德国工业4.0的借鉴 + +德国通过实施“工业4.0”战略,全面推动制造业数字化、智能化转型,为我国传统产业升级提供了有益的参考经验。 + +五、结论与展望 + +加快发展新质生产力是我国实现经济高质量发展和现代化建设的重要途径。在未来的实践中,我们需要持续深化科技创新、优化产业结构、完善政策支持,并加强国际合作,积极吸取全球先进经验。通过全社会的共同努力,我国必将实现从传统经济大国向创新经济强国的转型,为实现中华民族伟大复兴的中国梦提供坚实支撑。 + +参考文献 \ No newline at end of file diff --git a/posts/blog/生活记录/摄影.md b/posts/blog/生活记录/摄影.md new file mode 100644 index 0000000..40f5da3 --- /dev/null +++ b/posts/blog/生活记录/摄影.md @@ -0,0 +1,14 @@ +--- +title: 摄影 +date: 2024-10-27 +tags: [生活记录] +--- + + +### 配置1 + +- 4k/60帧 +- auto +- 0.0-0.3EV、ISO:100-1600 +- 8 +- -2、-1 \ No newline at end of file diff --git a/posts/blog/生活记录/面试.md b/posts/blog/生活记录/面试.md new file mode 100644 index 0000000..87dd3ca --- /dev/null +++ b/posts/blog/生活记录/面试.md @@ -0,0 +1,22 @@ +--- +title: 面试 +date: 2025-02-20 +tags: [生活记录] +--- + + +javaWeb + +[https://www.bilibili.com/video/BV1CL4y1i7qR?vd_source=7755fe49534ae933cbf5d23064211159&p=92&spm_id_from=333.788.videopod.episodes](https://www.bilibili.com/video/BV1CL4y1i7qR?vd_source=7755fe49534ae933cbf5d23064211159&p=92&spm_id_from=333.788.videopod.episodes) + +spring + +[https://www.bilibili.com/video/BV1Kv4y1x7is?vd_source=7755fe49534ae933cbf5d23064211159&spm_id_from=333.788.videopod.episodes&p=26](https://www.bilibili.com/video/BV1Kv4y1x7is?vd_source=7755fe49534ae933cbf5d23064211159&spm_id_from=333.788.videopod.episodes&p=26) + +mybatis + +[https://www.bilibili.com/video/BV1ME421w7Ms?spm_id_from=333.788.videopod.episodes&vd_source=7755fe49534ae933cbf5d23064211159&p=18](https://www.bilibili.com/video/BV1ME421w7Ms?spm_id_from=333.788.videopod.episodes&vd_source=7755fe49534ae933cbf5d23064211159&p=18) + +springMVC + +[https://www.bilibili.com/video/BV1Lh4y1M7kx?spm_id_from=333.788.videopod.episodes&vd_source=7755fe49534ae933cbf5d23064211159&p=17](https://www.bilibili.com/video/BV1Lh4y1M7kx?spm_id_from=333.788.videopod.episodes&vd_source=7755fe49534ae933cbf5d23064211159&p=17) \ No newline at end of file diff --git a/posts/blog/编程技术/java/JUC/JUC笔记.md b/posts/blog/编程技术/java/JUC/JUC笔记.md new file mode 100644 index 0000000..bc56654 --- /dev/null +++ b/posts/blog/编程技术/java/JUC/JUC笔记.md @@ -0,0 +1,647 @@ +--- +title: JUC笔记 +date: 2024-08-25 +tags: [JUC] +--- + + +- JUC:java.util.concurrent 并发编程 + +![image 30.png](JUC笔记/image30.png) + +## 常见线程安全类 + +- String +- Integer +- StringBuffer +- Random +- Vector +- Hashtable +- java.util.concurrent包下的类(juc) + +这里说它们是线程安全的是指,**多个线程调用它们同一个实例**的某个方法时,是线程安全的。但是自己手动组合方法是线程不安全的 + +> [!important] String、Integer是不可变线程,因为值是不可修改 + +### 效率问题 + +1. 单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用 + cpu ,不至于一个线程总占用 cpu,别的线程没法干活 +2. 多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的 + 有些任务,经过精心设计,将任务拆分,并行执行,当然可以提高程序的运行效率。但不是所有计算任 + 务都能拆分(参考后文的【阿姆达尔定律】) + 也不是所有任务都需要拆分,任务的目的如果不同,谈拆分和效率没啥意义 +3. IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】,这时相当于线程虽然不用 cpu,但需要一 + 直等待 IO 结束,没能充分利用线程。所以才有后面的【非阻塞 IO】和【异步 IO】优化 + +> 注意 +> 需要在多核 cpu 才能提高效率,单核仍然时是轮流执行 + +### 原子性 + +- 原子操作可以保证原子性,加锁本质是保证了多线程修改操作为原子操作 + - 原子操作:一个操作或者多个操作组合在一起,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。特点:不可打断、一致性 + +- 两个线程对一个变量进行修改,产生线程问题就是因为修改不是原子操作 + + 例如对于 i++、i-- 而言(i 为静态变量),实际会产生如下的 JVM 字节码指令: + + ```Java + //i++操作 + getstatic i // 获取静态变量i的值 + iconst_1 // 准备常量1 + iadd // 自增 + putstatic i // 将修改后的值存入静态变量i + //i--操作 + getstatic i // 获取静态变量i的值 + iconst_1 // 准备常量1 + isub // 自减 + putstatic i // 将修改后的值存入静态变量i + ``` + + 多线程,指令交错执行 + + ```Java + // 假设i的初始值为0 + getstatic i // 线程2-获取静态变量i的值 线程内i=0 + + getstatic i // 线程1-获取静态变量i的值 线程内i=0 + iconst_1 // 线程1-准备常量1 + iadd // 线程1-自增 线程内i=1 + putstatic i // 线程1-将修改后的值存入静态变量i 静态变量i=1 + iconst_1 // 线程2-准备常量1 + isub // 线程2-自减 线程内i=-1 + + putstatic i // 线程2-将修改后的值存入静态变量i 静态变量i=-1 + ``` + + +## synchronized原理 + +### Monitor 概念 + +- Java对象头:内存结构 + - 普通对象 + + ```Java + |--------------------------------------------------------------| + | Object Header (64 bits) | + |------------------------------------|-------------------------| + | Mark Word (32 bits) | Klass Word (32 bits) | + |------------------------------------|-------------------------| + ``` + + - 数组对象 + + ```Java + |---------------------------------------------------------------------------------| + | Object Header (96 bits) | + |--------------------------------|-----------------------|------------------------| + | Mark Word(32bits) | Klass Word(32bits) | array length(32bits) | + |--------------------------------|-----------------------|------------------------| + ``` + + - 其中 Mark Word 结构为 + + ```Java + |-------------------------------------------------------|--------------------| + | Mark Word (32 bits) | State | + |-------------------------------------------------------|--------------------| + | hashcode:25 | age:4 | biased_lock:0 | 01 | Normal | + |-------------------------------------------------------|--------------------| + | thread:23 | epoch:2 | age:4 | biased_lock:1 | 01 | Biased | + |-------------------------------------------------------|--------------------| + | ptr_to_lock_record:30 | 00 | Lightweight Locked | + |-------------------------------------------------------|--------------------| + | ptr_to_heavyweight_monitor:30 | 10 | Heavyweight Locked | + |-------------------------------------------------------|--------------------| + | | 11 | Marked for GC | + |-------------------------------------------------------|--------------------| + ``` + + ```Java + |--------------------------------------------------------------------|--------------------| + | Mark Word (64 bits) | State | + |--------------------------------------------------------------------|--------------------| + | unused:25 | hashcode:31 | unused:1 | age:4 | biased_lock:0 | 01 | Normal | + |--------------------------------------------------------------------|--------------------| + | thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | 01 | Biased | + |--------------------------------------------------------------------|--------------------| + | ptr_to_lock_record:62 | 00 | Lightweight Locked | + |--------------------------------------------------------------------|--------------------| + | ptr_to_heavyweight_monitor:62 | 10 | Heavyweight Locked | + |--------------------------------------------------------------------|--------------------| + | | 11 | Marked for GC | + |--------------------------------------------------------------------|--------------------| + ``` + + + + +- Monitor锁,记录了持有对象,阻塞线程,等待线程,Monitor是==操作系统提供的==,由c++实现的,每个锁对象关联一个Monitor锁(**锁对象保存了Monitor的地址**) + + ![image 1 15.png](JUC笔记/image115.png) + + - Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态 + - BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片 + - BLOCKED 线程会在 Owner 线程释放锁时唤醒 + - WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争 +- 每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(==重量级==)之后,该**对象头**的Mark Word 中就被设置指向 Monitor 对象的指针 + + ![image 2 8.png](JUC笔记/image28.png) + + +**synchronized的3种状态** + +- 线程与对象绑定,是在线程中使用对象锁,而不是调用对象 + +### 偏向锁 + +- 偏向锁从 Java 6 引入,在 Java 15 被废弃,Java 16-17 默认关闭,最终在 Java 18 中被完全移除 + - 17中,不使用锁就是默认状态,使用就是轻量锁 +- **默认是偏向锁**(对象头后3位:101),解决锁重入问题,即只有一个线程多次获取(已经获取)锁对象的情况 +- 锁升级 + - 当调用**hashcode**方法时,由于偏向锁代替了hash值的位置,对象头会恢复hash值正常,所以将锁升级为轻量级锁 + - 当多个线程使用锁时(以该对象为锁,错开使用,无竞争),锁期间为轻量锁 + - 调用**wait/notify**时,这是重量级锁的方法 +- 批量重偏向:当同一个类的**多个对象频繁地发生重偏向时**(重偏向阈值:20),JVM 将超出阈值的对象==偏向锁重置==,而不是升级为轻量锁 + + - 详解 + - 当锁对象与其他线程绑定,此时锁变为轻量级锁(00),解除时,变为初始状态(001),当多个相同类的对象修改绑定(集合中的元素),从第20个开始,对象进行重偏向(101),将与新的线程绑定 + - 101(偏向锁) ——> 001(无状态) 101(偏向锁) ——> 101(偏向锁) + + > [!important] 总结:批量重偏向会以class为单位,为每个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器+1,当这个值达到重偏向阈值(默认20)时,JVM就认为该class的偏向锁有问题,因此会进行批量重偏向。 + +- 批量撤销:当一个类的所有对象进行了40次重偏向时,那么这个类下的对象再偏向、创建都会赋默认状态(001) + +### 轻量级锁 + +- 高版本jdk默认是轻量级锁(对象头后2位:00),当一个对象锁**没有竞争**,那么这个锁就是轻量级锁,锁被占用(发生竞争)升级为重量级锁 + +- 详情 + - 在即将开始执行同步代码块中的内容时,会首先检查对象的Mark Word,查看锁对象是否被其他线程占用,如果没有任何线程占用,那么会在当前线程中所处的栈帧中建立一个名为锁记录(Lock Record)的空间,用于复制并存储对象目前的Mark Word信息(官方称为Displaced Mark Word)。接着,虚拟机将使用**CAS操作**将对象的Mark Word更新为轻量级锁状态(数据结构变为指向Lock Record的指针,指向的是当前的栈帧) + - 如果CAS操作失败了的话,那么说明可能这时有线程已经进入这个同步代码块了,这时虚拟机会再次检查对象的Mark Word,是否指向当前线程的栈帧,如果是,说明不是其他线程,而是当前线程已经有了这个对象的锁,直接放心大胆进同步代码块即可。如果不是,那确实是被其他线程占用了。 + - 这时,轻量级锁一开始的想法就是错的(这时有对象在竞争资源,已经赌输了),所以说只能将锁膨胀为重量级锁,按照重量级锁的操作执行(注意锁的膨胀是不可逆的) + +![image 3 7.png](JUC笔记/image37.png) + +### 重量级锁 + +- 当对象锁,发生竞争时,对象锁要关联一个Monitor对象,管理锁的状态 + +**自旋优化** + +- 重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。说白了就是,等一下 + +- 详情 + + | | | | + |---|---|---| + |线程 1 (core 1 上)|对象 Mark|线程 2 (core 2 上)| + |-|10(重量锁)|-| + |访问同步块,获取 monitor|10(重量锁)|-| + |成功(加锁)|10(重量锁)|-| + |执行同步块|10(重量锁)|-| + |执行同步块|10(重量锁)|访问同步块,获取 monitor| + |执行同步块|10(重量锁)|自旋重试| + |执行完毕|10(重量锁)|自旋重试| + |成功(解锁)|01(无锁)|自旋重试| + |-|10(重量锁)|成功(加锁)| + |-|10(重量锁)|执行同步块| + |-||| + + +## 保护性设计模式 + +**join原理** + +![image 4 7.png](JUC笔记/image47.png) + +- 当线程结束时,会调用notifyAll方法,释放资源和锁 + +### park&unpark + +- 基本使用:它们是 `**LockSupport**` 类中的**静态方法** + + ```Java + // 暂停当前线程 + LockSupport.park(); + // 恢复某个线程的运行 + LockSupport.unpark(暂停线程对象) + ``` + +- 正常:先 `park` 再 `unpark` ,但是,先`unpark`再`park`也可以,不过`park`将暂停不了 +- 特点: + - 与 `Object` 的 `wait` & `notify` 相比`wait`,`notify` 和 `notifyAll` 必须配合 `Object Monitor` 一起使用,而 `park`,`unpark` 不必 + - `park` & `unpark` 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【**精确**】 + - `park` & `unpark` 可以先 `unpark`,而 `wait` & `notify` 不能先 `notify` + +- **原理** + + - 具体是靠c/c++实现,每个线程都有自己的一个Parker对象,由三部分组成`_counter`、`_cond`和`_mutex` + - `_counter` :计数,1:表示线程继续,0:表示线程暂停 + - `_cond` : 条件变量,当线程暂停时存储在这里 + - `_mutex` : 互斥锁 + + - 小故事理解 + - 线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。`_counter`就好比背包中的备用干粮(0 为耗尽,1 为充足) + - 调用 `park`就是要看需不需要停下来歇息 + - 如果备用干粮耗尽,那么钻进帐篷歇息 + - 如果备用干粮充足,那么不需停留,继续前进 + - 调用`unpark`,就好比令干粮充足 + - 如果这时线程还在帐篷,就唤醒让他继续前进 + - 如果这时线程还在运行,那么下次他调用 `park`时,仅是消耗掉备用干粮,不需停留继续前进因为背包空间有限,多次调用 `unpark`仅会补充一份备用干粮 + + ![image 5 6.png](JUC笔记/image56.png) + + park方法 + + - 当线程调用`**park**`方法,检查`_counter`的值 + - 为0,则线程获取`_mutex`互斥锁,进入`_cond`条件变量,阻塞线程,设置`_counter=0` + - 为1,则线程无需阻塞,继续运行,设置`_counter=0` + + ![image 6 4.png](JUC笔记/image64.png) + + unpark方法 + + - 当线程调用`**unpark**` 方法,将`_counter=1` ,并尝试唤醒 `_cond`条件变量中的线程 + - `_cond` 中有线程,则恢复运行,设置 `_counter=0` + - `_cond` 中无线程,`_counter` 值不变 + +- 总结:park方法,一定会将`_cond` 值设置为0,unpark方法,无阻塞线程时,才能将`_cond` 值设置为1 + +## 活跃性问题 + +### 死锁 + +- 一个线程需要同时获取多把锁,这时就容易发生死锁 +- 解决方式 + + - 按序加锁 + + - 超时释放锁:使用ReentrantLock + + - 使用工具检测是否死锁 + + - 检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁 + + + + +### 活锁 + +- 活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束 + +### 哲学家就餐问题 + +- 有五位哲学家,围坐在圆桌旁。他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。如果筷子被身边的人拿着,自己就得等待 + + ![image 7 4.png](JUC笔记/image74.png) + + +## ReentrantLock + +- 可重入锁(ReentrantLock) 比synchronized更灵活,体现在可以不用包裹整个代码块中 + +```Java +synchronized(){ + if(){ + //内容 + } +} +``` + +```Java +lock.lock(); +if(){ +//内容 +lock.unlock(); +} +``` + +- 可重入锁(嵌套自己🔒) + - `lock.lock()` +- **可打断**(阻塞状态) + - 使用`lock.lockInterruptibly()` ,允许线程在等待获取锁的过程(阻塞状态)中被打断,普通锁无法打断,打断后报异常 + - 当线程获取`lockInterruptibly`锁后,则与`lock`方法效果一样,此时其他线程调用`thread.interrupt()`方法,无法中断线程 +- **锁超时**(谦让锁) + - `lock.tryLock()` ,返回布尔值,表示是否获取到锁 + - `lock.tryLock(long timeout, TimeUnit unit)` ,返回布尔值,表示是否获取到锁,可打断 +- 公平锁 + - 默认是不公平锁,创建实例时,设置为公平锁(true) + - 一般不设置,影响性能 +- **条件变量**(线程通信) + - 一个ReentrantLock实例可以创建**多个条件变量**,条件变量相等于`WaitSet`区域,使线程等待更加细分,不用多个线程在一个区域 + - 使用:要先获得锁,在对应条件变量中等待 + + ```Java + //创建条件变量(休息室) + Condition condition1 = lock.newCondition(); + Condition condition2 = lock.newCondition(); + Condition condition3 = lock.newCondition(); + //线程首先获取锁 + lock.lock(); + //进入等待状态,也可以设置时间 + condition1.await(); + //叫醒等待线程 + condition1.signal(); + condition1.signalAll(); + ``` + +- wait/notify→await/signal + +## 内存 + +### java内存模型 + +- 原理详解 + + 因为 java是对硬件虚拟化,所有**对 ,以及编译器、CPU指令重排机制这些提示效率的方式进行了实现**,所以定义了一套规范,抽象了线程和主内存之间的关系,规定了从 Java 源代码到 CPU 可执行指令,的这个转化过程要遵守哪些和并发相关的原则和规范,主要目的是为了简化多线程编程,增强程序可移植性的 + +- JMM(java Memory Model),JMM 是一种规范,定义了 Java 程序中各个变量(包括实例字段、静态字段和数组元素)的访问方式。它通过抽象的主内存(堆)和工作内存(栈)模型,确保线程间的内存**可见性**和**有序性** +- 了解java内存规范,有利于开发者编写**线程安全的程序** +- JMM 体现在以下几个方面 + - 原子性 - 保证指令不会受到**线程上下文切**换的影响 + - 可见性 - 保证指令不会受 **cpu 缓存**的影响 + - 有序性 - 保证指令不会受 **cpu 指令并行优化**的影响 + +### 可见性 + +- 现象:当一个线程对公共变量进行修改,另一个线程中数据无法同步(线程不安全) + + ```Java + static boolean run = true; + public static void main(String[] args) throws InterruptedException { + Thread t = new Thread(()->{ + while(run){ + // .... + } + }); + t.start(); + sleep(1); + run = false; // 线程t不会如预想的停下来 + } + ``` + + - 循环中有 `System.out.println()`输出语句也可以退出线程,因为输出语句中用到了锁 + +- 产生原因:t线程频繁读取run的值,**JIT编译器将run的值写入t的工作内存中的缓冲区(**模仿CPU高速缓冲区),其他线程修改的是主内存的值,所以导致数据不一致 + + ![image 8 4.png](JUC笔记/image84.png) + +- 解决方式:**[[JUC]]、s**ynchronized +- 使用场景:仅用在**一个写线程,多个读线程**的情况 + +> [!important] synchronized 语句块既可以**保证代码块的原子性**,也同时**保证代码块内变量的可见性**。但缺点是synchronized 是属于重量级操作,性能相对更低 +> +> - 使用synchronized锁,JVM 会插入内存屏障指令。这些内存屏障会**刷新本地线程的缓存**,确保线程可以**看到共享变量的最新值** + +### 有序性 + +- Java 源代码会经历 **编译器优化重排 —> 指令并行重排 —> 内存系统重排** 的过程,最终才变成操作系统可执行的指令序列。**指令重排序可以保证串行语义一致,但是没有义务保证多线程间的语义也一致** ,所以在多线程下,指令重排序可能会导致一些问题。 + +- 现象:**双重检查实现对象单例**无法保证单例 + + ```Java + public class Singleton { + private volatile static Singleton uniqueInstance; + private Singleton() {} + public static Singleton getUniqueInstance() { + //先判断对象是否已经实例过,没有实例化过才进入加锁代码 + if (uniqueInstance == null) { + //类对象加锁 + synchronized (Singleton.class) { + if (uniqueInstance == null) { + uniqueInstance = new Singleton(); + } + } + } + return uniqueInstance; + } + } + ``` + + 无法保证单例的原因是这条语句`**uniqueInstance = new Singleton();**` ,也就是new对象的过程, + + - new对象对应的指令:1、创建对象 2、调用构造方法(初始化)3、赋值给静态变量 + + ```Java + 17: new #3 // class cn/itcast/n5/Singleton + 20: dup + 21: invokespecial #4 // Method "":()V + 24: putstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton; + + //堆内创建对象,返回地址存入操作数栈 + //复制一份地址,存入操作数栈 + //调用构造方法,地址出栈 + //将地址赋给静态变量,地址出栈 + ``` + + - 由于指令重排,导致先3,后2,当其他线程进入方法时,`if (uniqueInstance == null)` 不满足,因为此时静态变量被赋值为未初始化对象,所以**其他线程可能会获得未初始化的对象**,使用该对象就可能触发空指针异常 + +- 所以,当在多线程环境下多数据的修改会出现难以预料的情况! +- 解决方式:**[[JUC]]、s**ynchronized + +### **volatile** + +- 是一个**修饰符**,修饰成员变量(多线程环境),它可以避免线程访问时,访问工作内存(缓存)中的值,==**直接访问主内存中的值**==**,**保证了==**可见性**==,它还可以==**禁止指令重排**==,保证了==**有序性**== +- volatile 的底层实现原理是**内存屏障**,Memory Barrier(Memory Fence) + - 对 volatile 变量的(赋值)写指令后会加入写屏障 + - 写屏障(sfence)保证在==**写屏障之前的指令不进行重排**==**,并且**==**对共享变量的改动,都同步到主存中**== + - 对 volatile 变量的(读取)读指令前会加入读屏障 + - 读屏障(lfence)保证在==**读屏障之后的指令不进行重排**==**,并且**==**对共享变量的读取,加载的主存中最新数据**== + +> [!important] 重要的是要注意,synchronized 并不阻止块内部的指令重排,volatile 提供了更严格的重排序规则, +> +> **它不允许 volatile 变量的读写与其他内存操作重排**,synchronized 的重排序规则相对没那么严格 + +### Happens-before + +- 是 Java 内存模型(JMM)中的一个核心**概念**,用于定义操作之间的内存可见性保证。它是一种偏序关系,用来**描述程序中不同操作的执行顺序和可见性** +- 定义:如果一个操作 A happens-before 另一个操作 B,那么 ==**A 的结果对 B 是可见的**==,且 A 的执行顺序排在 B 之前 +- 主要作用: + - 确保内存可见性:保证一个线程的写操作对其他线程是可见的。 + - 防止重排序:限制编译器和处理器对指令进行重排序的能力 + +- 常见情况 + + a. 程序顺序规则: + + 在同一个线程中,按照程序的顺序,前面的操作 happens-before 后面的操作。 + + b. 监视器锁规则: + + 一个锁的解锁 happens-before 于后续对这个锁的加锁。 + + c. volatile 变量规则: + + 对一个 volatile 变量的写操作 happens-before 于后续对这个变量的读操作。 + + d. 线程启动规则: + + Thread 对象的 start() 方法 happens-before 于这个线程的每一个动作。 + + e. 线程终止规则: + + 线程中的所有操作都 happens-before 于其他线程检测到这个线程已经终止。 + + f. 中断规则: + + 一个线程调用另一个线程的 interrupt() 方法 happens-before 于被中断线程检测到中断事件的发生。 + + g. 终结器规则: + + 一个对象的初始化完成 happens-before 于它的 finalize() 方法的开始。 + + h. 传递性: + + 如果 A happens-before B,且 B happens-before C,那么 A happens-before C。 + + +## 无锁 + +### CAS + +- 使用`CAS`机制解决并发修改问题 + + ```Java + //修改共享数据 + AtomicInteger balance;//封装的共享数据 + while(true) { + // 比如拿到了旧值 1000 + int prev = balance.get(); + // 在这个基础上 1000-10 = 990 + int next = prev - amount; + /* + compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值- 不一致了,next 作废,返回 false 表示失败 + 比如,别的线程已经做了减法,当前值已经被减成了 990 + 那么本线程的这次 990 就作废了,进入 while 下次循环重试- 一致,以 next 设置为新值,返回 true 表示成功 + */ + if (balance.compareAndSet(prev, next)) break; + } + ``` + +- Compare And Swap,它是一种用于实现多线程环境下的**无锁并发**编程技术,是基于 的思想,不怕共享变量被修改,保障每一次修改是成功的(原子修改),无锁并发、无阻塞并发, + +- 底层 ,封装数据对应的变量,由volatitle修饰 + - CAS 的底层是`lock cmpxchg` 指令(X86 架构),在单核 CPU 和多核 CPU 下都能够保证【比较-交换】的原子性 + +- 效率问题:因为线程需要一直运行(只有就绪、运行状态),所以线程数小于cpu核心数时才能提升效率 + +- 使用 + + **原子整数** + + - 对基本数据类型进行了封装,比如AtomicBoolean、AtomicInteger、AtomicLong使用类似,以AtomicInteger为例 + - 构造方法: + - 常用方法: + + | | | + |---|---| + |方法名|描述| + ||获取值| + ||修改数据,返回是否修改成功,可能修改失败| + ||返回自增后的值/返回值再自增| + ||| + ||返回增加data后的值/返回值再增加data| + ||| + ||修改数据| + + + **原子引用** + + - 对引用对象进行封装,如AtomicReference、AtomicMarkableReference、AtomicStampedReference,每次修改对象时,**更新的实际上是对象的引用地址** + + 上同 + + AtomicReference的·使用· + + - ABA问题:线程无法感知数据被修改(指修改完,值不变),可以使用版本号记录 + + AtomicStampedReference基于**版本号**的方法,每次修改需要修改版本号,告知共享数据是否被修改 + + AtomicMarkableReference维护一个**布尔值**表示状态,只关心数据是否被修改而不看次数 + + +**原子数组** + +- 因为原子引用每次修改的都是地址,而数组的修改是对内容的修改,所以推出原子数组AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray + + + + + + + +字段更新器 + +原子累加器 + +### 🚀 什么是 CAS? + +**Compare And Swap** 是一种原子操作,用于在多线程环境中安全地更新变量,避免使用互斥锁(`synchronized` 或 `mutex`)造成的性能瓶颈。 + +它的基本思路就是: + +> “我想把一个值改成新值,但前提是它现在还是旧值。” + +--- + +### 🔧 CAS 的原理 + +假设你有一个变量 `V`,你想把它从旧值 `A` 改成新值 `B`,CAS 会做这样的操作: + +```Plain +if (V == A) { + V = B; + return true; // 修改成功 +} else { + return false; // 修改失败,有别的线程动过它 +} +``` + +这个操作是 **原子的**(不会被中断),通常由硬件指令(如 x86 的 `CMPXCHG`)直接支持。 + +--- + +### 📦 应用场景 + +- Java 中的 `AtomicInteger`, `AtomicReference` 等类底层就是用 CAS 实现的。 +- 实现无锁数据结构(如无锁队列、无锁栈) +- 避免使用传统的锁,提高并发性能 + +--- + +### ⚠️ CAS 的问题 + +1. **ABA 问题**: + - 如果变量从 A 改成 B,然后又改回 A,CAS 认为它没变,其实发生了两次变化。 + - 解决方法:使用 **版本号** 或 **带时间戳的引用**,比如 Java 的 `AtomicStampedReference`。 +2. **自旋问题**: + - 如果一直 CAS 失败(比如多个线程争抢同一个变量),会反复尝试,造成高 CPU 占用。 +3. **只能操作一个变量**: + - 无法同时原子地更新多个变量,除非封装成一个整体结构。 + +--- + + + + + + + + + + + +## 悲观锁、乐观锁 + +### 悲观锁 + +- 悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,**共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程**。 +- 像 Java 中`synchronized`和`ReentrantLock`等独占锁就是悲观锁思想的实现。 + +### 乐观锁 + +- 乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法) +- 在 Java 中`java.util.concurrent.atomic`包下面的原子变量类(比如`AtomicInteger`、`LongAdder`)就是使用了乐观锁的一种实现方式 **CAS** 实现的。 \ No newline at end of file diff --git a/posts/blog/编程技术/java/JUC/JUC笔记/image115.png b/posts/blog/编程技术/java/JUC/JUC笔记/image115.png new file mode 100644 index 0000000..3f56dcd Binary files /dev/null and b/posts/blog/编程技术/java/JUC/JUC笔记/image115.png differ diff --git a/posts/blog/编程技术/java/JUC/JUC笔记/image28.png b/posts/blog/编程技术/java/JUC/JUC笔记/image28.png new file mode 100644 index 0000000..0162841 Binary files /dev/null and b/posts/blog/编程技术/java/JUC/JUC笔记/image28.png differ diff --git a/posts/blog/编程技术/java/JUC/JUC笔记/image30.png b/posts/blog/编程技术/java/JUC/JUC笔记/image30.png new file mode 100644 index 0000000..4b6a6c1 Binary files /dev/null and b/posts/blog/编程技术/java/JUC/JUC笔记/image30.png differ diff --git a/posts/blog/编程技术/java/JUC/JUC笔记/image37.png b/posts/blog/编程技术/java/JUC/JUC笔记/image37.png new file mode 100644 index 0000000..74b0c24 Binary files /dev/null and b/posts/blog/编程技术/java/JUC/JUC笔记/image37.png differ diff --git a/posts/blog/编程技术/java/JUC/JUC笔记/image47.png b/posts/blog/编程技术/java/JUC/JUC笔记/image47.png new file mode 100644 index 0000000..9f3c6c0 Binary files /dev/null and b/posts/blog/编程技术/java/JUC/JUC笔记/image47.png differ diff --git a/posts/blog/编程技术/java/JUC/JUC笔记/image56.png b/posts/blog/编程技术/java/JUC/JUC笔记/image56.png new file mode 100644 index 0000000..f602cac Binary files /dev/null and b/posts/blog/编程技术/java/JUC/JUC笔记/image56.png differ diff --git a/posts/blog/编程技术/java/JUC/JUC笔记/image64.png b/posts/blog/编程技术/java/JUC/JUC笔记/image64.png new file mode 100644 index 0000000..1a5daaf Binary files /dev/null and b/posts/blog/编程技术/java/JUC/JUC笔记/image64.png differ diff --git a/posts/blog/编程技术/java/JUC/JUC笔记/image74.png b/posts/blog/编程技术/java/JUC/JUC笔记/image74.png new file mode 100644 index 0000000..a186b75 Binary files /dev/null and b/posts/blog/编程技术/java/JUC/JUC笔记/image74.png differ diff --git a/posts/blog/编程技术/java/JUC/JUC笔记/image84.png b/posts/blog/编程技术/java/JUC/JUC笔记/image84.png new file mode 100644 index 0000000..bf46933 Binary files /dev/null and b/posts/blog/编程技术/java/JUC/JUC笔记/image84.png differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇.md b/posts/blog/编程技术/java/JVM/JVM中篇.md new file mode 100644 index 0000000..8d46ea4 --- /dev/null +++ b/posts/blog/编程技术/java/JVM/JVM中篇.md @@ -0,0 +1,708 @@ +--- +title: JVM中篇 +date: 2024-08-25 +tags: [JVM] +--- + + +## JVM内存管理 + +在传统的C/C++开发中,我们经常通过使用申请内存的方式来创建对象或是存放某些数据,但是这样也带来了一些额外的问题,我们要在何时释放这些内存,怎么才能使得内存的使用最高效,因此,内存管理是一个非常严肃的问题。 + +比如我们就可以通过C语言动态申请内存,并用于存放数据: + +```C +\#include #include int main(){ + //动态申请4个int大小的内存空间 + int* memory = malloc(sizeof(int) * 4); + //修改第一个int空间的值 + memory[0] = 10; + //修改第二个int空间的值 + memory[1] = 2; + //遍历内存区域中所有的值 + for (int i = 0;i < 4;i++){ + printf("%d ", memory[i]); + } + //释放指针所指向的内存区域 + free(memory); + //最后将指针赋值为NULL + memory = NULL; +} +``` + +而在Java中,这种操作实际上是不允许的,Java只支持直接使用基本数据类型和对象类型,至于内存到底如何分配,并不是由我们来处理,而是JVM帮助我们进行控制,这样就帮助我们节省很多内存上的工作,虽然带来了很大的便利,但是,一旦出现内存问题,我们就无法像C/C++那样对所管理的内存进行合理地处理,因为所有的内存操作都是由JVM在进行,**只有了解了JVM的内存管理机制,我们才能够在出现内存相关问题时找到解决方案。** + +## 内存管理划分 + +既然要管理内存,那么肯定不会是杂乱无章的,JVM对内存的管理采用的是分区治理,不同的内存区域有着各自的职责所在,在虚拟机运行时,内存区域如下划分:![点击查看图片来源](JVM中篇/CP4yv1iqrfjmXzW.jpg) + +我们可以看到,内存区域一共分为5个区域,其中方法区和堆是所有线程共享的区域,随着虚拟机的创建而创建,虚拟机的结束而销毁,而虚拟机栈、本地方法栈、程序计数器都是线程之间相互隔离的,每个线程都有一个自己的区域,并且线程启动时会自动创建,结束之后会自动销毁。内存划分完成之后,我们的JVM执行引擎和本地库接口,也就是Java程序开始运行之后就会根据分区合理地使用对应区域的内存了。 + +> 大致划分 详情🔗:[https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.5](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.5) + +### 程序计数器 + +- **目的**:首先我们来介绍一下程序计数器,它和我们的传统8086 CPU中PC寄存器的工作差不多,因为JVM虚拟机目的就是==**实现物理机那样的程序执行**==。在8086 CPU中,PC作为程序计数器,负责储存内存地址,该地址指向下一条即将执行的指令,每解释执行完一条指令,PC寄存器的值就会自动被更新为下一条指令的地址,进入下一个指令周期时,就会根据当前地址所指向的指令,进行执行。 +- **作用**:而JVM中的程序计数器可以看做是==**当前线程所执行字节码的行号指示器(记录行号)**==,而行号正好就指的是某一条指令,字节码解释器在工作时也会改变这个值,来指定下一条即将执行的指令,程序计数器因为只需要记录很少的信息,所以只占用很少一部分内存。 +- 因为Java的多线程也是依靠时间片轮转算法进行的,因此一个CPU同一时间也只会处理一个线程,当某个线程的时间片消耗完成后,会自动切换到下一个线程继续执行,而==**当前线程的执行位置会被保存到当前线程的程序计数器**==中,当下次轮转到此线程时,又继续根据之前的执行位置继续向下执行。 + +### 虚拟机栈 + +- 虚拟机栈就是一个非常关键的部分,看名字就知道它是一个**栈结构**,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(其实就是栈里面的一个元素:**方法栈帧**),栈帧中包括了当前方法的一些信息,比如局部变量表、操作数栈、动态链接、方法出口等。 + + ![image-20230306164822720](JVM中篇/As1NGy6BwhKJ9kY.webp) + +- 其中局部变量表就是我们方法中的局部变量,之前我们也进行过演示,实际上局部变量表在class文件中就已经定义好了,操作数栈就是我们之前字节码执行时使用到的栈结构; 每个栈帧还保存了一个**可以指向当前方法所在类**的运行时常量池,目的是:当前方法中如果需要调用其他方法的时候,能够从运行时常量池中找到对应的符号引用,然后将符号引用转换为直接引用,然后就能直接调用对应方法,这就是动态链接(我们还没讲到常量池,暂时记住即可,建议之后再回顾一下),最后是方法出口,也就是方法该如何结束,是抛出异常还是正常返回。 + - 这里我们来模拟一下整个虚拟机栈的运作流程,我们先编写一个测试类: + + ```Java + public class Main { + public static void main(String[] args) { + int res = a(); + System.out.println(res); + } + + public static int a(){ + return b(); + } + + public static int b(){ + return c(); + } + + public static int c(){ + int a = 10; + int b = 20; + return a + b; + } + } + ``` + + 当我们的主方法执行后,会依次执行三个方法`a() -> b() -> c() -> 返回`,我们首先来观察一下反编译之后的结果: + + ```Plain + { + public com.test.Main(); \#这个是构造方法 + descriptor: ()V + flags: ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + 0: aload_0 + 1: invokespecial #1 // Method java/lang/Object."":()V + 4: return + LineNumberTable: + line 3: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/test/Main; + + public static void main(java.lang.String[]); \#主方法 + descriptor: ([Ljava/lang/String;)V + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=2, locals=2, args_size=1 + 0: invokestatic #2 // Method a:()I + 3: istore_1 + 4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; + 7: iload_1 + 8: invokevirtual #4 // Method java/io/PrintStream.println:(I)V + 11: return + LineNumberTable: + line 5: 0 + line 6: 4 + line 7: 11 + LocalVariableTable: + Start Length Slot Name Signature + 0 12 0 args [Ljava/lang/String; + 4 8 1 res I + + public static int a(); + descriptor: ()I + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=1, locals=0, args_size=0 + 0: invokestatic #5 // Method b:()I + 3: ireturn + LineNumberTable: + line 10: 0 + + public static int b(); + descriptor: ()I + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=1, locals=0, args_size=0 + 0: invokestatic #6 // Method c:()I + 3: ireturn + LineNumberTable: + line 14: 0 + + public static int c(); + descriptor: ()I + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=2, locals=2, args_size=0 + 0: bipush 10 + 2: istore_0 + 3: bipush 20 + 5: istore_1 + 6: iload_0 + 7: iload_1 + 8: iadd + 9: ireturn + LineNumberTable: + line 18: 0 + line 19: 3 + line 20: 6 + LocalVariableTable: + Start Length Slot Name Signature + 3 7 0 a I + 6 4 1 b I + } + ``` + + 可以看到在编译之后,我们整个方法的最大操作数栈深度、局部变量表都是已经确定好的,当我们程序开始执行时,会根据这些信息封装为对应的栈帧,我们从`main`方法开始看起: + + ![image-20230306164838564](JVM中篇/rRtxbFZXDkmGci4.webp) + + 接着我们继续往下,到了 `0: invokestatic #2 // Method a:()I`时,需要调用方法`a()`,这时当前方法就不会继续向下运行了,而是去执行方法`a()`,那么同样的,将此方法也入栈,注意是放入到栈顶位置,`main`方法的栈帧会被压下去: + + ![image-20230306164848411](JVM中篇/SZJWH8l5xByTjr7.webp) + + 这时,进入方法a之后,又继而进入到方法b,最后在进入c,因此,到达方法c的时候,我们的虚拟机栈变成了: + + ![image-20230306164859169](https://oss.itbaima.cn/internal/markdown/2023/03/06/FdS8U4lHVjCLKwD.png) + + 现在我们依次执行方法c中的指令,最后返回a+b的结果,在方法c返回之后,也就代表方法c已经执行结束了,栈帧4会自动出栈,这时栈帧3就得到了上一栈帧返回的结果,并继续执行,但是由于紧接着马上就返回,所以继续重复栈帧4的操作,此时栈帧3也出栈并继续将结果交给下一个栈帧2,最后栈帧2再将结果返回给栈帧1,然后栈帧1就可以继续向下运行了,最后输出结果。 + + ![image-20230306164908882](JVM中篇/FBpxKWIbuY7ftq4.webp) + +### 本地方法栈 + +- 本地方法栈与虚拟机栈作用差不多,这里不多做介绍。 + +### 堆 + +- 堆是整个Java应用程序共享的区域,也是整个虚拟机最大的一块内存空间,而此区域的职责就是**存放和管理对象和数组**,而我们马上要提到的垃圾回收机制也是主要作用于这一部分内存区域。 + +### 方法区 + +- 方法区也是整个Java应用程序共享的区域,它用于存储所有的类信息、常量、静态变量、动态编译缓存等数据,可以大致分为两个部分,一个是**类信息表**,一个是**运行时常量池**。方法区也是我们要重点介绍的部分。 + +![image 53.png](JVM中篇/image53.png) + +- 首先类信息表中存放的是当前应用程序加载的所有类信息,包括类的版本、字段、方法、接口等信息,同时会将编译时生成的常量池数据全部存放到运行时常量池中。当然,常量也并不是只能从类信息中获取,==在程序运行时,也有可能会有新的常量进入到常量池==。 + +- 其实我们的String类正是利用了常量池进行优化,这里我们编写一个测试用例: + + ```Java + public static void main(String[] args) { + String str1 = new String("abc"); + String str2 = new String("abc"); + + System.out.println(str1 == str2); + System.out.println(str1.equals(str2)); + } + ``` + + 得到的结果也是显而易见的,由于`str1`和`str2`是单独创建的两个对象,那么这两个对象实际上会在堆中存放,保存在不同的地址: + + ![image-20230306164934743](JVM中篇/tnqFSxzEcB4U9WL.webp) + + 所以当我们使用`==`判断时,得到的结果`false`,而使用`equals`时因为比较的是值,所以得到`true`。现在我们来稍微修改一下: + + ```Java + public static void main(String[] args) { + String str1 = "abc"; + String str2 = "abc"; + + System.out.println(str1 == str2); + System.out.println(str1.equals(str2)); + } + ``` + + 现在我们没有使用new的形式,而是直接使用双引号创建,那么这时得到的结果就变成了两个`true`,这是为什么呢?这其实是因为我们直接使用双引号赋值,会先在常量池中查找是否存在相同的字符串,若存在,则将引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将引用指向该字符串: + + ![image-20230306164942208](JVM中篇/GTgbpIYdCyK3RLj.webp) + + 实际上两次调用String类的`intern()`方法,和上面的效果差不多,也是第一次调用会将堆中字符串复制并放入常量池中,第二次通过此方法获取字符串时,会查看常量池中是否包含,如果包含那么会直接返回常量池中字符串的地址: + + ```Java + public static void main(String[] args) { + //不能直接写"abc",双引号的形式,写了就直接在常量池里面吧abc创好了 + String str1 = new String("ab")+new String("c"); + String str2 = new String("ab")+new String("c"); + + System.out.println(str1.intern() == str2.intern()); + System.out.println(str1.equals(str2)); + } + ``` + + ![image-20230306164954716](JVM中篇/EOC5ilkr396BHNy.webp) + + 所以上述结果中得到的依然是两个`true`。在JDK1.7之后,稍微有一些区别,在调用`intern()`方法时,当常量池中没有对应的字符串时,不会再进行复制操作,而是将其直接修改为指向当前字符串堆中的的引用:(也就是说,1.7之前是在字符串常量池中,新建一个String对象保存,1.7之后不创建对象,而是使用引用类型,那么该字符串将一直保存在堆中) + + ![image-20230306165005169](JVM中篇/2fXENphit4OZvVk.webp) + + ![image 1 31.png](JVM中篇/image131.png) + + ```Java + public static void main(String[] args) { + //不能直接写"abc",双引号的形式,写了就直接在常量池里面吧abc创好了 + String str1 = new String("ab")+new String("c"); + System.out.println(str1.intern() == str1); + } + ``` + + ```Java + public static void main(String[] args) { + String str1 = new String("ab")+new String("c"); + String str2 = new String("ab")+new String("c"); + + System.out.println(str1 == str1.intern()); + System.out.println(str2.intern() == str1); + } + ``` + + 所以最后我们会发现,`str1.intern()`和`str1`都是同一个对象,结果为`true`。 + +- 值得注意的是,在JDK7之后,**字符串常量池从方法区移动到了堆中,**也很好理解,毕竟实例已经在堆内存中 + +### 总结 + +最后我们再来进行一个总结,各个内存区域的用途: + +- (线程独有)程序计数器:保存当前程序的执行位置。 +- (线程独有)虚拟机栈:通过栈帧来**维持方法调用顺序**,帮助控制程序有序运行。 +- (线程独有)本地方法栈:同上,作用与本地方法。 +- 堆:所有的对象和数组都在这里保存。 +- 方法区:类信息、即时编译器的代码缓存、运行时常量池。 + +当然,这些内存区域划分仅仅是概念上的,具体的实现过程我们后面还会提到。 + +## 内存泄漏和栈溢出 + +- 内存泄漏简单理解,就是==**堆**==**内存不足**,是指当创建过多对象,没有及时进行垃圾回收清空,即存在多个”**泄漏对象**“导致内存不足(`OutOfMemoryError`),称为内存泄漏,数组容量过大也会引起该问题 + + - 解决方式:可以通过设置堆内存进行调整:`-Xms最小值 -Xmx最大值` + + - 例子 + + ```Java + public static void main(String[] args) { + int[] a = new int[Integer.MAX_VALUE]; + } + ``` + + 这里我们申请了一个容量为21亿多的int型数组,显然,如此之大的数组不可能放在我们的堆内存中,所以程序运行时就会这样: + + ```Java + Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit + at com.test.Main.main(Main.java:5) + ``` + + 这里得到了一个`OutOfMemoryError`错误,也就是我们常说的内存溢出错误。我们可以通过参数来控制堆内存的最大值和最小值: + + ```Plain + -Xms最小值 -Xmx最大值 + ``` + + 比如我们现在限制堆内存为固定值1M大小,并且在抛出内存溢出异常时保存当前内存堆转储快照: + + ![image-20230306165041598](JVM中篇/r5IsmTk3DZfXA26.webp) + + 注意堆内存不要设置太小,不然连虚拟机都不足以启动, + + 接着我们编写一个一定会导致内存溢出的程序: + + ```Java + public class Main { + public static void main(String[] args) { + List list = new ArrayList<>(); + while (true){ + list.add(new Test()); //无限创建Test对象并丢进List中 + } + } + + static class Test{ } + } + ``` + + 在程序运行之后: + + ```Plain + java.lang.OutOfMemoryError: Java heap space + Dumping heap to java_pid35172.hprof ... + Heap dump file created [12895344 bytes in 0.028 secs] + Exception in thread "main" java.lang.OutOfMemoryError: Java heap space + at java.util.Arrays.copyOf(Arrays.java:3210) + at java.util.Arrays.copyOf(Arrays.java:3181) + at java.util.ArrayList.grow(ArrayList.java:267) + at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:241) + at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:233) + at java.util.ArrayList.add(ArrayList.java:464) + at com.test.Main.main(Main.java:10) + ``` + + 可以看到错误出现原因正是`Java heap space`,也就是堆内存满了,并且根据我们设定的VM参数,堆内存保存了快照信息。我们可以在IDEA内置的Profiler中进行查看: + + ![image-20230306165105858](JVM中篇/C71bdyIY2JVpLKz.webp) + + 可以很明显地看到,在创建了360146个Test对象之后,堆内存蚌埠住了,于是就抛出了内存溢出错 + +- **栈溢出**是指栈中的栈帧满了,一般是递归过多或无限递归引发的 + + - 解决方式:修改代码逻辑,设置栈内存`-Xss`来设定栈容量 + + - 例子 + + ```Java + public class Main { + public static void main(String[] args) { + test(); + } + + public static void test(){ + test(); + } + } + ``` + + 这很明显是一个永无休止的程序,并且会不断继续向下调用test方法本身,那么按照我们之前的逻辑推导,无限地插入栈帧那么一定会将虚拟机栈塞满,所以,当栈的深度已经不足以继续插入栈帧时,就会这样: + + ```Plain + Exception in thread "main" java.lang.StackOverflowError + at com.test.Main.test(Main.java:12) + at com.test.Main.test(Main.java:12) + at com.test.Main.test(Main.java:12) + at com.test.Main.test(Main.java:12) + at com.test.Main.test(Main.java:12) + at com.test.Main.test(Main.java:12) + ....以下省略很多行 + ``` + + 这也是我们常说的栈溢出,它和堆溢出比较类似,也是由于容纳不下才导致的,我们可以使用`-Xss`来设定栈容量。 + + + + +## 申请堆外内存 + +- 除了堆内存可以存放对象数据以外,我们也可以申请堆外内存(直接内存),也就是不受JVM管控的内存区域,这部分区域的内存需要我们自行去申请和释放。 +- 实际上本质就是JVM通过C/C++调用`malloc`函数申请的内存,当然得我们自己去释放了。不过虽然是直接内存,不会受到堆内存容量限制,但是依然会受到本机最大内存的限制,所以还是有可能抛出`OutOfMemoryError`异常。 +- 这里我们需要提到一个堆外内存操作类:`Unsafe`,就像它的名字一样,虽然Java提供堆外内存的操作类,但是实际上它是不安全的,只有你完全了解底层原理并且能够合理控制堆外内存,才能安全地使用堆外内存(这个类不能直接使用,通过反射获得) + +- 例子 + + 注意这个类不让我们new,也没有直接获取方式(压根就没想让我们用): + + ```Java + public final class Unsafe { + + private static native void registerNatives(); + static { + registerNatives(); + sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe"); + } + + private Unsafe() {} + + private static final Unsafe theUnsafe = new Unsafe(); + + @CallerSensitive + public static Unsafe getUnsafe() { + Class caller = Reflection.getCallerClass(); + if (!VM.isSystemDomainLoader(caller.getClassLoader())) + throw new SecurityException("Unsafe"); //不是JDK的类,不让用。 + return theUnsafe; + } + ``` + + 所以我们这里就通过反射给他giao出来: + + ```Java + public static void main(String[] args) throws IllegalAccessException { + Field unsafeField = Unsafe.class.getDeclaredFields()[0]; + unsafeField.setAccessible(true); + Unsafe unsafe = (Unsafe) unsafeField.get(null); + + } + ``` + + 成功拿到Unsafe类之后,我们就可以开始申请堆外内存了,比如我们现在想要申请一个int大小的内存空间,并在此空间中存放一个int类型的数据: + + ```Java + public static void main(String[] args) throws IllegalAccessException { + Field unsafeField = Unsafe.class.getDeclaredFields()[0]; + unsafeField.setAccessible(true); + Unsafe unsafe = (Unsafe) unsafeField.get(null); + + //申请4字节大小的内存空间,并得到对应位置的地址 + long address = unsafe.allocateMemory(4); + //在对应的地址上设定int的值 + unsafe.putInt(address, 6666666); + //获取对应地址上的Int型数值 + System.out.println(unsafe.getInt(address)); + //释放申请到的内容 + unsafe.freeMemory(address); + + //由于内存已经释放,这时数据就没了 + System.out.println(unsafe.getInt(address)); + } + ``` + + 我们可以来看一下`allocateMemory`底层是如何调用的,这是一个native方法,我们来看C++源码: + + ```C++ + UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory0(JNIEnv *env, jobject unsafe, jlong size)) { + size_t sz = (size_t)size; + + sz = align_up(sz, HeapWordSize); + void* x = os::malloc(sz, mtOther); //这里调用了os::malloc方法 + + return addr_to_java(x); + } UNSAFE_END + ``` + + 接着来看: + + ```C++ + void* os::malloc(size_t size, MEMFLAGS flags) { + return os::malloc(size, flags, CALLER_PC); + } + + void* os::malloc(size_t size, MEMFLAGS memflags, const NativeCallStack& stack) { + ... + u_char* ptr; + ptr = (u_char*)::malloc(alloc_size); //调用C++标准库函数 malloc(size) + .... + // we do not track guard memory + return MemTracker::record_malloc((address)ptr, size, memflags, stack, level); + } + ``` + + 所以,我们上面的Java代码转换为C代码,差不多就是这个意思: + + ```C + \#include #include int main(){ + int * a = malloc(sizeof(int)); + *a = 6666666; + printf("%d\n", *a); + free(a); + printf("%d\n", *a); + } + ``` + + + + + +- 所以说,直接内存实际上就是JVM申请的一块额外的内存空间,但是它并不在受管控的几种内存空间中,当然这些**内存依然属于是JVM的**,由于JVM提供的堆内存会进行垃圾回收等工作,效率不如直接申请和操作内存来得快,一些比较追求极致性能的框架会用到堆外内存来提升运行速度,如nio框架 + +--- + +## 垃圾回收机制 + +[[垃圾回收机制]] + +## 元空间 + +- JDK8之前,Hotspot虚拟机的方法区实际上是永久代实现的。在JDK8之后,Hotspot虚拟机不再使用永久代,而是采用了全新的元空间。类的元信息被存储在元空间中。元空间没有使用堆内存,而是与堆不相连的本地内存区域。所以,理论上系统可以使用的内存有多大,元空间就有多大,所以不会出现永久代存在时的内存溢出问题。这项改造也是有必要的,永久代的调优是很困难的,虽然可以设置永久代的大小,但是很难确定一个合适的大小,因为其中的影响因素很多,比如类数量的多少、常量数量的多少等。 + +![image-20230306165703340](JVM中篇/2RD3AnPvbh1lQ5N.webp) + +因此在JDK8时直接将本地内存作为元空间(**Metaspace**)的区域,物理内存有多大,元空间内存就可以有多大,这样永久代的空间分配问题就讲解了,所以最终它变成了这样: + +![image-20230306165714662](JVM中篇/iVcYMU9jdn2NC6Z.webp) + +到此,我们对于JVM内存区域的讲解就基本完成了。 + +## 引用类型 + +### 强引用 + +- 我们知道,在Java中,如果变量是一个对象类型的,那么它实际上存放的是对象的引用,但是如果是一个基本类型,那么存放的就是基本类型的值。实际上我们平时代码中类似于`Object o = new Object()`这样的的引用类型,细分之后可以称为`**强引用**`。 +- 我们通过前面的学习可以明确,如果方法中存在这样的`强引用`类型,现在需要回收强引用所指向的对象,那么要么此==方法运行结束,要么引用连接断开==,否则被引用的对象是无法被判定为可回收的,因为我们说不定后面还要使用它。 +- 所以,当JVM内存空间不足时,JVM宁愿抛出OutOfMemoryError使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。 + +除了强引用之外,Java也为我们提供了三种额外的引用类型。 + +### 软引用 + +- 软引用不像强引用那样不可回收,**当 JVM 认为内存不足时**,会去试图回收软引用指向的对象,即JVM 会确保在抛出 `OutOfMemoryError` 之前,清理软引用指向的对象。当然,如果内存充足,那么是不会轻易被回收的。 + +- 我们可以通过以下方式来创建一个软引用:使用`**SoftReference**`类 + + ```Java + public class Main { + public static void main(String[] args) { + //强引用写法:Object obj = new Object(); + //软引用写法: + SoftReference reference = new SoftReference<>(new Object()); + //使用get方法就可以获取到软引用所指向的对象了 + System.out.println(reference.get()); + } + } + ``` + + 可以看到软引用还存在一个带队列的构造方法,软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。 + + 这里进行一个测试,首先我们需要设定一下参数,来限制最大堆内存为10M,并且打印GC日志: + + ```Plain + -XX:+PrintGCDetails -Xms10M -Xmx10M + ``` + + 接着运行以下代码: + + ```Java + public class Main { + public static void main(String[] args) { + ReferenceQueue queue = new ReferenceQueue<>(); + SoftReference reference = new SoftReference<>(new Object(), queue); + System.out.println(reference); + + try{ + List list = new ArrayList<>(); + while (true) list.add(new String("lbwnb")); + }catch (Throwable t){ + System.out.println("发生了内存溢出!"+t.getMessage()); + System.out.println("软引用对象:"+reference.get()); + System.out.println(queue.poll()); + } + } + } + ``` + + 运行结果如下: + + ```Plain + java.lang.ref.SoftReference@232204a1 + [GC (Allocation Failure) [PSYoungGen: 3943K->501K(4608K)] 3943K->2362K(15872K), 0.0050615 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] + [GC (Allocation Failure) [PSYoungGen: 3714K->496K(4608K)] 5574K->4829K(15872K), 0.0049642 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] + [GC (Allocation Failure) [PSYoungGen: 3318K->512K(4608K)] 7652K->7711K(15872K), 0.0059440 secs] [Times: user=0.03 sys=0.00, real=0.00 secs] + [GC (Allocation Failure) --[PSYoungGen: 4608K->4608K(4608K)] 11807K->15870K(15872K), 0.0078912 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] + [Full GC (Ergonomics) [PSYoungGen: 4608K->0K(4608K)] [ParOldGen: 11262K->10104K(11264K)] 15870K->10104K(15872K), [Metaspace: 3207K->3207K(1056768K)], 0.0587856 secs] [Times: user=0.24 sys=0.00, real=0.06 secs] + [Full GC (Ergonomics) [PSYoungGen: 4096K->1535K(4608K)] [ParOldGen: 10104K->11242K(11264K)] 14200K->12777K(15872K), [Metaspace: 3207K->3207K(1056768K)], 0.0608198 secs] [Times: user=0.25 sys=0.01, real=0.06 secs] + [Full GC (Ergonomics) [PSYoungGen: 3965K->3896K(4608K)] [ParOldGen: 11242K->11242K(11264K)] 15207K->15138K(15872K), [Metaspace: 3207K->3207K(1056768K)], 0.0972088 secs] [Times: user=0.58 sys=0.00, real=0.10 secs] + [Full GC (Allocation Failure) [PSYoungGen: 3896K->3896K(4608K)] [ParOldGen: 11242K->11225K(11264K)] 15138K->15121K(15872K), [Metaspace: 3207K->3207K(1056768K)], 0.1028222 secs] [Times: user=0.63 sys=0.01, real=0.10 secs] + 发生了内存溢出!Java heap space + 软引用对象:null + java.lang.ref.SoftReference@232204a1 + Heap + PSYoungGen total 4608K, used 4048K [0x00000007bfb00000, 0x00000007c0000000, 0x00000007c0000000) + eden space 4096K, 98% used [0x00000007bfb00000,0x00000007bfef40a8,0x00000007bff00000) + from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000) + to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) + ParOldGen total 11264K, used 11225K [0x00000007bf000000, 0x00000007bfb00000, 0x00000007bfb00000) + object space 11264K, 99% used [0x00000007bf000000,0x00000007bfaf64a8,0x00000007bfb00000) + Metaspace used 3216K, capacity 4500K, committed 4864K, reserved 1056768K + class space used 352K, capacity 388K, committed 512K, reserved 1048576K + ``` + + 可以看到,当内存不足时,软引用所指向的对象被回收了,所以`get()`方法得到的结果为null,并且软引用对象本身被丢进了队列中。 + + + + +### 弱引用 + +- 弱引用比软引用的生命周期还要短,**在进行垃圾回收时**,不管当前内存空间是否充足,都会回收它的内存。 + +- 我们可以像这样创建一个弱引用:使用`**WeakReference**`类 + + ```Java + public class Main { + public static void main(String[] args) { + WeakReference reference = new WeakReference<>(new Object()); + System.out.println(reference.get()); + } + } + ``` + + 使用方法和软引用是差不多的,但是如果我们在这之前手动进行一次GC: + + ```Java + public class Main { + public static void main(String[] args) { + SoftReference softReference = new SoftReference<>(new Object()); + WeakReference weakReference = new WeakReference<>(new Object()); + + //手动GC + System.gc(); + + System.out.println("软引用对象:"+softReference.get()); + System.out.println("弱引用对象:"+weakReference.get()); + } + } + ``` + + 可以看到,弱引用对象直接就被回收了,而软引用对象没有被回收。同样的,它也支持ReferenceQueue,和软引用用法一致,这里就不多做介绍了。 + + `WeakHashMap`正是一种类似于弱引用的HashMap类,如果Map中的Key没有其他引用那么此Map会自动丢弃此键值对。 + + ```Java + public class Main { + public static void main(String[] args) { + Integer a = new Integer(1); + + WeakHashMap weakHashMap = new WeakHashMap<>(); + weakHashMap.put(a, "yyds"); + System.out.println(weakHashMap); + + a = null; + System.gc(); + + System.out.println(weakHashMap); + } + } + ``` + + 可以看到,**当变量a的引用断开后**,这时只有WeakHashMap本身对此对象存在引用,所以在GC之后,这个键值对就自动被舍弃了。所以说这玩意,就挺适合拿去做缓存的。 + + + + +### 虚引用(鬼引用) + +- 虚引用相当于没有引用,随时都有可能会被回收。 + +- 看看它的源码,非常简单:`**PhantomReference**` + + ```Java + public class PhantomReference extends Reference { + + /** + * Returns this reference object's referent. Because the referent of a + * phantom reference is always inaccessible, this method always returns + * null. + * + * @return null + */ + public T get() { + return null; + } + + /** + * Creates a new phantom reference that refers to the given object and + * is registered with the given queue. + * + *

It is possible to create a phantom reference with a null + * queue, but such a reference is completely useless: Its get + * method will always return null and, since it does not have a queue, it + * will never be enqueued. + * + * @param referent the object the new phantom reference will refer to + * @param q the queue with which the reference is to be registered, + * or null if registration is not required + */ + public PhantomReference(T referent, ReferenceQueue q) { + super(referent, q); + } + + } + ``` + + 也就是说我们无论调用多少次`get()`方法得到的永远都是`null`,因为虚引用本身就不算是个引用,相当于这个对象不存在任何引用,并且只能使用带队列的构造方法,以便对象被回收时接到通知 + + +最后,Java中4种引用的级别由高到低依次为: **强引用 > 软引用 > 弱引用 > 虚引用** \ No newline at end of file diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/2RD3AnPvbh1lQ5N.webp b/posts/blog/编程技术/java/JVM/JVM中篇/2RD3AnPvbh1lQ5N.webp new file mode 100644 index 0000000..df55857 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/2RD3AnPvbh1lQ5N.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/2fXENphit4OZvVk.webp b/posts/blog/编程技术/java/JVM/JVM中篇/2fXENphit4OZvVk.webp new file mode 100644 index 0000000..86880c9 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/2fXENphit4OZvVk.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/As1NGy6BwhKJ9kY.webp b/posts/blog/编程技术/java/JVM/JVM中篇/As1NGy6BwhKJ9kY.webp new file mode 100644 index 0000000..c693fcf Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/As1NGy6BwhKJ9kY.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/C71bdyIY2JVpLKz.webp b/posts/blog/编程技术/java/JVM/JVM中篇/C71bdyIY2JVpLKz.webp new file mode 100644 index 0000000..ef57f11 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/C71bdyIY2JVpLKz.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/CP4yv1iqrfjmXzW.jpg b/posts/blog/编程技术/java/JVM/JVM中篇/CP4yv1iqrfjmXzW.jpg new file mode 100644 index 0000000..ffb98f6 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/CP4yv1iqrfjmXzW.jpg differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/EOC5ilkr396BHNy.webp b/posts/blog/编程技术/java/JVM/JVM中篇/EOC5ilkr396BHNy.webp new file mode 100644 index 0000000..2b7d391 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/EOC5ilkr396BHNy.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/FBpxKWIbuY7ftq4.webp b/posts/blog/编程技术/java/JVM/JVM中篇/FBpxKWIbuY7ftq4.webp new file mode 100644 index 0000000..e3f5c19 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/FBpxKWIbuY7ftq4.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/GTgbpIYdCyK3RLj.webp b/posts/blog/编程技术/java/JVM/JVM中篇/GTgbpIYdCyK3RLj.webp new file mode 100644 index 0000000..114643c Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/GTgbpIYdCyK3RLj.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/SZJWH8l5xByTjr7.webp b/posts/blog/编程技术/java/JVM/JVM中篇/SZJWH8l5xByTjr7.webp new file mode 100644 index 0000000..cd36c9f Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/SZJWH8l5xByTjr7.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/iVcYMU9jdn2NC6Z.webp b/posts/blog/编程技术/java/JVM/JVM中篇/iVcYMU9jdn2NC6Z.webp new file mode 100644 index 0000000..bf16fde Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/iVcYMU9jdn2NC6Z.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/image131.png b/posts/blog/编程技术/java/JVM/JVM中篇/image131.png new file mode 100644 index 0000000..e0126ad Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/image131.png differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/image53.png b/posts/blog/编程技术/java/JVM/JVM中篇/image53.png new file mode 100644 index 0000000..00b44c0 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/image53.png differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/r5IsmTk3DZfXA26.webp b/posts/blog/编程技术/java/JVM/JVM中篇/r5IsmTk3DZfXA26.webp new file mode 100644 index 0000000..658e0a1 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/r5IsmTk3DZfXA26.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/rRtxbFZXDkmGci4.webp b/posts/blog/编程技术/java/JVM/JVM中篇/rRtxbFZXDkmGci4.webp new file mode 100644 index 0000000..7fb680a Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/rRtxbFZXDkmGci4.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM中篇/tnqFSxzEcB4U9WL.webp b/posts/blog/编程技术/java/JVM/JVM中篇/tnqFSxzEcB4U9WL.webp new file mode 100644 index 0000000..6e94676 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM中篇/tnqFSxzEcB4U9WL.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM前篇.md b/posts/blog/编程技术/java/JVM/JVM前篇.md new file mode 100644 index 0000000..9673913 --- /dev/null +++ b/posts/blog/编程技术/java/JVM/JVM前篇.md @@ -0,0 +1,154 @@ +--- +title: JVM前篇 +date: 2024-08-25 +tags: [JVM] +--- + + +## 前言 + +jvm规范 + +![image 52.png](JVM前篇/image52.png) + +## 概述 + +首先我们要了解虚拟机的具体定义,我们所接触过的虚拟机有安装操作系统的虚拟机,也有我们的Java虚拟机,而它们所面向的对象不同,Java虚拟机只是面向单一应用程序的虚拟机,但是它和我们接触的系统级虚拟机一样,我们也可以为其分配实际的硬件资源,比如最大内存大小等。 + + + +并且Java虚拟机并没有采用传统的PC架构,比如现在的**HotSpot虚拟机**,实际上采用的是`**基于栈的指令集架构**`,而我们的传统程序设计一般都是`基于``**寄存器**``的指令集架构`,这里我们需要回顾一下`计算机组成原理`中的CPU结构: + +![image 1 30.png](JVM前篇/image130.png) + +> 省略了C语言在不同架构下编译出的汇编程序 + +C语言在不同的CPU架构下,实际上得到的汇编代码也不一样,并且在arm架构下并没有和x86架构一样的寄存器结构,因此只能使用不同的汇编指令操作来实现。所以这也是为什么C语言不支持跨平台的原因,依赖于硬件的支持。 + +Java利用了JVM,它提供了很好的平台无关性(当然,JVM本身是不跨平台的),我们的Java程序编译之后,并不是可以由平台直接运行的程序,而是由JVM运行,同时,我们前面说了,JVM(如HotSpot虚拟机),实际上采用的是`基于栈的指令集架构`,它并没有依赖于寄存器,而是更多的利用操作栈来完成,这样不仅设计和实现起来更简单,并且也能够更加方便地实现跨平台,不太依赖于硬件的支持。 + +> 省略分析字节码(class)文件分析 + +实际上我们发现,JVM执行的命令基本都是**入栈出栈**等,而且大部分指令都是没有==操作数(字面量)==的,传统的汇编指令有一操作数、二操作数甚至三操作数的指令,Java相比C编译出来的汇编指令,执行起来会更加复杂,实现某个功能的指令条数也会更多,所以Java的执行效率实际上是不如C/C++的,虽然能够很方便地实现跨平台,但是性能上大打折扣,所以在性能要求比较苛刻的Android上,采用的是**定制版的JVM**,并且是==基于寄存器的指令集架构==。此外,在某些情况下,我们还可以使用JNI机制来通过Java调用C/C++编写的程序以提升性能(也就是本地方法,使用到native关键字) + +## jvm历史 + +JVM会根据当前代码的进行判断,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler) + +![image 2 20.png](JVM前篇/image220.png) + +## JVM启动流程 + +![image 3 17.png](JVM前篇/image317.png) + +## JNI调用本地方法 + +Java还有一个**JNI**机制,它的全称:==Java Native Interface==,即Java本地接口。它允许在Java虚拟机内运行的Java代码与其他编程语言(如C/C++和汇编语言)编写的程序和库进行交互(在Android开发中用得比较多)比如我们现在想要让C语言程序帮助我们的Java程序实现a+b的运算,首先我们需要创建一个本地方法: + +```Java +public class Main { + public static void main(String[] args) { + System.out.println(sum(1, 2)); + } + //本地方法使用native关键字标记,无需任何实现,交给C语言实现 + public static native int sum(int a, int b); +} +``` + +创建好后,接着点击构建(编译)按钮,会出现一个out文件夹,也就是生成的class文件在其中,接着我们直接生成对应的C头文件: + +```Shell +javah -classpath out/production/SimpleHelloWorld -d ./jni com.test.Main +``` + +生成的头文件位于jni文件夹下: + +```C +/* DO NOT EDIT THIS FILE - it is machine generated */ +\#include /* Header for class com_test_Main */ + +\#ifndef _Included_com_test_Main +\#define _Included_com_test_Main +\#ifdef __cplusplus +extern "C" { +\#endif/* + * Class: com_test_Main + * Method: sum + * Signature: (II)V + */ +JNIEXPORT void JNICALL Java_com_test_Main_sum + (JNIEnv *, jclass, jint, jint); + +\#ifdef __cplusplus +} +\#endif#endif +``` + +接着我们在CLion中新建一个C++项目,并引入刚刚生成的头文件,并导入jni相关头文件(在JDK文件夹中)首先修改CMake文件: + +```Plain +cmake_minimum_required(VERSION 3.21) +project(JNITest) + +include_directories(/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/include) +include_directories(/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/include/darwin) +set(CMAKE_CXX_STANDARD 14) + +add_executable(JNITest com_test_Main.cpp com_test_Main.h) +``` + +接着就可以编写实现了,首先认识一下引用类型对照表: + +![image 4 15.png](JVM前篇/image415.png) + +所以我们这里直接返回a+b即可: + +```C++ +\#include "com_test_Main.h"JNIEXPORT jint JNICALL Java_com_test_Main_sum + (JNIEnv * env, jclass clazz, jint a, jint b){ + return a + b; +} +``` + +接着我们就可以将cpp编译为动态链接库,在MacOS下会生成`.dylib`文件,Windows下会生成`.dll`文件,我们这里就只以MacOS为例,命令有点长,因为还需要包含JDK目录下的头文件: + +```Shell +gcc com_test_Main.cpp -I /Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/include -I /Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/include/darwin -fPIC -shared -o test.dylib -lstdc++ +``` + +编译完成后,得到`test.dylib`文件,这就是动态链接库了。 + +最后我们再将其放到桌面,然后在Java程序中加载: + +```Java +public class Main { + static { + System.load("/Users/nagocoler/Desktop/test.dylib"); + } + + public static void main(String[] args) { + System.out.println(sum(1, 2)); + } + + public static native int sum(int a, int b); +} +``` + +运行,成功得到结果: + +```shell +/home/nagocoler/jdk-jdk8-b120/build/linux-x86_64-normal-server-slowdebug/jdk/bin/java Main +Hello World! + +Process finished with exit code 0 +``` + +### 总结 + +- 在Java中编写方法接口(即native修饰的方法) +- 编译生成class文件,根据clas文件生成JNI头文件(C头文件),其中包含你在C代码中需要实现的函数声明(生成一个c头文件,头文件说明了需要的函数) +- 创建C项目,引入生成的头文件,编写需要实现的函数 +- 使用==CMake==将C编译成动态链接共享库 +- 运行Java程序(需要调用系统类对共享库进行加载) + +通过了解JVM的一些基础知识,我们心目中大致有了一个JVM的模型,在下一章,我们将继续深入学习JVM的内存管理机制和垃圾收集器机制,以及一些实用工具。 \ No newline at end of file diff --git a/posts/blog/编程技术/java/JVM/JVM前篇/image130.png b/posts/blog/编程技术/java/JVM/JVM前篇/image130.png new file mode 100644 index 0000000..3a3a2ef Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM前篇/image130.png differ diff --git a/posts/blog/编程技术/java/JVM/JVM前篇/image220.png b/posts/blog/编程技术/java/JVM/JVM前篇/image220.png new file mode 100644 index 0000000..e172d56 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM前篇/image220.png differ diff --git a/posts/blog/编程技术/java/JVM/JVM前篇/image317.png b/posts/blog/编程技术/java/JVM/JVM前篇/image317.png new file mode 100644 index 0000000..d276db1 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM前篇/image317.png differ diff --git a/posts/blog/编程技术/java/JVM/JVM前篇/image415.png b/posts/blog/编程技术/java/JVM/JVM前篇/image415.png new file mode 100644 index 0000000..45206cb Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM前篇/image415.png differ diff --git a/posts/blog/编程技术/java/JVM/JVM前篇/image52.png b/posts/blog/编程技术/java/JVM/JVM前篇/image52.png new file mode 100644 index 0000000..4ea439b Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM前篇/image52.png differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇.md b/posts/blog/编程技术/java/JVM/JVM后篇.md new file mode 100644 index 0000000..91e844b --- /dev/null +++ b/posts/blog/编程技术/java/JVM/JVM后篇.md @@ -0,0 +1,837 @@ +--- +title: JVM后篇 +date: 2024-08-25 +tags: [JVM] +--- + + +# 类与类加载 + +## 类文件结构 + +- 概述 + + 在我们学习C语言的时候,我们的编程过程会经历如下几个阶段:写代码、保存、编译、运行。实际上,最关键的一步是**编译**,因为只有经历了编译之后,我们所编写的代码才能够翻译为机器可以直接运行的二进制代码,并且在不同的操作系统下,我们的代码都需要进行一次编译之后才能运行。 + + > 如果全世界所有的计算机指令集只有x86一种,操作系统只有Windows一种,那也许就不会有Java语言的出现。 + + 随着时代的发展,人们迫切希望能够在不同的操作系统、不同的计算机架构中运行同一套编译之后的代码。本地代码不应该是我们编程的唯一选择,所以,越来越多的语言选择了与操作系统和机器指令集无关的中立格式作为编译后的存储格式。 + + “一次编写,到处运行”,Java最引以为傲的口号,标志着平台不再是限制编程语言的阻碍。 + + 实际上,Java正式利用了这样的解决方案,将源代码编译为平台无关的中间格式,并通过对应的Java虚拟机读取和运行这些中间格式的编译文件,这样,我们只需要考虑不同平台的虚拟机如何编写,而Java语言本身很轻松地实现了跨平台。 + + +现在,越来越多的开发语言都支持将源代码编译为`.class`字节码文件格式,以便能够直接交给JVM运行,包括Kotlin(安卓开发官方指定语言)、Groovy、Scala等。 + +![image-20230306165746693](JVM后篇/u2K8Y5yU1zf9LQ4.webp) + +那么,让我们来看看,我们的源代码编译之后,是如何保存在字节码文件中的。 + +--- + +### 类文件信息 + +- 查看class文件,进行解析 + + 我们之前都是使用`javap`命令来对字节码文件进行反编译查看的,那么,它以二进制格式是怎么保存呢?我们可以使用WinHex软件(Mac平台可以使用[010 Editor](https://www.macwk.com/soft/010-editor))来以十六进制查看字节码文件。 + + ```Java + public class Main { + public static void main(String[] args) { + int i = 10; + int a = i++; + int b = ++i; + } + } + ``` + + 找到我们在IDEA中编译出来的class文件,将其拖动进去: + + ![image-20230306165815432](JVM后篇/QOvi5YpnaHTxrVU.webp) + + 可以看到整个文件中,全是一个字节一个字节分组的样子,从左上角开始,一行一行向下读取。可以看到在右侧中还出现了一些我们之前也许见过的字符串,比如""、"Object"等。 + + 实际上Class文件采用了一种类似于C中结构体的伪结构来存储数据(当然我们直接看是看不出来的),但是如果像这样呢? + + ```Plain + Classfile /Users/nagocoler/Develop.localized/JavaHelloWorld/target/classes/com/test/Main.class + Last modified 2022-2-23; size 444 bytes + MD5 checksum 8af3e63f57bcb5e3d0eec4b0468de35b + Compiled from "Main.java" + public class com.test.Main + minor version: 0 + major version: 52 + flags: ACC_PUBLIC, ACC_SUPER + Constant pool: + #1 = Methodref #3.#21 // java/lang/Object."":()V + #2 = Class #22 // com/test/Main + #3 = Class #23 // java/lang/Object + #4 = Utf8 + #5 = Utf8 ()V + #6 = Utf8 Code + #7 = Utf8 LineNumberTable + #8 = Utf8 LocalVariableTable + #9 = Utf8 this + #10 = Utf8 Lcom/test/Main; + #11 = Utf8 main + #12 = Utf8 ([Ljava/lang/String;)V + #13 = Utf8 args + #14 = Utf8 [Ljava/lang/String; + #15 = Utf8 i + #16 = Utf8 I + #17 = Utf8 a + #18 = Utf8 b + #19 = Utf8 SourceFile + #20 = Utf8 Main.java + #21 = NameAndType #4:#5 // "":()V + #22 = Utf8 com/test/Main + #23 = Utf8 java/lang/Object + { + public com.test.Main(); + descriptor: ()V + flags: ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + 0: aload_0 + 1: invokespecial #1 // Method java/lang/Object."":()V + 4: return + LineNumberTable: + line 11: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/test/Main; + + public static void main(java.lang.String[]); + descriptor: ([Ljava/lang/String;)V + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=1, locals=4, args_size=1 + 0: bipush 10 + 2: istore_1 + 3: iload_1 + 4: iinc 1, 1 + 7: istore_2 + 8: iinc 1, 1 + 11: iload_1 + 12: istore_3 + 13: return + LineNumberTable: + line 13: 0 + line 14: 3 + line 15: 8 + line 16: 13 + LocalVariableTable: + Start Length Slot Name Signature + 0 14 0 args [Ljava/lang/String; + 3 11 1 i I + 8 6 2 a I + 13 1 3 b I + } + SourceFile: "Main.java" + ``` + + 乍一看,是不是感觉还真的有点结构体那味? + + 而结构体中,有两种允许存在的数据类型,一个是无符号数,还有一个是表。 + + - 无符号数一般是基本数据类型,==用u1、u2、u4、u8来表示,表示1个字节~8个字节的无符号数==。可以表示数字、索引引用、数量值或是以UTF-8编码格式的字符串。 + - 表包含多个无符号数,并且以"_info"结尾。 + + 我们首先从最简的开始看起。 + + ![image-20230306165846303](JVM后篇/4nFVBxOIo6QKjHA.webp) + + - 首先,我们可以看到,==前4个字节(共32位)组成了魔数==(其实就是表示这个文件是一个JVM可以运行的字节码文件,除了Java以外,其他某些文件中也采用了这种魔数机制来进行区分,这种方式比直接起个文件扩展名更安全) + - 字节码文件的魔数为:==**CAFEBABE**==(这名字能想出来也是挺难的了,毕竟4个bit位只能表示出A-F这几个字母) + - 紧接着魔数的==后面4个字节存储的是字节码文件的版本号==,注意前两个是次要版本号(现在基本都不用了,都是直接Java8、Java9这样命名了),后面两个是主要版本号,这里我们主要看主版本号,比如上面的就是34,注意这是以16进制表示的,我们把它换算为10进制后,得到的结果为:`34 -> 3*16 + 4 = 52`,其中`52`代表的是`JDK8`编译的字节码文件(51是JDK7、50是JDK6、53是JDK9,以此类推) + - JVM会根据版本号决定是否能够运行,比如JDK6只能支持版本号为1.1~6的版本,也就是说必须是Java6之前的环境编译出来的字节码文件,否则无法运行。又比如我们现在安装的是JDK8版本,它能够支持的版本号为1.1~8,那么如果这时我们有一个通过Java7编译出来的字节码文件,依然是可以运行的,所以说Java版本是向下兼容的。 + - 紧接着,就是类的常量池了,这里面存放了类中所有的常量信息(注意这里的常量并不是指我们手动创建的final类型常量,而是程序运行一些需要用到的常量数据,比如==字面量和符号引用等==)由于常量的数量不是确定的,所以在最开始的位置会存放常量池中常量的数量(是从1开始计算的,不是0,比如这里是18,翻译为10进制就是24,所以实际上有23个常量) + + 接着再往下,就是常量池里面的数据了,每一项常量池里面的数据都是一个表,我们可以看到他们都是以_info结尾的: + + ![image-20230306165906177](JVM后篇/i857GLYJ1fSIKz4.webp) + + 我们来看看一个表中定义了哪些内容: + + ![image-20230306165923282](JVM后篇/tizymh9BjYAFp3x.webp) + + 首先上来就会有一个1字节的无符号数,它用于表示当前常量的类型(常量类型有很多个)这里只列举一部分的类型介绍: + + | | | | + |---|---|---| + |类型|标志|描述| + |CONSTANT_Utf8_info|1|UTF-8编码格式的字符串| + |CONSTANT_Integer_info|3|整形字面量(第一章我们演示的很大的数字,实际上就是以字面量存储在常量池中的)| + |CONSTANT_Class_info|7|类或接口的符号引用| + |CONSTANT_String_info|8|字符串类型的字面量| + |CONSTANT_Fieldref_info|9|字段的符号引用| + |CONSTANT_Methodref_info|10|方法的符号引用| + |CONSTANT_MethodType_info|16|方法类型| + |CONSTANT_NameAndType_info|12|字段或方法的部分符号引用| + + 实际上这些东西,虽然我们不知道符号引用是什么东西,我们可以观察出来,这些东西或多或少都是存放类中一些名称、数据之类的东西。 + + 比如我们来看第一个`CONSTANT_Methodref_info`表中存放了什么数据,这里我只列出它的结构表(详细的结构表可以查阅《深入理解Java虚拟机 第三版》中222页总表): + + | | | | | + |---|---|---|---| + |常量|项目|类型|描述| + |CONSTANT_Methodref_info|tag|u1|值为10| + ||index|u2|指向声明方法的类描述父CONSTANT_Class_info索引项| + ||index|u2|指向名称及类型描述符CONSTANT_NameAndType_info索引项| + + 比如我们刚刚的例子中: + + ![image-20230306165936467](JVM后篇/WORY4BSMzhcQNyt.webp) + + 可以看到,第一个索引项指向了第3号常量,我们来看看三号常量: + + ![image-20230306165947568](JVM后篇/AbpBRLIwczTyqMP.webp) + + | 常量 | 项目 | 类型 | 描述 | + | ------------------- | ----- | ---- | ------------------------ | + | CONSTANT_Class_info | tag | u1 | 值为7 | + | | index | u2 | 指向全限定名常量项的索引 | + + 那么我们接着来看23号常量又写的啥: + + ![image-20230306170009857](JVM后篇/oVg2cwD4Gjrfab7.webp) + + 可以看到指向的UTF-8字符串值为`java/lang/Object`这下搞明白了,首先这个方法是由Object类定义的,那么接着我们来看第二项u2 `name_and_type_index`,指向了21号常量,也就是字段或方法的部分符号引用: + + ![image-20230306170021693](JVM后篇/2hbcO5flxzodN9J.webp) + + |常量|项目|类型|描述| + |---|---|---|---| + |CONSTANT_NameAndType_info|tag|u1|值为12| + ||index|u2|指向字段或方法名称常量项的索引| + ||index|u2|指向字段或方法描述符常量项的索引| + + 其中第一个索引就是方法的名称,而第二个就是方法的描述符,描述符明确了方法的参数以及返回值类型,我们分别来看看4号和5号常量: + + ![image-20230306170037268](JVM后篇/aB8fu1GUNwYMPLh.webp) + + 可以看到,方法名称为"",一般构造方法的名称都是,普通方法名称是什么就是什么,方法描述符为"()V",表示此方法没有任何参数,并且返回值类型为void,描述符对照表如下: + + ![image-20230306170051403](JVM后篇/V1RyLUTBgfFMk69.webp) + + 比如这里有一个方法`public int test(double a, char c){ ... }`,那么它的描述符就应该是:`(DC)I`,参数依次放入括号中,括号右边是返回值类型。再比如`public String test(Object obj){ ... }`,那么它的描述符就应该是:`(Ljava/lang/Object;)Ljava/lang/String`,注意如果参数是对象类型,那么必须在后面添加`;` + + 对于数组类型,只需要在类型最前面加上`[`即可,有几个维度,就加几个,比如`public void test(int[][] arr)`,参数是一个二维int类型数组,那么它的描述符为:`([[I)V` + + 所以,这里表示的,实际上就是此方法是一个无参构造方法,并且是属于Object类的。那么,为什么这里需要Object类构造方法的符号引用呢?还记得我们在JavaSE中说到的,每个类都是直接或间接继承自Object类,所有类的构造方法,必须先调用父类的构造方法,但是如果父类存在无参构造,默认可以不用显示调用`super`关键字(当然本质上是调用了的)。 + + 所以说,当前类因为没有继承自任何其他类,那么就默认继承的Object类,所以,在当前类的默认构造方法中,调用了父类Object类的无参构造方法,因此这里需要符号引用的用途显而易见,就是因为需要调用Object类的无参构造方法。 + + 我们可以在反编译结果中的方法中看到: + + ```Plain + public com.test.Main(); + descriptor: ()V + flags: ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + 0: aload_0 + 1: invokespecial #1 // Method java/lang/Object."":()V + 4: return + LineNumberTable: + line 11: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/test/Main; + ``` + + 其中`invokespecial`(调用父类构造方法)指令的参数指向了1号常量,而1号常量正是代表的Object类的无参构造方法,虽然饶了这么大一圈,但是过程理清楚,还是很简单的。 + + 虽然我们可以直接查看16进制的结果,但是还是不够方便,但是我们也不能每次都去使用`javap`命令,所以我们这里安装一个IDEA插件,来方便我们查看字节码中的信息,名称为`jclasslib Bytecode Viewer` : + + ![image-20230306170130977](JVM后篇/28BVwcadNEjCnLi.webp) + + 安装完成后,我们可以在我们的IDEA右侧看到它的板块,但是还没任何数据,那么比如现在我们想要查看Main类的字节码文件时,可以这样操作: + + ![image-20230306170141372](JVM后篇/3TvqOXGsDIPbzlh.webp) + + 首先在项目中选中我们的Main类,然后点击工具栏的视图,然后点击`Show Bytecode With Jclasslib`,这样右侧就会出现当前类的字节码解析信息了。注意如果修改了类的话,那么需要你点击运行或是构建,然后点击刷新按钮来进行更新。 + + 接着我们来看下一个内容,在常量池之后,紧接着就是访问标志,访问标志就是类的种类以及类上添加的一些关键字等内容: + + ![image-20230306170153604](JVM后篇/HE7QJpfbh6sOLji.webp) + + 可以看到它只占了2个字节,那么它是如何表示访问标志呢? + + ![image-20230306170207034](JVM后篇/lY6sJwicX5d2FVg.webp) + + 比如我们这里的Main类,它是一个普通的class类型,并且访问权限为public,那么它的访问标志值是这样计算的: + + `ACC_PUBLIC | ACC_SUPER = 0x0001 | 0x0020 = 0x0021`(这里进行的是按位或运算),可以看到和我们上面的结果是一致的。 + + 再往下就是类索引、父类索引、接口索引: + + ![image-20230306170218340](JVM后篇/WskFX26NDco8Jgv.webp) + + 可以看到它们的值也是指向常量池中的值,其中2号常量正是存储的当前类信息,3号常量存储的是父类信息,这里就不再倒推回去了,由于没有接口,所以这里接口数量为0,如果不为0还会有一个索引表来引用接口。 + + 接着就是字段和方法表集合了: + + ![image-20230306170228985](JVM后篇/p6wAqsZneuI8Wo3.webp) + + 由于我们这里没有声明任何字段,所以我们先给Main类添加一个字段再重新加载一下: + + ```Java + public class Main { + + public static int a = 10; + + public static void main(String[] args) { + int i = 10; + int a = i++; + int b = ++i; + } + } + ``` + + ![image-20230306170307227](JVM后篇/dzmcQUyFrH9kTWZ.webp) + + 现在字节码就新增了一个字段表,这个字段表实际上就是我们刚刚添加的成员字段`a`的数据。 + + 可以看到一共有四个2字节的数据: + + ![image-20230306170316904](JVM后篇/Phy8UoMGiLkpdsf.webp) + + 首先是`access_flags`,这个与上面类标志的计算规则是一样的,表还是先列出来吧: + + ![image-20230306170332081](JVM后篇/uCrZ8aG2EoxLM5c.webp) + + 第二个数据`name_index`表示字段的名称常量,这里指向的是5号常量,那么我们来看看5号常量是不是字段名称: + + ![image-20230306170345473](JVM后篇/LOycXsCrm4vAjDk.webp) + + 没问题,这里就是`a`,下一个是`descirptor_index`,存放的是描述符,不过这里因为不是方法而是变量,所以描述符直接写对应类型的标识字符即可,比如这里是`int`类型,那么就是`I`。 + + 最后,`attrbutes_count`属性计数器,用于描述一些额外信息,这里我们暂时不做介绍。 + + 接着就是我们的方法表了: + + ![image-20230306170359554](JVM后篇/O4XnFbfRShkc8WZ.webp) + + 可以看到方法表中一共有三个方法,其中第一个方法我们刚刚已经介绍过了,它的方法名称为``,表示它是一个构造方法,我们看到最后一个方法名称为``,这个是类在初始化时会调用的方法(是隐式的,自动生成的),它主要是用于静态变量初始化语句和静态块的执行,因为我们这里给静态成员变量a赋值为10,所以会在一开始为其赋值: + + ![image-20230306170411225](JVM后篇/zkJVicf764bFPXd.webp) + + 而第二个方法,就是我们的`main`方法了,但是现在我们先不急着去看它的详细实现过程,我们来看看它的属性表。 + + 属性表实际上类中、字段中、方法中都可以携带自己的属性表,属性表存放的正是我们的代码、本地变量等数据,比如main方法就存在4个本地变量,那么它的本地变量存放在哪里呢: + + ![image-20230306170426154](JVM后篇/dPoWa74AeYJcIG1.webp) + + 可以看到,属性信息呈现套娃状态,在此方法中的属性包括了一个Code属性,此属性正是我们的Java代码编译之后变成字节码指令,然后存放的地方,而在此属性中,又嵌套了本地变量表和源码行号表。 + + 可以看到code中存放的就是所有的字节码指令: + + ![image-20230306170436483](JVM后篇/c7ebHq3TBxudK1A.webp) + + 这里我们暂时不对字节码指令进行讲解(其实也用不着讲了,都认识的差不多了)。我们接着来看本地变量表,这里存放了我们方法中要用到的局部变量: + + ![image-20230306170452941](JVM后篇/heoLwXRVpuBYbZn.webp) + + 可以看到一共有四个本地变量,而第一个变量正是main方法的形参`String[] args`,并且表中存放了本地变量的长度、名称、描述符等内容。当然,除了我们刚刚认识的这几个属性之外,完整属性可以查阅《深入理解Java虚拟机 第三版》231页。 + + 最后,类也有一些属性: + + ![image-20230306170510722](JVM后篇/fDTVYpAlaBF6j91.webp) + + 此属性记录的是源文件名称。 + + 这样,我们对一个字节码文件的认识差不多就结束了,在了解了字节码文件的结构之后,是不是感觉豁然开朗? + + + + +### 字节码指令 + +- 查看class文件,分析指令 + + - 虚拟机的指令是由一个字节长度的、代表某种特定操作含义的数字(操作码,类似于机器语言),操作后面也可以携带0个或多个参数一起执行。我们前面已经介绍过了,JVM实际上并不是面向寄存器架构的,而是面向操作数栈,所以大多数指令都是不带参数的。 + + 由于之前已经讲解过大致运行流程,这里我们就以当前的Main类中的main方法作为教材进行讲解: + + ```Java + public static void main(String[] args) { + int i = 10; + int a = i++; + int b = ++i; + } + ``` + + 可以看到,main方法中首先是定义了一个int类型的变量i,并赋值为10,然后变量a接收`i++`的值,变量b接收`++i`的值。 + + 那么我们来看看编译成字节码之后,是什么样的: + + ![image-20230306170531318](JVM后篇/1wOpRXoQklGN9Ez.webp) + + - 首先第一句,`bipush`,将10送至操作数栈顶。 + - 接下来将操作数栈顶的数值存进1号本地变量,也就是变量i中。 + - 接着将变量i中的值又丢向操作数栈顶 + - 这里使用`iinc`指令,将1号本地变量的值增加1(结束之后i的值就是11了) + - 接着将操作数栈顶的值(操作数栈顶的值是10)存入2号本地变量(这下彻底知道i++到底干了啥才会先返回后自增了吧,从原理角度来说,实际上i是先自增了的,但由于这里取的是操作数栈中的值,所以说就得到了i之前的值) + - 接着往下,我们看到++i是先直接将i的值自增1 + - 然后在将其值推向操作数栈顶 + + ![image-20230306170553756](JVM后篇/kOPlftdWADqyoHL.webp) + + 而从结果来看,`i++`操作确实是先返回再自增的,而字节码指令层面来说,却是截然相反的,只是结果一致罢了。 + +### ASM字节码编程 + +- 使用ASM框架(Java类)编写Class文件 + + - 既然字节码文件结构如此清晰,那么我们能否通过编程,来直接创建一个字节码文件呢?如果我们可以直接编写一个字节码文件,那么我们就可以省去编译的过程。ASM(某些JDK中内置)框架正是用于支持字节码编程的框架。 + + 比如现在我们需要创建一个普通的Main类(暂时不写任何内容) + + 首先我们来看看如何通过编程创建一个Main类的字节码文件: + + ```Java + public class Main { + public static void main(String[] args) { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + } + } + ``` + + 首先需要获取`ClassWriter`对象,我们可以使用它来编辑类的字节码文件,在构造时需要传入参数: + + - 0 这种方式不会自动计算操作数栈和局部临时变量表大小,需要自己手动来指定 + - ClassWriter.COMPUTE_MAXS(1) 这种方式会自动计算上述操作数栈和局部临时变量表大小,但需要手动触发。 + - ClassWriter.COMPUTE_FRAMES(2) 这种方式不仅会计算上述操作数栈和局部临时变量表大小,而且会自动计算StackMapFrames + + 这里我们使用`ClassWriter.COMPUTE_MAXS`即可。 + + 接着我们首先需要指定类的一些基本信息: + + ```Java + public class Main { + public static void main(String[] args) { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + //因为这里用到的常量比较多,所以说直接一次性静态导入:import static jdk.internal.org.objectweb.asm.Opcodes.*; + writer.visit(V1_8, ACC_PUBLIC,"com/test/Main", null, "java/lang/Object",null); + } + } + ``` + + 这里我们将字节码文件的版本设定位Java8,然后修饰符设定为`ACC_PUBLIC`代表`public class Main`,类名称注意要携带包名,标签设置为`null`,父类设定为Object类,然后没有实现任何接口,所以说最后一个参数也是`null`。 + + 接着,一个简答的类字节码文件就创建好了,我们可以尝试将其进行保存 + + ```Java + public class Main { + public static void main(String[] args) { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + writer.visit(V1_8, ACC_PUBLIC,"com/test/Main", null, "java/lang/Object",null); + //调用visitEnd表示结束编辑 + writer.visitEnd(); + + try(FileOutputStream stream = new FileOutputStream("./Main.class")){ + stream.write(writer.toByteArray()); //直接通过ClassWriter将字节码文件转换为byte数组,并保存到根目录下 + } catch (IOException e) { + e.printStackTrace(); + } + } + } + ``` + + 可以看到,在IDEA中反编译的结果为: + + ```Java + package com.test; + + public class Main { + } + ``` + + 我们知道,正常的类在编译之后,如果没有手动添加构造方法,那么会自带一个无参构造,但是我们这个类中还没有,所以我们来手动添加一个无参构造方法: + + ```Java + //通过visitMethod方法可以添加一个新的方法 + writer.visitMethod(ACC_PUBLIC, "", "()V", null, null); + ``` + + 可以看到反编译的结果中已经存在了我们的构造方法: + + ```Java + package com.test; + + public class Main { + public Main() { + } + } + ``` + + 但是这样是不合法的,因为我们的构造方法还没有添加父类构造方法调用,所以说我们还需要在方法中添加父类构造方法调用指令: + + ```Plain + public com.test.Main(); + descriptor: ()V + flags: ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + 0: aload_0 + 1: invokespecial #1 // Method java/lang/Object."":()V + 4: return + LineNumberTable: + line 11: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/test/Main; + ``` + + 我们需要对方法进行详细编辑: + + ```Java + //通过MethodVisitor接收返回值,进行进一步操作 + MethodVisitor visitor = writer.visitMethod(ACC_PUBLIC, "", "()V", null, null); + //开始编辑代码 + visitor.visitCode(); + + //Label用于存储行号 + Label l1 = new Label(); + //当前代码写到哪行了,l1得到的就是多少行 + visitor.visitLabel(l1); + //添加源码行数对应表(其实可以不用) + visitor.visitLineNumber(11, l1); + + //注意不同类型的指令需要用不同方法来调用,因为操作数不一致,具体的注释有写 + visitor.visitVarInsn(ALOAD, 0); + visitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + visitor.visitInsn(RETURN); + + Label l2 = new Label(); + visitor.visitLabel(l2); + //添加本地变量表,这里加的是this关键字,但是方法中没用到,其实可以不加 + visitor.visitLocalVariable("this", "Lcom/test/Main;", null, l1, l2, 0); + + //最后设定最大栈深度和本地变量数 + visitor.visitMaxs(1, 1); + //结束编辑 + visitor.visitEnd(); + ``` + + 我们可以对编写好的class文件进行反编译,看看是不是和IDEA编译之后的结果差不多: + + ```Plain + { + public com.test.Main(); + descriptor: ()V + flags: ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + 0: aload_0 + 1: invokespecial #8 // Method java/lang/Object."":()V + 4: return + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/test/Main + LineNumberTable: + line 11: 0 + } + ``` + + 可以看到和之前的基本一致了,到此为止我们构造方法就编写完成了,接着我们来写一下main方法,一会我们就可以通过main方法来运行Java程序了。比如我们要编写这样一个程序: + + ```Java + public static void main(String[] args) { + int a = 10; + System.out.println(a); + } + ``` + + 看起来很简单的一个程序对吧,但是我们如果手动去组装指令,会极其麻烦!首先main方法是一个静态方法,并且方法是public权限,然后还有一个参数`String[] args`,所以说我们这里要写的内容有点小多: + + ```Java + //开始安排main方法 + MethodVisitor v2 = writer.visitMethod(ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); + v2.visitCode(); + //记录起始行信息 + Label l3 = new Label(); + v2.visitLabel(l3); + v2.visitLineNumber(13, l3); + + //首先是int a = 10的操作,执行指令依次为: + // bipush 10 将10推向操作数栈顶 + // istore_1 将操作数栈顶元素保存到1号本地变量a中 + v2.visitIntInsn(BIPUSH, 10); + v2.visitVarInsn(ISTORE, 1); + Label l4 = new Label(); + v2.visitLabel(l4); + //记录一下行信息 + v2.visitLineNumber(14, l4); + + //这里是获取System类中的out静态变量(PrintStream接口),用于打印 + v2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); + //把a的值取出来 + v2.visitVarInsn(ILOAD, 1); + //调用接口中的抽象方法println + v2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false); + + //再次记录行信息 + Label l6 = new Label(); + v2.visitLabel(l6); + v2.visitLineNumber(15, l6); + + v2.visitInsn(RETURN); + Label l7 = new Label(); + v2.visitLabel(l7); + + //最后是本地变量表中的各个变量 + v2.visitLocalVariable("args", "[Ljava/lang/String;", null, l3, l7, 0); + v2.visitLocalVariable("a", "I", null, l4, l7, 1); + v2.visitMaxs(1, 2); + //终于OK了 + v2.visitEnd(); + ``` + + 可以看到,虽然很简单的一个程序,但是如果我们手动去编写字节码,实际上是非常麻烦的,但是要实现动态代理之类的操作(可以很方便地修改字节码创建子类),是不是感觉又Get到了新操作(其实Spring实现动态代理的CGLib框架底层正是调用了ASM框架来实现的),所以说了解一下还是可以的,不过我们自己肯定是没多少玩这个的机会了。 + + +--- + +## 类加载机制 + +现在,我们已经了解了字节码文件的结构,以及JVM如何对内存进行管理,现在只剩下最后一个谜团等待解开了,也就是我们的类字节码文件到底是如何加载到内存中的,加载之后又会做什么事情。 + +### 类加载过程 + +- 详情 + + 首先,要加载一个类,一定是出于某种目的的,比如我们要运行我们的Java程序,那么就必须要加载主类才能运行主类中的主方法,又或是我们需要加载数据库驱动,那么可以通过反射来将对应的数据库驱动类进行加载。 + + 所以,一般在这些情况下,如果类没有被加载,那么会被自动加载: + + - 使用new关键字创建对象时 + - 使用某个类的静态成员(包括方法和字段)的时候(当然,final类型的静态字段有可能在编译的时候被放到了当前类的常量池中,这种情况下是不会触发自动加载的) + - 使用反射对类信息进行获取的时候(之前的数据库驱动就是这样的) + - 加载一个类的子类时 + - 加载接口的实现类,且接口带有`default`的方法默认实现时 + + 比如这种情况,那么需要用到另一个类中的成员字段,所以就必须将另一个类加载之后才能访问: + + ```Java + public class Main { + public static void main(String[] args) { + System.out.println(Test.str); + } + + public static class Test{ + static { + System.out.println("我被初始化了!"); + } + + public static String str = "都看到这里了,不给个三连+关注吗?"; + } + } + ``` + + 这里我们就演示一个不太好理解的情况,我们现在将静态成员变量修改为final类型的: + + ```Java + public class Main { + public static void main(String[] args) { + System.out.println(Test.str); + } + + public static class Test{ + static { + System.out.println("我被初始化了!"); + } + + public final static String str = "都看到这里了,不给个三连+关注吗?"; + } + } + ``` + + 可以看到,在主方法中,我们使用了Test类的静态成员变量,并且此静态成员变量是一个final类型的,也就是说不可能再发生改变。那么各位觉得,Test类会像上面一样被初始化吗? + + 按照正常逻辑来说,既然要用到其他类中的字段,那么肯定需要加载其他类,但是这里我们结果发现,并没有对Test类进行加载,那么这是为什么呢?我们来看看Main类编译之后的字节码指令就知道了: + + ![image-20230306170622970](JVM后篇/JyFWfPbBvIK5zMe.webp) + + 很明显,这里使用的是`ldc`指令从常量池中将字符串取出并推向操作数栈顶,也就是说,在编译阶段,整个`Test.str`直接被替换为了对应的字符串(因为final不可能发生改变的,编译就会进行优化,直接来个字符串比你去加载类在获取快得多不是吗,反正结果都一样),所以说编译之后,实际上跟Test类半毛钱关系都没有了。 + + 所以说,当你在某些情况下疑惑为什么类加载了或是没有加载时,可以从字节码指令的角度去进行分析,一般情况下,只要遇到`new`、`getstatic`、`putstatic`、`invokestatic`这些指令时,都会进行类加载,比如: + + ![image-20230306170635704](JVM后篇/IRo9i6hntA2jQ3X.webp) + + 这里很明显,是一定会将Test类进行加载的。除此之外,各位也可以试试看数组的定义会不会导致类被加载。 + + 好了,聊完了类的加载触发条件,我们接着来看一下类的详细加载流程。 + + ![image-20230306170654350](JVM后篇/UIV6fJknmM4bojP.webp) + + 首先类的生命周期一共有7个阶段,而首当其冲的就是加载,加载阶段需要获取此类的二进制数据流,比如我们要从硬盘中读取一个class文件,那么就可以通过文件输入流来获取类文件的`byte[]`,也可以是其他各种途径获取类文件的输入流,甚至网络传输并加载一个类也不是不可以。然后交给类加载器进行加载(类加载器可以是JDK内置的,也可以是开发者自己撸的,后面会详细介绍)类的所有信息会被加载到方法区中,并且在堆内存中会生成一个代表当前类的Class类对象(那么思考一下,同一个Class文件加载的类,是唯一存在的吗?),我们可以通过此对象以及反射机制来访问这个类的各种信息。 + + 数组类要稍微特殊一点,通过前面的检验,我没发现数组在创建后是不会导致类加载的,数组类型本身不会通过类加载器进行加载的,不过你既然要往里面丢对象进去,那最终依然是要加载类的。 + + 接着我们来看验证阶段,验证阶段相当于是对加载的类进行一次规范校验(因为一个类并不一定是由我们使用IDEA编译出来的,有可能是像我们之前那样直接用ASM框架写的一个),如果说类的任何地方不符合虚拟机规范,那么这个类是不会验证通过的,如果没有验证机制,那么一旦出现危害虚拟机的操作,整个程序会出现无法预料的后果。 + + 验证阶段,首先是文件格式的验证: + + - 是否魔数为CAFEBABE开头。 + - 主、次版本号是否可以由当前Java虚拟机运行 + - Class文件各个部分的完整性如何。 + - ... + + 有关类验证的详细过程,可以参考《深入理解Java虚拟机 第三版》268页。 + + 接下来就是准备阶段了,这个阶段会为类变量分配内存,并为一些字段设定初始值,注意是系统规定的初始值,不是我们手动指定的初始值。 + + 再往下就是解析阶段,此阶段是将常量池内的符号引用替换为直接引用的过程,也就是说,到这个时候,所有引用变量的指向都是已经切切实实地指向了内存中的对象了。 + + 到这里,链接过程就结束了,也就是说这个时候类基本上已经完成大部分内容的初始化了。 + + 最后就是真正的初始化阶段了,从这里开始,类中的Java代码部分,才会开始执行,还记得我们之前介绍的``方法吗,它就是在这个时候执行的,比如我们的类中存在一个静态成员变量,并且赋值为10,或是存在一个静态代码块,那么就会自动生成一个``方法来进行赋值操作,但是这个方法是自动生成的。 + + 全部完成之后,我们的类就算是加载完成了。 + + +### 类加载器 + +- 详情 + + Java提供了类加载器,以便我们自己可以更好地控制类加载,我们可以自定义类加载器,也可以使用官方自带的类加载器去加载类。对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性。 + + 也就是说,一个类可以由不同的类加载器加载,并且,不同的类加载器加载的出来的类,即使来自同一个Class文件,也是不同的,只有两个类来自同一个Class文件并且是由同一个类加载器加载的,才能判断为是同一个。默认情况下,所有的类都是由JDK自带的类加载器进行加载。 + + 比如,我们先创建一个Test类用于测试: + + ```Java + package com.test; + + public class Test { + + } + ``` + + 接着我们自己实现一个ClassLoader来加载我们的Test类,同时使用官方默认的类加载器来加载: + + ```Java + public class Main { + public static void main(String[] args) throws ReflectiveOperationException { + Class testClass1 = Main.class.getClassLoader().loadClass("com.test.Test"); + CustomClassLoader customClassLoader = new CustomClassLoader(); + Class testClass2 = customClassLoader.loadClass("com.test.Test"); + + //看看两个类的类加载器是不是同一个 + System.out.println(testClass1.getClassLoader()); + System.out.println(testClass2.getClassLoader()); + + //看看两个类是不是长得一模一样 + System.out.println(testClass1); + System.out.println(testClass2); + + //两个类是同一个吗? + System.out.println(testClass1 == testClass2); + + //能成功实现类型转换吗? + Test test = (Test) testClass2.newInstance(); + } + + static class CustomClassLoader extends ClassLoader { + @Override + public Class loadClass(String name) throws ClassNotFoundException { + try (FileInputStream stream = new FileInputStream("./target/classes/"+name.replace(".", "/")+".class")){ + byte[] data = new byte[stream.available()]; + stream.read(data); + if(data.length == 0) return super.loadClass(name); + return defineClass(name, data, 0, data.length); + } catch (IOException e) { + return super.loadClass(name); + } + } + } + } + ``` + + 通过结果我们发现,即使两个类是同一个Class文件加载的,只要类加载器不同,那么这两个类就是不同的两个类。 + + 所以说,我们当时在JavaSE阶段讲解的每个类都在堆中有一个唯一的Class对象放在这里来看,并不完全正确,只是当前为了防止各位初学者搞混。 + + 实际上,JDK内部提供的类加载器一共有三个,比如上面我们的Main类,其实是被AppClassLoader加载的,而JDK内部的类,都是由BootstrapClassLoader加载的,这其实就是为了实现双亲委派机制而做的。 + + ![image-20230306170728531](JVM后篇/RFaE7s5CnmylgkT.webp) + + 有关双亲委派机制,我们在JavaSE阶段反射板块已经讲解过了,所以说这就不多做介绍了。 + +- 为什么要自定义类加载器 + + Java 提供自定义类加载器的机制有几个重要的原因。让我们深入探讨这个问题: + + 1. 扩展类加载方式: + - 默认的类加载器从文件系统加载类,但有时我们需要从其他源加载类,如网络、数据库等。 + - 自定义类加载器允许我们定义特殊的类加载逻辑。 + 2. 实现类的隔离: + - 在同一个 JVM 中,不同的应用可能需要使用同一个类的不同版本。 + - 自定义类加载器可以为每个应用加载独立的类版本,实现类的隔离。 + 3. 提高安全性: + - 可以在加载类之前进行安全检查,如验证数字签名。 + - 可以实现自定义的安全策略,控制哪些类可以被加载。 + 4. 实现热部署: + - 在不重启 JVM 的情况下,通过自定义类加载器可以重新加载更新后的类。 + - 这在需要频繁更新的系统中非常有用,如开发环境或某些服务器应用。 + 5. 懒加载: + - 可以实现按需加载类,而不是在启动时就加载所有类。 + - 这可以提高应用的启动速度和资源利用效率。 + 6. 修改字节码: + - 在加载类的过程中,可以动态修改类的字节码。 + - 这对于一些特殊需求如性能监控、代码增强等非常有用。 + 7. 实现插件化架构: + - 在插件系统中,可以使用自定义类加载器来加载和管理插件。 + - 这样可以实现插件的动态加载和卸载。 + 8. 处理特殊的类加载需求: + - 某些框架或库可能需要特殊的类加载机制。 + - 例如,一些 Web 服务器使用自定义类加载器来隔离不同的 Web 应用。 + 9. 跨应用程序边界共享类: + - 在某些情况下,可能需要在不同的应用程序间共享类,自定义类加载器可以实现这一点。 + 10. 实现类的版本控制: + - 可以加载特定版本的类,而不受系统类路径中可能存在的其他版本影响。 + 11. 优化类加载性能: + - 在某些情况下,自定义的类加载策略可能比默认的更高效。 + 12. 动态代理和AOP的实现: + - 一些高级的编程技术,如动态代理和面向切面编程(AOP),often利用自定义类加载器来动态生成和加载类。 + + 示例:自定义网络类加载器 + + 以下是一个简单的自定义类加载器示例,它从网络加载类: + + ```Java + public class NetworkClassLoader extends ClassLoader { + private String baseUrl; + + public NetworkClassLoader(String baseUrl) { + this.baseUrl = baseUrl; + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + try { + String url = baseUrl + "/" + name.replace('.', '/') + ".class"; + URL classUrl = new URL(url); + try (InputStream is = classUrl.openStream()) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int ch; + while ((ch = is.read()) != -1) { + baos.write(ch); + } + byte[] bytes = baos.toByteArray(); + return defineClass(name, bytes, 0, bytes.length); + } + } catch (IOException e) { + throw new ClassNotFoundException("Class " + name + " not found", e); + } + } + } + ``` + + 这个例子展示了如何创建一个从网络加载类的自定义类加载器。它从指定的 URL 下载类文件,然后使用 `defineClass` 方法将字节码转换为 Class 对象。 + + 总结来说,Java 提供自定义类加载器机制,极大地增强了 Java 平台的灵活性和可扩展性,使得开发者能够实现各种复杂的类加载策略,满足不同场景下的特殊需求。 \ No newline at end of file diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/1wOpRXoQklGN9Ez.webp b/posts/blog/编程技术/java/JVM/JVM后篇/1wOpRXoQklGN9Ez.webp new file mode 100644 index 0000000..6d80607 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/1wOpRXoQklGN9Ez.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/28BVwcadNEjCnLi.webp b/posts/blog/编程技术/java/JVM/JVM后篇/28BVwcadNEjCnLi.webp new file mode 100644 index 0000000..622af29 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/28BVwcadNEjCnLi.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/2hbcO5flxzodN9J.webp b/posts/blog/编程技术/java/JVM/JVM后篇/2hbcO5flxzodN9J.webp new file mode 100644 index 0000000..8f9e1f5 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/2hbcO5flxzodN9J.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/3TvqOXGsDIPbzlh.webp b/posts/blog/编程技术/java/JVM/JVM后篇/3TvqOXGsDIPbzlh.webp new file mode 100644 index 0000000..0103e63 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/3TvqOXGsDIPbzlh.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/4nFVBxOIo6QKjHA.webp b/posts/blog/编程技术/java/JVM/JVM后篇/4nFVBxOIo6QKjHA.webp new file mode 100644 index 0000000..dc0eec4 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/4nFVBxOIo6QKjHA.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/AbpBRLIwczTyqMP.webp b/posts/blog/编程技术/java/JVM/JVM后篇/AbpBRLIwczTyqMP.webp new file mode 100644 index 0000000..1a55c8c Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/AbpBRLIwczTyqMP.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/HE7QJpfbh6sOLji.webp b/posts/blog/编程技术/java/JVM/JVM后篇/HE7QJpfbh6sOLji.webp new file mode 100644 index 0000000..9b61e52 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/HE7QJpfbh6sOLji.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/IRo9i6hntA2jQ3X.webp b/posts/blog/编程技术/java/JVM/JVM后篇/IRo9i6hntA2jQ3X.webp new file mode 100644 index 0000000..3258401 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/IRo9i6hntA2jQ3X.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/JyFWfPbBvIK5zMe.webp b/posts/blog/编程技术/java/JVM/JVM后篇/JyFWfPbBvIK5zMe.webp new file mode 100644 index 0000000..1b0c130 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/JyFWfPbBvIK5zMe.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/LOycXsCrm4vAjDk.webp b/posts/blog/编程技术/java/JVM/JVM后篇/LOycXsCrm4vAjDk.webp new file mode 100644 index 0000000..c7f6419 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/LOycXsCrm4vAjDk.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/O4XnFbfRShkc8WZ.webp b/posts/blog/编程技术/java/JVM/JVM后篇/O4XnFbfRShkc8WZ.webp new file mode 100644 index 0000000..1aa412b Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/O4XnFbfRShkc8WZ.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/Phy8UoMGiLkpdsf.webp b/posts/blog/编程技术/java/JVM/JVM后篇/Phy8UoMGiLkpdsf.webp new file mode 100644 index 0000000..668441f Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/Phy8UoMGiLkpdsf.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/QOvi5YpnaHTxrVU.webp b/posts/blog/编程技术/java/JVM/JVM后篇/QOvi5YpnaHTxrVU.webp new file mode 100644 index 0000000..b521ef5 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/QOvi5YpnaHTxrVU.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/RFaE7s5CnmylgkT.webp b/posts/blog/编程技术/java/JVM/JVM后篇/RFaE7s5CnmylgkT.webp new file mode 100644 index 0000000..0da7eb3 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/RFaE7s5CnmylgkT.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/UIV6fJknmM4bojP.webp b/posts/blog/编程技术/java/JVM/JVM后篇/UIV6fJknmM4bojP.webp new file mode 100644 index 0000000..5f02447 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/UIV6fJknmM4bojP.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/V1RyLUTBgfFMk69.webp b/posts/blog/编程技术/java/JVM/JVM后篇/V1RyLUTBgfFMk69.webp new file mode 100644 index 0000000..68e4408 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/V1RyLUTBgfFMk69.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/WORY4BSMzhcQNyt.webp b/posts/blog/编程技术/java/JVM/JVM后篇/WORY4BSMzhcQNyt.webp new file mode 100644 index 0000000..ffbd1b3 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/WORY4BSMzhcQNyt.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/WskFX26NDco8Jgv.webp b/posts/blog/编程技术/java/JVM/JVM后篇/WskFX26NDco8Jgv.webp new file mode 100644 index 0000000..6d536d1 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/WskFX26NDco8Jgv.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/aB8fu1GUNwYMPLh.webp b/posts/blog/编程技术/java/JVM/JVM后篇/aB8fu1GUNwYMPLh.webp new file mode 100644 index 0000000..0b37d98 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/aB8fu1GUNwYMPLh.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/c7ebHq3TBxudK1A.webp b/posts/blog/编程技术/java/JVM/JVM后篇/c7ebHq3TBxudK1A.webp new file mode 100644 index 0000000..9c27d9d Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/c7ebHq3TBxudK1A.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/dPoWa74AeYJcIG1.webp b/posts/blog/编程技术/java/JVM/JVM后篇/dPoWa74AeYJcIG1.webp new file mode 100644 index 0000000..aa0d8ef Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/dPoWa74AeYJcIG1.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/dzmcQUyFrH9kTWZ.webp b/posts/blog/编程技术/java/JVM/JVM后篇/dzmcQUyFrH9kTWZ.webp new file mode 100644 index 0000000..a47222f Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/dzmcQUyFrH9kTWZ.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/fDTVYpAlaBF6j91.webp b/posts/blog/编程技术/java/JVM/JVM后篇/fDTVYpAlaBF6j91.webp new file mode 100644 index 0000000..2a8bed7 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/fDTVYpAlaBF6j91.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/heoLwXRVpuBYbZn.webp b/posts/blog/编程技术/java/JVM/JVM后篇/heoLwXRVpuBYbZn.webp new file mode 100644 index 0000000..b71cf13 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/heoLwXRVpuBYbZn.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/i857GLYJ1fSIKz4.webp b/posts/blog/编程技术/java/JVM/JVM后篇/i857GLYJ1fSIKz4.webp new file mode 100644 index 0000000..72e7d7a Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/i857GLYJ1fSIKz4.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/kOPlftdWADqyoHL.webp b/posts/blog/编程技术/java/JVM/JVM后篇/kOPlftdWADqyoHL.webp new file mode 100644 index 0000000..b80ec8b Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/kOPlftdWADqyoHL.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/lY6sJwicX5d2FVg.webp b/posts/blog/编程技术/java/JVM/JVM后篇/lY6sJwicX5d2FVg.webp new file mode 100644 index 0000000..a43c118 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/lY6sJwicX5d2FVg.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/oVg2cwD4Gjrfab7.webp b/posts/blog/编程技术/java/JVM/JVM后篇/oVg2cwD4Gjrfab7.webp new file mode 100644 index 0000000..aedf0fd Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/oVg2cwD4Gjrfab7.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/p6wAqsZneuI8Wo3.webp b/posts/blog/编程技术/java/JVM/JVM后篇/p6wAqsZneuI8Wo3.webp new file mode 100644 index 0000000..47ba171 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/p6wAqsZneuI8Wo3.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/tizymh9BjYAFp3x.webp b/posts/blog/编程技术/java/JVM/JVM后篇/tizymh9BjYAFp3x.webp new file mode 100644 index 0000000..4653302 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/tizymh9BjYAFp3x.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/u2K8Y5yU1zf9LQ4.webp b/posts/blog/编程技术/java/JVM/JVM后篇/u2K8Y5yU1zf9LQ4.webp new file mode 100644 index 0000000..4d22e84 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/u2K8Y5yU1zf9LQ4.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/uCrZ8aG2EoxLM5c.webp b/posts/blog/编程技术/java/JVM/JVM后篇/uCrZ8aG2EoxLM5c.webp new file mode 100644 index 0000000..fd5371c Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/uCrZ8aG2EoxLM5c.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM后篇/zkJVicf764bFPXd.webp b/posts/blog/编程技术/java/JVM/JVM后篇/zkJVicf764bFPXd.webp new file mode 100644 index 0000000..53217f4 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM后篇/zkJVicf764bFPXd.webp differ diff --git a/posts/blog/编程技术/java/JVM/JVM笔记.md b/posts/blog/编程技术/java/JVM/JVM笔记.md new file mode 100644 index 0000000..eb82fac --- /dev/null +++ b/posts/blog/编程技术/java/JVM/JVM笔记.md @@ -0,0 +1,102 @@ +--- +title: JVM笔记 +date: 2024-08-25 +tags: [JVM] +--- + + +[前篇](前篇.md) + +[中篇](中篇.md) + +[后篇](后篇.md) + +![image 29.png](JVM笔记/image29.png) + + + +软引用(Soft Reference)和弱引用(Weak Reference)都是 Java 中提供的引用类型,它们的主要作用是控制垃圾回收机制如何处理引用对象。它们都属于 **java.lang.ref** 包中,并且在不同的场景中可以避免内存泄漏,并帮助实现更灵活的内存管理。 + +### 1. **软引用(Soft Reference)** + +- **定义**:软引用是一种在内存充足时不会被垃圾回收器回收的引用类型,只有在内存不足时,垃圾回收器才会回收这些对象。 +- **特点**: + - 软引用通常用于实现内存敏感的缓存。 + - 如果 JVM 运行时内存不足,软引用指向的对象会被回收。 + - 软引用的对象会在垃圾回收时被清理,但如果 JVM 内存充足,它们会一直存在。 +- **用途**: + - 用于内存缓存,例如当内存不足时,缓存中的一些对象可以被回收,但并不在内存充足时轻易回收。 +- **如何使用**: + - 软引用的对象通过 `SoftReference` 类来创建。 + - 一般结合 `SoftReference` 和 `SoftReference.get()` 来使用。 + +```Java +import java.lang.ref.*; + +public class SoftReferenceExample { + public static void main(String[] args) { + String str = new String("Hello"); + SoftReference softRef = new SoftReference<>(str); + + // 在内存充足时,softRef.get() 可以访问对象 + System.out.println(softRef.get()); // 输出:Hello + + str = null; // 将原始引用设为 null + + // 如果 JVM 内存不足,softRef.get() 可能返回 null + System.gc(); // 触发垃圾回收 + + System.out.println(softRef.get()); // 输出:null (如果对象被回收) + } +} +``` + +### 2. **弱引用(Weak Reference)** + +- **定义**:弱引用是一种比软引用更弱的引用类型。弱引用指向的对象,在垃圾回收时只要没有强引用指向它,就会被回收。 +- **特点**: + - 弱引用的对象会在下一次垃圾回收时被回收,无论内存是否充足。 + - 弱引用常用于实现那些不阻止对象回收的场景,如在某些缓存机制中,用来缓存不需要强引用的对象。 +- **用途**: + - 用于对象的缓存或者在一些不需要长期存在的资源管理中。 + - 典型的应用是 `**ThreadLocal**` 类,它使用弱引用来存储每个线程的局部变量。 +- **如何使用**: + - 使用 `WeakReference` 类创建弱引用对象。 + - 和软引用一样,通过 `WeakReference.get()` 方法来获取对象。 + +```Java +import java.lang.ref.*; + +public class WeakReferenceExample { + public static void main(String[] args) { + String str = new String("Hello"); + WeakReference weakRef = new WeakReference<>(str); + + // 在没有强引用时,weakRef.get() 会返回 null + System.out.println(weakRef.get()); // 输出:Hello + + str = null; // 将原始引用设为 null + + System.gc(); // 强制触发垃圾回收 + + System.out.println(weakRef.get()); // 输出:null (对象被回收) + } +} +``` + +### 3. **软引用与弱引用的区别** + +| | | | +|---|---|---| +|特性|软引用(Soft Reference)|弱引用(Weak Reference)| +|回收时机|仅在内存不足时被回收|一旦没有强引用指向,立刻被垃圾回收| +|内存敏感性|相对更灵活,在内存充足时保持对象存在|对象更容易被回收,几乎没有内存保护| +|使用场景|实现内存敏感缓存,如图片缓存、对象缓存|适用于短期存活的数据,例如 `ThreadLocal`| +|对象存活周期|在内存充足时,可能会长时间存在|对象几乎总是在短时间内被回收| + +### 4. **总结** + +- **软引用**:在内存不足时会被回收,适用于内存敏感的缓存场景。 +- **弱引用**:一旦没有强引用指向的对象就会被垃圾回收器回收,适用于短期有效的对象,如线程局部存储(`ThreadLocal`)等。 + +两者都可以帮助实现内存管理,但选择哪个取决于你的实际需求。如果你希望缓存数据可以在内存充足时长时间保持,但在内存紧张时及时回收,可以使用软引用;如果对象不需要长期保存且希望快速回收,应该选择弱引用。 \ No newline at end of file diff --git a/posts/blog/编程技术/java/JVM/JVM笔记/image29.png b/posts/blog/编程技术/java/JVM/JVM笔记/image29.png new file mode 100644 index 0000000..dd16d90 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/JVM笔记/image29.png differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制.md b/posts/blog/编程技术/java/JVM/垃圾回收机制.md new file mode 100644 index 0000000..473f3ad --- /dev/null +++ b/posts/blog/编程技术/java/JVM/垃圾回收机制.md @@ -0,0 +1,472 @@ +--- +title: 垃圾回收机制 +date: 2024-08-25 +tags: [JVM] +--- + + +## 对象存活判定算法 + +首先我们来套讨论第一个问题,也就是:对象在什么情况下可以被判定为不再使用已经可以回收了?这里就需要提到以下几种垃圾回收算法了。 + +![image-20230306165128192](垃圾回收机制/stqTu8gD3ykixva.webp) + +### 引用计数法 + +我们知道,如果我们要经常操作一个对象,那么首先一定会创建一个引用变量: + +```Java +//str就是一个引用类型的变量,它持有对后面字符串对象的引用,可以代表后面这个字符串对象本身 +String str = "lbwnb"; + +//str.xxxxx... +``` + +实际上,我们会发现,只要一个对象还有使用价值,我们就会通过它的引用变量来进行操作,那么可否这样判断一个对象是否还需要被使用: + +- 每个对象都包含一个 **引用计数器**,用于存放引用计数(其实就是存放被引用的次数) +- 每当有一个地方**引用此对象时**,引用计数`+1` +- 当引用失效( 比如离开了局部变量的作用域或是引用被设定为`null`)时,引用计数`1` +- 当引用计数为`0`时,表示此对象不可能再被使用,因为这时我们已经没有任何方法可以得到此对象的引用了 + +**缺陷**:但是这样存在一个问题,如果两个对象相互引用呢? + +```Java +public class Main { + public static void main(String[] args) { + Test a = new Test(); + Test b = new Test(); + + a.another = b; + b.another = a; + + //这里直接把a和b赋值为null,这样前面的两个对象我们不可能再得到了 + a = b = null; + } + + private static class Test{ + Test another; + } +} +``` + +按照引用计数算法,那么当出现以上情况时,虽然我们无法在得到此对象的引用了,并且此对象我们也无需再使用,但是由于这两个对象直接存在相互引用的情况,那么引用计数器的值将会永远是`1`,但是实际上此对象已经没有任何用途了。所以引用计数法并不是最好的解决方案。 + +### 可达性分析算法⭐ + +目前比较主流的编程语言(包括Java),一般都会使用可达性分析算法来判断对象是否存活,它采用了类**似于树结构的搜索机制**。 + +- 首先每个对象的引用都有机会成为树的根节点(GC Roots),可以被选定作为根节点条件如下: + - 位于虚拟机栈的栈帧中的本地变量表中所引用到的对象(其实就是我们方法中的局部变量)同样也包括本地方法栈中JNI引用的对象。 + - 类的静态成员变量引用的对象。 + - 方法区中,常量池里面引用的对象,比如我们之前提到的`String`类型对象。 + - 被添加了锁的对象(比如synchronized关键字) + - 虚拟机内部需要用到的对象。 + +![image-20230306165140204](垃圾回收机制/4MIORcEDzquZFia.webp) + +- 一旦已经存在的根节点不满足存在的条件时,那么根节点与对象之间的连接将被断开。此时虽然对象1仍存在对其他对象的引用,但是由于其没有任何根节点引用,所以此对象即可被判定为不再使用。比如某个方法中的局部变量引用,在方法执行完成返回之后: + +![image-20230306165153841](垃圾回收机制/WYSGI5xCh8mOBcK.webp) + +- 这样就能很好地解决我们刚刚提到的循环引用问题,我们再来重现一下出现循环引用的情况: + + ![image-20230306165211604](垃圾回收机制/48ZguJqIK6ojEba.webp) + + 可以看到,对象1和对象2依然是存在循环引用的,但是只有他们各自的GC Roots断开,那么就会变成下面这样: + + ![image-20230306165225991](垃圾回收机制/KdZpLNTzEitCOB3.webp) + +所以,我们最后进行一下总结:如果某个对象==无法到达任何GC Roots==,则证明此对象是不可能再被使用的。 + + + +- 首先会从 GC Roots 出发,标记所有存活的对象 + +### 最终判定 + +- 虽然在经历了可达性分析算法之后基本可能判定哪些对象能够被回收,但是并不代表此对象一定会被回收,我们依然可以==在最终判定阶段对其进行挽留==。 +- `Object`类中的`**finalize()**`方法正是最终判定方法 + +- 重写`finalize`方法详解 + + ```Java + /** + * Called by the garbage collector on an object when garbage collection + * determines that there are no more references to the object. + * A subclass overrides the {@code finalize} method to dispose of + * system resources or to perform other cleanup. + * ... + */ + protected void finalize() throws Throwable { } + ``` + + 此方法正是最终判定方法,如果子类重写了此方法,那么子类对象在被判定为可回收时,会进行二次确认,也就是执行`finalize()`方法,而在此方法中,当前对象是完全有可能重新建立GC Roots的!所以,如果在二次确认后对象不满足可回收的条件,那么此对象不会被回收,巧妙地逃过了垃圾回收的命运。比如下面这个例子: + + ```Java + public class Main { + private static Test a; + public static void main(String[] args) throws InterruptedException { + a = new Test(); + + //这里直接把a赋值为null,这样前面的对象我们不可能再得到了 + a = null; + + //手动申请执行垃圾回收操作(注意只是申请,并不一定会执行,但是一般情况下都会执行) + System.gc(); + + //等垃圾回收一下() + Thread.sleep(1000); + + //我们来看看a有没有被回收 + System.out.println(a); + } + + private static class Test{ + @Override + protected void finalize() throws Throwable { + System.out.println(this+" 开始了它的救赎之路!"); + a = this; + } + } + } + ``` + + 注意`finalize()`方法并不是在主线程调用的,而是虚拟机自动建立的一个低优先级的`Finalizer`线程(正是因为优先级比较低,所以前面才需要等待1秒钟)进行处理,我们可以稍微修改一下看看: + + ```Java + private static class Test{ + @Override + protected void finalize() throws Throwable { + System.out.println(Thread.currentThread()); + a = this; + } + } + ``` + + ```Plain + Thread[Finalizer,8,system] + com.test.Main$Test@232204a1 + ``` + + 同时,同一个对象的`finalize()`方法只会有一次调用机会,也就是说,如果我们连续两次这样操作,那么第二次,对象必定被回收: + + ```Java + public static void main(String[] args) throws InterruptedException { + a = new Test(); + //这里直接把a赋值为null,这样前面的对象我们不可能再得到了 + a = null; + //手动申请执行垃圾回收操作(注意只是申请,并不一定会执行,但是一般情况下都会执行) + System.gc(); + //等垃圾回收一下 + Thread.sleep(1000); + + System.out.println(a); + //这里直接把a赋值为null,这样前面的对象我们不可能再得到了 + a = null; + //手动申请执行垃圾回收操作(注意只是申请,并不一定会执行,但是一般情况下都会执行) + System.gc(); + //等垃圾回收一下 + Thread.sleep(1000); + + System.out.println(a); + } + ``` + + 当然,`finalize()`方法也并不是专门防止对象被回收的,我们可以使用它来释放一些程序使用中的资源等。 + + 最后,总结成一张图:二次调用`finalize()`直接回收 + + ![image-20230306165250173](垃圾回收机制/8sSvloxtKINFYz4.webp) + +- 当然,除了堆中的对象以外,方法区中的数据也是可以被垃圾回收的,但是回收条件比较严格 + +## 哪些对象需要进行回收 + +**概述**:前面我们介绍了对象存活判定算法,现在我们已经可以准确地知道堆中的哪些对象可以被回收了,那么,接下来就该考虑如何对对象进行回收了,垃圾收集器会不定期地检查堆中的对象,查看它们是否满足被回收的条件。我们该如何对这些对象进行回收,是一个一个判断是否需要回收吗? + +### 分代收集机制 + +- 属于一种**内存管理机制**,使内存的利用率大大提高 + +- 产生原因 + - 实际上,如果我们对堆中的每一个对象都依次判断是否需要回收,这样的效率其实是很低的,那么有没有更好地回收机制呢?第一步,我们可以对堆中的对象进行分代管理。 + - 比如某些对象,在多次垃圾回收时,都未被判定为可回收对象,我们完全可以将这一部分对象放在一起,并让垃圾收集器减少回收此区域对象的频率,这样就能很好地提高垃圾回收的效率了。 + +- 因此,Java虚拟机将堆内存划分为**新生代**、**老年代**和**永久代**(其中永久代是HotSpot虚拟机特有的概念,在JDK8之前方法区实际上就是采用的永久代作为实现,而在JDK8之后,方法区由元空间实现,并且使用的是本地内存,容量大小取决于物理机实际大小)这里我们主要讨论的是**新生代**和**老年代**。 +- 结构:不同的分代内存回收机制也存在一些不同之处,在HotSpot虚拟机中,新生代被划分为三块,一块较大的**Eden空间**和两块较小的**Survivor空间**,默认比例为8:1:1,==老年代的GC频率相对较低==,永久代一般存放类信息等(其实就是方法区的实现)如图所示: + + ![image-20230306165311823](垃圾回收机制/OZrKbUm39lfaAgv.webp) + +- ⭐那么它是如何运作的呢? + - 当对象创建时,它会进入==Eden==区,如果是大对象会被直接丢进老年代,当==Eden区空间不足时==,触发**Minor GC**(次要垃圾回收,主要进行新生代区域的垃圾收集),每经历一轮GC,对象年龄就加1(对象的年龄通常存储在对象头中,一般是4位,最大值为`15`),==超过15岁,进入老年代,====GC详情如下== + - 通过**复制算法**来完成**Minor GC**,==From和To分区是服务该算法的== + - 过程:对象区域转移 + - **标记存活对象**:通过可达性算法分析 + - 在==Eden区和From Survivor区==中,JVM首先标记哪些对象仍然存活 + - **复制存活对象**: + - JVM将存活对象从Eden区和From Survivor区复制到==To Survivor==区,年龄加1。如果To Survivor区中无法容纳所有存活对象,部分存活对象可能会被直接晋升到老年代 + - **清空Eden区和From Survivor区**: + - 复制完存活对象后,Eden区和From Survivor区中的所有内容都会被清空(即所有未被标记为存活的对象会被视为垃圾并被丢弃)。 + - **交换From和To区域**: + - 在GC完成后,To Survivor区会变成新的From Survivor区,而原来的From Survivor区被清空,成为新的To Survivor区。 + - 为什么使用复制算法 + - 复制算法在移动对象的同时,自然地实现了**内存整理**。 + - 存活对象被复制到新的区域时,它们会紧密排列,**消除了碎片**。 + - 因为新生代中大多数对象生命周期短,存活率低,**复制少量存活对象**比清理大量死亡对象更高效 + +- 垃圾收集也分为: + - Minor GC - 次要垃圾回收,主要进行新生代区域的垃圾收集。 + - 触发条件:新生代的Eden区容量已满时。 + - Major GC - 主要垃圾回收,主要进行老年代的垃圾收集。 + - Full GC - 完全垃圾回收,对整个Java堆内存和方法区进行垃圾回收。 + - 触发条件1:每次晋升到老年代的对象平均大小大于老年代剩余空间 + - 触发条件2:Minor GC后存活的对象超过了老年代剩余空间 + - 触发条件3:永久代内存不足(JDK8之前) + - 触发条件4:手动调用`System.gc()`方法 + +- 测试 + + 我们可以添加启动参数来查看JVM的GC日志: + + ![image-20230306165355706](垃圾回收机制/iWqvCUySGk8Qlad.webp) + + ```Java + public class Main { + public static void main(String[] args) { + Object o = new Object(); + o = null; + System.gc(); + } + } + ``` + + ```Plain + [GC (System.gc()) [PSYoungGen: 2621K->528K(76288K)] 2621K->528K(251392K), 0.0006874 secs] [Times: user=0.01 sys=0.01, real=0.00 secs] + [Full GC (System.gc()) [PSYoungGen: 528K->0K(76288K)] [ParOldGen: 0K->332K(175104K)] 528K->332K(251392K), [Metaspace: 3073K->3073K(1056768K)], 0.0022693 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] + Heap + PSYoungGen total 76288K, used 3277K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000) + eden space 65536K, 5% used [0x000000076ab00000,0x000000076ae334d8,0x000000076eb00000) + from space 10752K, 0% used [0x000000076eb00000,0x000000076eb00000,0x000000076f580000) + to space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000) + ParOldGen total 175104K, used 332K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000) + object space 175104K, 0% used [0x00000006c0000000,0x00000006c00532d8,0x00000006cab00000) + Metaspace used 3096K, capacity 4496K, committed 4864K, reserved 1056768K + class space used 333K, capacity 388K, committed 512K, reserved 1048576K + ``` + + +### 空间分配担保 + +- 是一种空间不足的弥补措施 + +- 产生原因 + + 我们可以思考一下,有没有这样一种极端情况(正常情况下新生代的回收率是很高的,所以说不用太担心会经常出现这种问题),在一次GC后,新生代Eden区仍然存在大量的对象(因为GC之后存活对象会进入到一个Survivor区,但是==很明显这时已经超出Survivor区的容量==了,肯定是装不下的)那么现在该怎么办?这时就需要用到**空间分配担保机制**了,可以把==Survivor区无法容纳的对象直接送到老年代==,让老年代进行分配担保(当然老年代也得装得下才行)在现实生活中,贷款会指定担保人,就是当借款人还不起钱的时候由担保人来还钱。好,那既然新生代装不下就丢给老年代,那么要是==老年代也装不下新生代的数据==呢?这时,老年代肯定担保人是当不成了,那么这样的话,首先会判断一下之前的每次垃圾回收==进入老年代的平均大小是否小于当前老年代的剩余空间==,如果小于,那么说明也许可以放得下(不过也仅仅是也许,依然有可能放不下,因为判断的实际上只是平均值,万一这一次突然非常大呢),否则,会先来一次Full GC,进行一次大规模垃圾回收,来尝试腾出空间,再次判断老年代是否有空间存放,要是还是装不下,直接抛出OOM错误,摆烂。 + + +最后,我们来总结一下一次Minor GC的整个过程: + +![image-20230306165425918](垃圾回收机制/71PqlEfiGgBSLae.webp) + +--- + +## 收集过程 + +### 标记-清除算法 + +前面我们已经了解了整个堆内存实际上是以分代收集机制为主,但是依然没有讲到具体的收集过程,那么,具体的回收过程又是什么样的呢?首先我们来了解一下最古老的`标记-清除`算法。 + +首先==标记出所有需要回收的对象==,然后再==依次回收掉被标记的对象==,或是标记出所有不需要回收的对象,只回收未标记的对象。实际上这种算法是非常基础的,并且最易于理解的(这里对象我就以一个方框代替了,当然实际上存放是我们前说到的GC Roots形式) + +![image-20230306165444019](垃圾回收机制/d8anmBcLrW3iyJl.webp) + +虽然此方法非常简单,但是缺点也是非常明显的 ,首先如果内存中存在大量的对象,那么可能就会存在大量的标记,并且大规模进行清除。并且一次标记清除之后,连续的内存空间可能会出现许许多多的空隙,**碎片化会导致连续内存空间利用率降低**。 + +### 标记-复制算法 + +既然标记清除算法在面对大量对象时效率低,那么我们可以采用标记-复制算法。它将容量分为同样大小的两块区域, + +标记复制算法,实际上就是将内存区域划分为大小相同的两块区域,每次只使用其中的一块区域,每次垃圾回收结束后,将所有存活的对象全部复制到另一块区域中,并一次性清空当前区域。虽然浪费了一些时间进行复制操作,但是这样能够很好地解决对象大面积回收后空间碎片化严重的问题。 + +![image-20230306165458671](垃圾回收机制/JeifGj6kmVAY2PR.webp) + +这种算法就非常适用于新生代(因为新生代的回收效率极高,一般不会留下太多的对象)的垃圾回收,而我们之前所说的**新生代Survivor区其实就是这个思路**,包括8:1:1的比例也正是为了对标记复制算法进行优化而采取的。 + +### 标记-整理(压缩)算法 + +虽然标记-复制算法能够很好地应对新生代高回收率的场景,但是放到老年代,它就显得很鸡肋了。我们知道,一般长期都回收不到的对象,才有机会进入到老年代,所以老年代一般都是些钉子户,可能一次GC后,仍然存留很多对象。而标记复制算法会在GC后完整复制整个区域内容,并且会折损50%的区域,显然这并不适用于老年代。 + +那么我们能否这样,在标记所有待回收对象之后,不急着去进行回收操作,而是将所有待回收的对象整齐排列在一段内存空间中,而需要回收的对象全部往后丢,这样,前半部分的所有对象都是无需进行回收的,而后半部分直接一次性清除即可。 + +![image-20230306165514101](垃圾回收机制/4UncSVvO1P2xWKL.webp) + +虽然这样能保证内存空间充分使用,并且也没有标记复制算法那么繁杂,但是缺点也是显而易见的,它的**效率比前两者都低**。甚至,由于需要修改对象在内存中的位置,此时程序必须要暂停才可以,在极端情况下,可能会导致整个程序发生停顿(被称为“Stop The World”)。 + +所以,我们可以将**标记清除算法和标记整理算法**混合使用,在内存空间还不是很凌乱的时候,采用标记清除算法其实是没有多大问题的,当内存空间凌乱到一定程度后,我们可以进行一次标记整理算法。 + +## 垃圾收集器的实现 + +### Serial收集器 + +- **单线程**,需要STW,新生代:标记复制算法,老年代:标记整理算法 + - 详情 + + 这款垃圾收集器也是元老级别的收集器了,在JDK1.3.1之前,是虚拟机新生代区域收集器的唯一选择。这是一款单线程的垃圾收集器,也就是说,当开始进行垃圾回收时,需要**暂停所有的线程**,直到垃圾收集工作结束。它的新生代收集算法采用的是标记复制算法,老年代采用的是标记整理算法。 + + ![image-20230306165527009](垃圾回收机制/v79TEdmyo1njKwl.webp) + + 可以看到,当进入到垃圾回收阶段时,所有的用户线程必须等待GC线程完成工作,就相当于你打一把LOL 40分钟,中途每隔1分钟网络就卡5秒钟,可能这时你正在打团,结果你被物理控制直接在那里站了5秒钟,这确实让人难以接受。 + + - 虽然缺点很明显,但是优势也是显而易见的: + 1. 设计简单而高效。 + 2. 在用户的桌面应用场景中,内存一般不大,可以在较短时间内完成垃圾收集,只要不频繁发生,使用串行回收器是可以接受的。 + + 所以,在客户端模式(一般用于一些桌面级图形化界面应用程序)下的新生代中,默认垃圾收集器至今依然是Serial收集器。我们可以在`java -version`中查看默认的客户端模式: + + - **Client Mode** 优化桌面应用程序,侧重于快速启动和低资源占用。 + - **Server Mode** 优化服务器端应用程序,侧重于长时间运行中的高性能和高吞吐量。 + - 不同模式,垃圾回收器不同,内存占用不同 + + ```Plain + openjdk version "1.8.0_322" + OpenJDK Runtime Environment (Zulu 8.60.0.21-CA-macos-aarch64) (build 1.8.0_322-b06) + OpenJDK 64-Bit Server VM (Zulu 8.60.0.21-CA-macos-aarch64) (build 25.322-b06, mixed mode) + ``` + + 我们可以在jvm.cfg文件中切换JRE为Server VM或是Client VM,默认路径为: + + ```Plain + JDK安装目录/jre/lib/jvm.cfg + ``` + + 比如我们需要将当前模式切换为客户端模式,那么我们可以这样编辑: + + ```Plain + -client KNOWN + -server IGNORE + ``` + + +### ParNew收集器 + +- **多线程**,需要STW,用于新生代:标记复制算法 + - 详情 + + - 这款垃圾收集器相当于是Serial收集器的多线程版本,它能够支持**多线程垃圾**收集: + + ![image-20230306165542516](垃圾回收机制/tQwzAK1XU5gdl8Y.webp) + + - 如何保证线程安全 + + ### 1. **GC Barrier (垃圾收集屏障)** + + - **概念**:与 Parallel Scavenge 类似,ParNew 也使用 GC Barrier 来同步线程之间的操作,防止在并发垃圾回收过程中发生数据不一致的情况。 + - **作用**:这些屏障确保了在对象复制和移动的过程中,其他线程的操作不会破坏对象的状态。 + + ### 2. **卡表 (Card Table)** + + - **概念**:ParNew 同样使用卡表来追踪新生代中的对象引用变化。 + - **作用**:卡表机制允许垃圾收集器在多线程并发时快速确定哪些内存区域需要重新扫描,减少了不必要的开销,从而保持了线程安全。 + + ### 3. **写屏障 (Write Barrier)** + + - **概念**:写屏障在 ParNew 中被用于确保对象引用的修改在并行回收时被正确地记录下来。 + - **作用**:当对象的引用在新生代垃圾回收过程中被修改时,写屏障可以防止引用的状态被多个线程同时修改,确保这些操作的原子性和一致性。 + + ### 4. **对象头的锁机制** + + - **概念**:ParNew 收集器在并行垃圾回收过程中,也利用对象头中的锁信息来管理对象的并发访问。 + - **作用**:当多个线程同时访问或修改某个对象时,对象头的锁机制确保只有一个线程可以对其状态进行修改,从而避免数据竞争问题。 + + ### 5. **停止-the-world (STW) 机制** + + - **概念**:ParNew 收集器在某些阶段仍然会使用 STW 机制来暂停所有应用线程,确保垃圾回收的操作在一个安全的环境下执行。 + - **作用**:STW 机制确保了复杂操作(如对象复制或移动)可以在不受干扰的情况下进行,从而保证线程安全。 + + ### 6. **CAS(Compare-And-Swap)操作** + + - **概念**:ParNew 也利用了 CAS 操作来保证某些关键操作的原子性。 + - **作用**:CAS 操作用于确保标记或更新对象引用时,不会因为并发访问导致数据不一致。 + + ### 7. **线程本地分配缓冲区 (Thread-Local Allocation Buffers, TLABs)** + + - **概念**:ParNew 收集器中,TLAB 允许每个线程在自己的缓冲区内进行对象分配,从而避免了多个线程在分配对象时的竞争。 + - **作用**:TLAB 不仅提高了对象分配的效率,还减少了由于并发分配带来的锁争用问题。 + + ### 8. **工作窃取(Work Stealing)** + + - **概念**:ParNew 收集器使用工作窃取机制来平衡线程的工作负载,防止某些线程过载而其他线程空闲的情况。 + - **作用**:工作窃取机制提高了垃圾回收过程中的并行性,并减少了线程竞争,从而提高了整体效率和线程安全性。 + + 除了多线程支持以外,其他内容基本与Serial收集器一致,并且目前某些==JVM默认的服务端模式新生代收集器就是使用的ParNew收集器==。 + + + + +### Parallel Scavenge/Parallel Old收集器 + +- 多线程,**关注吞吐量**,新生代:标记复制算法,老年代:标记整理算法 + - 详情 + + - Parallel Scavenge同样是一款==面向新生代的垃圾收集器==,同样采用==标记复制算法==实现,在JDK6时也推出了其老年代收集器Parallel Old,采用标记整理算法实现: + + ![image-20230306165555265](垃圾回收机制/npiZY1Q4e5EhGuB.webp) + + - 与ParNew收集器不同的是,它会自动衡量一个吞吐量,并根据吞吐量来决定每次垃圾回收的时间,这种自适应机制,能够很好地权衡当前机器的性能,根据性能选择最优方案。 + - 吞吐量问题 + 该收集器的一个显著特点是它可以通过参数调整,以优化吞吐量(应用程序运行的总时间占总时间的比例)。它允许开发者通过设置 `-XX:MaxGCPauseMillis`(最大 GC 停顿时间)和 `-XX:GCTimeRatio`(GC 时间比例)来控制 GC 的行为,找到停顿时间和吞吐量之间的平衡。 + + 目前JDK8采用的就是这种 Parallel Scavenge + Parallel Old 的垃圾回收方案。 + + +> [!important] 以上收集器,都会触发 +> +> **Stop-the-World (STW)** 事件,也就是需要**将所有应用线程暂停**。这是为了确保垃圾回收过程中对象的内存状态一致性,防止并发修改导致数据错误或崩溃 + +### CMS收集器 + +- 基于**标记-清除算法**,以获取最短回收停顿时间为目标,短暂STW +- 并发收集,减少停顿时间,工作线程一起运行 + - 详情 + + - 在JDK1.5,HotSpot推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器:CMS(Concurrent-Mark-Sweep)收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的**并发**(注意这里的并发和之前的并行是有区别的,并发可以理解为**同时运行用户线程和GC线程**,而并行可以理解为多条GC线程同时工作(线程暂停))收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。 + + 它主要采用标记清除算法: + + ![image-20230306165610810](垃圾回收机制/qhmndIQPKarvDso.webp) + + 它的垃圾回收分为4个阶段: + + - 初始标记(**需要暂停用户线程**):这个阶段的主要任务仅仅只是标记出GC Roots能直接关联到的对象,速度比较快,不用担心会停顿太长时间。 + - 并发标记:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是**不需要停顿用户线程,可以与垃圾收集线程一起并发运行**。 + - 重新标记(需要暂停用户线程):由于并发标记阶段可能某些用户线程会导致标记产生变得,因此这里需要再次暂停所有线程进行并行标记,这个时间会比初始标记时间长一丢丢。 + - 并发清除:最后就可以直接将所有标记好的无用对象进行删除,因为这些对象程序中也用不到了,所以可以与用户线程并发运行。 + + 虽然它的优点非常之大,但是缺点也是显而易见的,我们之前说过,标记清除算法会产生大量的内存碎片,导致可用连续空间逐渐变少,长期这样下来,会有更高的概率触发Full GC,并且在与用户线程并发执行的情况下,也会占用一部分的系统资源,导致用户线程的运行速度一定程度上减慢。 + + 不过,如果你希望的是最低的GC停顿时间,这款垃圾收集器无疑是最佳选择,不过自从G1收集器问世之后,CMS收集器不再推荐使用了。 + + +### Garbage First (G1) 收集器⭐ + +- **堆划分为多个区域(Region)** +- 并发标记,基于复制的年轻代回收,基于复制和整理的老年代增量回收 + - 详情 + + - 历史:此垃圾收集器也是一款划时代的垃圾收集器,在JDK7的时候正式走上历史舞台,它是一款主要面向于服务端的垃圾收集器,并且在JDK9时,取代了JDK8默认的 Parallel Scavenge + Parallel Old 的回收方案。 + - 分区:我们知道,我们的垃圾回收分为`Minor GC`、`Major GC` 和`Full GC`,它们分别对应的是新生代,老年代和整个堆内存的垃圾回收,而G1收集器巧妙地绕过了这些约定,它将整个Java堆划分成`2048`个大小相同的独立`Region`块,每个`Region块`的大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,且都为2的N次幂。所有的`Region`大小相同,且在JVM的整个生命周期内不会发生改变。 + - 那么分出这些`Region`有什么意义呢?每一个`Region`都可以根据需要,自由决定扮演哪个角色(Eden、Survivor和老年代),收集器会根据对应的角色采用不同的回收策略。此外,G1收集器还存在一个Humongous区域,它专门用于存放大对象(一般认为大小超过了Region容量一半的对象为大对象)这样,新生代、老年代在物理上,不再是一个连续的内存区域,而是到处分布的。 + + ![image-20230306165629129](垃圾回收机制/yHxXZP92DKc5Qre.webp) + + 它的**回收过程与CMS大体类似**: + + ![image-20230306165641872](垃圾回收机制/kXRMlt5iFDvjq8y.webp) + + 分为以下四个步骤: + + - 初始标记(暂停用户线程):仅仅只是标记一下GC Roots==能直接关联到的对象==,并且修改TAMS指针的值(区分了初始阶段之前之后的对象,之前的对象需要进行分析),让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。 + - 并发标记:从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。 + - 最终标记(暂停用户线程):对用户线程做一个短暂的暂停,用于处理并发标记阶段漏标的那部分对象。 + - 筛选回收:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多个收集器线程并行完成的。 \ No newline at end of file diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/48ZguJqIK6ojEba.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/48ZguJqIK6ojEba.webp new file mode 100644 index 0000000..13cb968 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/48ZguJqIK6ojEba.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/4MIORcEDzquZFia.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/4MIORcEDzquZFia.webp new file mode 100644 index 0000000..72ff5b9 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/4MIORcEDzquZFia.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/4UncSVvO1P2xWKL.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/4UncSVvO1P2xWKL.webp new file mode 100644 index 0000000..c5bfa7a Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/4UncSVvO1P2xWKL.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/71PqlEfiGgBSLae.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/71PqlEfiGgBSLae.webp new file mode 100644 index 0000000..cb3ab91 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/71PqlEfiGgBSLae.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/8sSvloxtKINFYz4.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/8sSvloxtKINFYz4.webp new file mode 100644 index 0000000..5a9c691 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/8sSvloxtKINFYz4.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/JeifGj6kmVAY2PR.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/JeifGj6kmVAY2PR.webp new file mode 100644 index 0000000..05af48e Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/JeifGj6kmVAY2PR.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/KdZpLNTzEitCOB3.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/KdZpLNTzEitCOB3.webp new file mode 100644 index 0000000..8557b59 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/KdZpLNTzEitCOB3.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/OZrKbUm39lfaAgv.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/OZrKbUm39lfaAgv.webp new file mode 100644 index 0000000..264a306 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/OZrKbUm39lfaAgv.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/WYSGI5xCh8mOBcK.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/WYSGI5xCh8mOBcK.webp new file mode 100644 index 0000000..f673b49 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/WYSGI5xCh8mOBcK.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/d8anmBcLrW3iyJl.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/d8anmBcLrW3iyJl.webp new file mode 100644 index 0000000..102790b Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/d8anmBcLrW3iyJl.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/iWqvCUySGk8Qlad.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/iWqvCUySGk8Qlad.webp new file mode 100644 index 0000000..6d8f94a Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/iWqvCUySGk8Qlad.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/kXRMlt5iFDvjq8y.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/kXRMlt5iFDvjq8y.webp new file mode 100644 index 0000000..af0317f Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/kXRMlt5iFDvjq8y.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/npiZY1Q4e5EhGuB.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/npiZY1Q4e5EhGuB.webp new file mode 100644 index 0000000..f915c02 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/npiZY1Q4e5EhGuB.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/qhmndIQPKarvDso.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/qhmndIQPKarvDso.webp new file mode 100644 index 0000000..51bbf63 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/qhmndIQPKarvDso.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/stqTu8gD3ykixva.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/stqTu8gD3ykixva.webp new file mode 100644 index 0000000..90714f8 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/stqTu8gD3ykixva.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/tQwzAK1XU5gdl8Y.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/tQwzAK1XU5gdl8Y.webp new file mode 100644 index 0000000..72e175c Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/tQwzAK1XU5gdl8Y.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/v79TEdmyo1njKwl.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/v79TEdmyo1njKwl.webp new file mode 100644 index 0000000..8b68813 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/v79TEdmyo1njKwl.webp differ diff --git a/posts/blog/编程技术/java/JVM/垃圾回收机制/yHxXZP92DKc5Qre.webp b/posts/blog/编程技术/java/JVM/垃圾回收机制/yHxXZP92DKc5Qre.webp new file mode 100644 index 0000000..8133892 Binary files /dev/null and b/posts/blog/编程技术/java/JVM/垃圾回收机制/yHxXZP92DKc5Qre.webp differ diff --git a/posts/blog/编程技术/java/JavaSE/IO流/File类.md b/posts/blog/编程技术/java/JavaSE/IO流/File类.md new file mode 100644 index 0000000..fc05341 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/File类.md @@ -0,0 +1,41 @@ +--- +title: File类 +date: 2024-08-20 +tags: [IO流] +--- + + +## 概述 + +- 表示路径的抽象形式,可以是文件、目录,因此File代表了某个文件或某个目录 +- File类不是IO流,和IO的四个头领没有关系,因此通过File是无法读写文件 + +## 详解 + +- 构造方法:`File(String pathname)` +- 常用方法: + + |适用|方法|描述| + |---|---|---| + |通用|boolean delete()|删除File| + |通用|boolean exists()|是否存在File| + |通用|String getName()|获取File的名字| + |通用|String getAbsolutePath()|获取File的绝对路径| + |通用|boolean renameTo(File dest)|移动或重命名File| + |通用|String getParent()|获取父路径| + |通用|long lastModified()|获取最后一次修改时间| + |通用|boolean isAbsolute()|是否传入绝对路径| + |通用|boolean isHidden()|是否隐藏| + |文件|boolean createNewFile()|创建文件| + |文件|boolean isFile()|是否是文件| + |文件|long length()|文件大小| + |文件|boolean setReadOnly()|设置为可读| + |文件|boolean setWritable(boolean writable)|设置为可写| + |文件夹|File[] listFiles();|获取子文件| + |文件夹|File[] listFiles(FilenameFilter filter)|筛选子文件| + |文件夹|boolean mkdir()|创建文件夹| + |文件夹|boolean mkdirs()|创建多级文件夹| + |文件夹|boolean isDirectory()|判断是否是文件夹| + + - `renameTo(File dest)` 详解 + - 把File移动到dest,把原文件删除,重命名只要移到同一个文件夹不同名字即 \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/IO流/IO流.md b/posts/blog/编程技术/java/JavaSE/IO流/IO流.md new file mode 100644 index 0000000..0e58184 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/IO流.md @@ -0,0 +1,122 @@ +--- +title: IO流 +date: 2024-08-16 +tags: [IO流] +--- + + +## 概述 + +- 什么是IO流? + - IO流指的是:程序中数据的流动。数据可以从内存流动到硬盘,也可以从硬盘流动到内存 + - Java中IO流最基本的作用是:完成文件的读和写 + - Java中已经将IO流实现了,在**java.io**包下,可以直接使用 + +![image 46.png](IO流/image46.png) + +- IO流的分类? + + - 根据==数据流向==分为:输入和输出是相对于内存而言的,将内存理解为大脑进行类比 + + ①输入流:从硬盘到内存(输入又叫做读:`read` ) + + ②输出流:从内存到硬盘(输出又叫做写:`write` ) + + - 根据==读写数据形式==分为: + + ①字节流:一次读取一个字节,适合读取非文本数据。例如图片、声音、视频等文件。(当然字节流是万能的。什么都可以读和写) + + ②字符流:一次读取一个字符,只适合读取普通文本。不适合读取二进制文件。因为字符流统一使用Unicode编码,可以避免出现编码混乱的问题 + + - 根据流在IO操作中的==作用和实现方式==来分类: + + ①**节点流**:节点流负责数据源和数据目的地的连接,是IO中最基本的组成部分 + + - 字节流、内存流 + + ②**处理流**:处理流对节点流进行装饰/包装,提供更多高级处理操作,方便用户进行数据处理,使用时要传入节点流,根据数据来源(文件、网络),确定节点流 + + + **注意:Java的所有IO流中凡是以Stream结尾的都是字节流。凡是以Reader和Writer结尾的都是字符流** + + +## IO流的体系结构 + +- 下图是常用的IO流。实际上IO流远远不止这些。 + - `InputStream`:字节输入流 + - `OutputStream`:字节输出流 + - `Reader`:字符输入流 + - `Writer`:字符输出流以上4个流都是==**抽象类**==,是所有IO流的四大头领!!! +- 所有的输出流都实现了`Flushable`接口,都有`flush()`方法,flush方法的作用是,将缓存清空,全部写出,如果在流关闭前需要手动控制数据写入,可以在适当的时候调用 `flush()`,以确保数据即时写入,~~最后在关闭前养成好习惯,以防数据丢失~~ +- 所有的流都实现了`Closeable`接口,都有`close()`方法,流用完要关闭 + - 流操作是与操作系统进行交互,所以不使用应及时关闭,不浪费资源 + - 因为`Closeable`继承了`AutoCloseable` 接口,所以使用`try-with-resource`可以自动关闭,并且`close`方法中会调用`flush`方法 + +![image 1 26.png](IO流/image126.png) + +## 具体实现 + +[[关于File的读写]] + +[[文件路径问题]] + +[[缓冲流]] + +[[转换流]] + +[[数据流]] + +[[对象流]] + +[[打印流]] + +[[File类]] + +[[读取属性配置文件]] + +[[装饰器设计模式(Decorator Pattern)]] + +[[压缩和解压缩流]] + +[[字节数组流]] + +[[对象克隆]] + +## 总结 + +- 四大头领:抽象类 + - InputStream + - OutputStream + - Reader + - Writer +- File相关的:文本文件、二进制文件 + - FileInputStream + - FileOutputStream + - FileReader + - FileWriter +- 缓冲流相关的:提高效率 + - BufferedInputStream + - BufferedOutputStream + - BufferedReader + - BufferedWriter +- 转换流相关的:处理非系统编码的文本 + - InputStreamReader + - OutputStreamWriter +- 打印流相关的:输出控制、或文本 + - PrintStream + - PrintWriter +- 对象相关的:序列化Java对象 + - ObjectInputStream + - ObjectOutputStream +- 数据相关的:读写Java基本数据类型 + - DataInputStream + - DataOutputStream +- 字节数组相关的:用于内存数组操作 + - ByteArrayInputStream + - ByteArrayOutputStream +- 压缩和解压缩相关的:解、压缩文件 + - GZIPInputStream + - GZIPOutputStream +- 线程相关的: + - PipedInputStream + - PipedOutputStream \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/IO流/IO流/image126.png b/posts/blog/编程技术/java/JavaSE/IO流/IO流/image126.png new file mode 100644 index 0000000..d740eca Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/IO流/IO流/image126.png differ diff --git a/posts/blog/编程技术/java/JavaSE/IO流/IO流/image46.png b/posts/blog/编程技术/java/JavaSE/IO流/IO流/image46.png new file mode 100644 index 0000000..6bef540 Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/IO流/IO流/image46.png differ diff --git a/posts/blog/编程技术/java/JavaSE/IO流/关于File的读写.md b/posts/blog/编程技术/java/JavaSE/IO流/关于File的读写.md new file mode 100644 index 0000000..9b02ed3 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/关于File的读写.md @@ -0,0 +1,166 @@ +--- +title: 关于File的读写 +date: 2024-08-16 +tags: [IO流] +--- + + +## FileInputStream + +- **字节流**,可以读取如何文件 +- 构造方法:`FileInputStream(String name)` 传入文件地址 +- 常用方法 + + |方法|描述| + |---|---| + |int read()|从文件读一个字节| + |int read(byte[] b)|从文件读取字节数组范围的字节| + |int read(byte[] b, int off, int len)|与上一个类似,但指定开始位置和长度| + |long skip(long n)|==跳过n个字节,不能回跳==| + |int available()|返回流中剩余的==估计==字节数量| + |void close()|关闭流| + + - `read()` 的问题细节 + - 从文件读取一个字节(8个二进制位),返回值为该字节对应的int值,如果读不到任何数据返回-1 + - 为什么不返回byte,因为范围不够,全部用来表示二进制,-1就没有对应二进制了 + - int对应的数字的值范围(0-255)对应byte范围(-129-127) + - `read(byte[] b)` 的问题细节 + + - 一次读取多个字节,如果文件内容足够多,则一次最多读取b.length个字节。返回值是读取到字节总数。如果没有读取到任何数据,则返回 -1 + - 注意数组可能读不满,还遗留上一次的值 + + + + - `available()` 的问题细节 + - 可以使用这个估计文件的字节数 + +## FileOutputStream + +- 文件字节输出流 +- 常用构造方法: + - `FileOutputStream(String name)` 创建输出流,==先将文件清空,再不断写入== + - `FileOutputStream(String name, boolean append)` 创建输出流,传入==true表示追加== +- 常用方法: + + |方法|描述| + |---|---| + |void write(int b)|写入一个字节| + |void write(byte[] b)|将字节数组中所有数据全部写出| + |void write(byte[] b, int off, int len)|将字节数组的一部分写出| + |void close()|关闭流| + |void flush()|刷新| + + - `write(int b)` 的问题细节 + - 将写入内容转为二进制(文字需要编码),再分成多个单字节正整数(0-255)进行写入 + + - 假如你想写入“帅”这个字符,先使用char表示UTF-16编码,然后将char传入(自动转成)转成int值 + + ```Java + //先转成UTF-16的char + char[] chars = "帅".toCharArray(); + for (char b:chars) { + //char自动会转成int + writer.write(b); + } + ``` + + - 为什么是int类型,个人认为是为了与read对应,避免手动类型转换(处理数字) + +## 字节流小结 + +- 字节流属于**节点流**,==读写过程没有经过解码== +- 使用FileInputStream和FileOutputStream完成文件的复制 + +- 乱码问题 + - 使用FileInputStream读取的文件中有中文时,有可能读取到中文某个汉字的一半,在将==byte[]数组转换为String时==可能出现乱码问题,因此FileInputStream不太适合读取纯文本。 + - 由此得出,乱码与文件的字节读写流没有关系,问题出现在转字符串的过程,因为String底层是char数组也就是UTF-16编码(unicode码),所以这是一个解码的过程,byte中无效的二进制将导致乱码 + +## FileReader + +- 文件字符输入流 + - 底层原理 + + - 最初Java是不支持对文本文件处理的,为了弥补这个缺憾而引入了Reader和Writer两个类 + - FileReader直接继承自InputStreamReader,而InputStreamReader是一个输入转换流,将输入字节转换为字符形式,这说明FileReader底层还是==依靠字节流读取==,并且维护了一个字符数组(类似缓冲区),java帮我们进行了编码转换的工作,底层内容: + + ![image 55.png](关于File的读写/image55.png) + + FileReader的构造器 + + - 构造器可以设置不同字符集编码,不过个数不多 + + ```Java + FileReader fileReader = new FileReader("D:\\result\\IO\\test.txt", StandardCharsets.ISO_8859_1); + ``` + +- 常用的构造方法: + - `FileReader(String fileName)` 传入地址 + - `FileReader(String fileName, Charset charset)` 传入地址,**指定解码字符集** +- 常用的方法: + + |方法|描述| + |---|---| + |int read()|读取一个字符| + |int read(char[] cbuf)|从文件读取字符数组范围的字节| + |int read(char[] cbuf, int off, int len)|与上一个类似,但指定开始位置和长度| + |long skip(long n)|==跳过n个字节,不能回跳==| + |void close()|关闭流| + + - `read()` 原理 + + - 解析完整码点,解成Unicode码,转成utf-16编码 + - 底层使用字节流,使用byte数组读取多个字节(==类似缓冲区原理==),将字节进行解析成一个个合法的码点(对应你设置的编码`charset`)时,解码出Unicode码,然后转成UTF-16(char对应的编码),如果该Unicode码值大于2个字节(0xFFFF),那么按UTF-16的编码规则,使用代理对( ),类中保存一部分,返回一部分,下一次调用返回,剩余部分,如果想要复原该字符,则使用Char数组接收2个值,再用String拼接 + + ![image 1 33.png](关于File的读写/image133.png) + + 调用字节流的byte数组方法 + + ```Java + char[] cs=new char[]{'\uD83D','\uDE04'}; + String smile=new String(cs); + System.out.println(smile); + ``` + + - 综上也说明了为什么返回int,而不是byte或char(范围不够) + +## FileWriter + +- 文件字符输出流 + - 底层是OutputStream +- 常用的构造方法 + - `FileWriter(String fileName)` 创建输出流,**先将文件清空,再不断写入** + - `FileWriter(String fileName, Charset charset)` **设置写入编码格式** + - `FileWriter(String fileName, boolean append)`创建输出流,传入**true表示追加** +- 常用的方法: + + | | | + |---|---| + |方法|描述| + |void write(char[] b)|将字符数组中所有数据全部写出| + |void write(char[] cbuf, int off, int len)|将字符数组的一部分写出| + |void write(String str)|将字符串写入数组| + |void write(String str, int off, int len)|指定字符串开始和结束位置,将这部分写入文件| + |Writer append(CharSequence csq, int start, int end)|和上面一样| + |void close()|关闭流| + |void flush()|刷新| + + - `write(String str)`详解 + - 底层将字符串转成char数组,然后解码为Unicode值,再按你设置的编码格式进行编码为byte数组,然后进行字节流的写入 + +## 字符流总结 + +- 字符流是**包装流**(处理流),继承转换流,封装了字节流 +- 宏观上是对字符进行了读写,底层是对二进制进行了一系列处理 +- 底层依赖字符流,为我们省去了**获取完整码点**,**解码过程**(自定义编码 —>Unicode码 —>UTF-16)的过程,由于char字符面对占用字节数大于2的字符时,不能一次性表示一个字符,所以一般定义一个StringBuilder进行存储,最后一起输出,保证了数据的完整性 + - 当读取文件的解码方式与自定义编码不一致时,会导致乱码,同理,但追加写入字符,该字符的编码方式与文件的编码方式不一致时,也会导致乱码 + +```Java +//查看系统默认编码格式 +Charset.defaultCharset().displayName(); +``` + +- 使用FileReader和FileWriter拷贝普通文本文件 + +## 总结 + +- 对于文本文件,使用字符流,其他用字节流 \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/IO流/关于File的读写/image133.png b/posts/blog/编程技术/java/JavaSE/IO流/关于File的读写/image133.png new file mode 100644 index 0000000..d1aa304 Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/IO流/关于File的读写/image133.png differ diff --git a/posts/blog/编程技术/java/JavaSE/IO流/关于File的读写/image55.png b/posts/blog/编程技术/java/JavaSE/IO流/关于File的读写/image55.png new file mode 100644 index 0000000..2c9580d Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/IO流/关于File的读写/image55.png differ diff --git a/posts/blog/编程技术/java/JavaSE/IO流/压缩和解压缩流.md b/posts/blog/编程技术/java/JavaSE/IO流/压缩和解压缩流.md new file mode 100644 index 0000000..3d887ba --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/压缩和解压缩流.md @@ -0,0 +1,53 @@ +--- +title: 压缩和解压缩流 +date: 2024-08-20 +categories: [编程技术, java, JavaSE, IO流] +tags: [IO流] +--- + + +## 概述 + +- **包装流**,对File进行压缩或解压 +- 压缩后的文件格式为 **.gz 格式** + +## GZIPOutputStream(压缩) + +- 构造方法:`GZIPOutputStream(OutputStream out)` +- 使用:将读出来的文件,通过它压缩写入,代码如下 + + ```Java + String path="D:\\result\\IO\\javaIo\\zip\\hh.txt"; + //读文件 + FileInputStream fileInputStream=new FileInputStream(path); + //创建压缩流 + GZIPOutputStream gzipOutputStream=new GZIPOutputStream(new FileOutputStream(path+".gz")); + byte[] bytes=new byte[1024]; + int t; + while ((t=fileInputStream.read(bytes))!=-1){ + gzipOutputStream.write(bytes,0,t); + } + // 在压缩完所有数据之后调用finish()方法 + gzipOutputStream.finish(); + fileInputStream.close(); + gzipOutputStream.close(); + ``` + + - 关键点: + - 压缩文件名为,**源文件名(包括后缀)+.gz,**名字可以修改但后缀不变 + - 在压缩完所有数据之后调用`**finish()**`方法,以确保所有未压缩的数据都被刷新到输出流中,并生成必要的 Gzip 结束标记,标志着压缩数据的结束。 + +## GZIPInputStream(解压缩) + +- 使用`GZIPInputStream`可以将`.gz` 格式的压缩文件解压 +- 核心代码:没有`finish`方法 + +```Java +GZIPInputStream gzip = new GZIPInputStream(new FileInputStream("d:/test.txt.gz")); +FileOutputStream out = new FileOutputStream("d:/test.txt"); +byte[] bytes = new byte[1024]; +int readCount = 0; +while((readCount = gzip.read(bytes)) != -1){ +out.write(bytes, 0, readCount); +} +``` \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/IO流/字节数组流.md b/posts/blog/编程技术/java/JavaSE/IO流/字节数组流.md new file mode 100644 index 0000000..243dde7 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/字节数组流.md @@ -0,0 +1,38 @@ +--- +title: 字节数组流 +date: 2024-08-20 +tags: [IO流] +--- + + +## 概述 + +- **节点流**,数据源在**内存**(区别于文件、网络等) + + - 原理:底层是一个`byte`数组,所以不涉及关闭流的操作 + + ![image 59.png](字节数组流/image59.png) + + +## ByteArrayOutputStream + +- 将数据写入到内存中的字节数组当中 +- 构造方法:`new ByteArrayOutputStream()` 直接创建一个字节流 +- 常用方法: + - `write(0xb1)` 写入一个字节 + - `writeBytes(bytes)` 写入整个数组 + - `write(bytes,0,1)` 写入指定范围数组 + - `size()` 获取字节个数 + - `toString(StandardCharsets.UTF_16)` 将字节按设置编码进行解码出字符串 + - `toByteArray()` 获取流中的数组 +- 与其他包装流一起使用,比如数据流、对象流,将读出的内容存在字节流中 + +## ByteArrayInputStream + +- 读取内存中某个字节数组中的数据 +- 构造方法:`new ByteArrayInputStream(byte[] b)` 传入byte数组 +- 常用方法: + - `read()` 读一个字节 + - `read(byte[] b)` 读一个字节数组 + - `read(byte[] b,int off,int len)` 读范围内的字节数组 +- 与其他包装流一起使用,==前提是里面要有数据== \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/IO流/字节数组流/image59.png b/posts/blog/编程技术/java/JavaSE/IO流/字节数组流/image59.png new file mode 100644 index 0000000..4f19721 Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/IO流/字节数组流/image59.png differ diff --git a/posts/blog/编程技术/java/JavaSE/IO流/对象克隆.md b/posts/blog/编程技术/java/JavaSE/IO流/对象克隆.md new file mode 100644 index 0000000..935e047 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/对象克隆.md @@ -0,0 +1,22 @@ +--- +title: 对象克隆 +date: 2024-08-20 +tags: [IO流] +--- + + +## 概述 + +- 之前使用Object类中clone方法进行克隆,这里介绍使用IO操作进行克隆 + - 原理是:将要克隆的Java对象写到内存中的字节数组中,再从内存中的字节数组中读取对象,读取到的对象就是一个深克隆 +- 代码如下,要**实现序列号的接口**(`Serializable`),不需要使用`cloneable`接口 + + ```Java + //先写 后读 + ByteArrayOutputStream buffOut = new ByteArrayOutputStream(); + ObjectOutputStream outputStream=new ObjectOutputStream(buffOut); + outputStream.writeObject(stu); + byte[] bytes = buffOut.toByteArray(); + ObjectInputStream inputStream=new ObjectInputStream(new ByteArrayInputStream(bytes)); + Student stuClone = (Studnet)inputStream.readObject(); + ``` \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/IO流/对象流.md b/posts/blog/编程技术/java/JavaSE/IO流/对象流.md new file mode 100644 index 0000000..b0bcf04 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/对象流.md @@ -0,0 +1,47 @@ +--- +title: 对象流 +date: 2024-08-20 +tags: [IO流] +--- + + +## 概述 + +- **包装流**,可以完成对象的==序列化和反序列化== + - 序列化(Serial):将Java对象转换为**字节序列**,将对象进行文件存储、网络传输,使用`ObjectOutputStream`序列化 + - 反序列化(DeSerial):将字节序列转换为Java对象,使用`ObjectInputStream`进行反序列化 +- 特点:参与序列化和反序列化的java对象必须实现一个标志性接口:`java.io.``**Serializable**` + - **序列化版本号**的属性(`private static final long`):`**serialVersionUID**` ,实现了Serializable接口的类,编译器会自动给该类添加该属性 + - 每个类的序列号版本号是唯一的,生成规则:类名、类中实现的接口、字段(field)、方法(method)包括类型、名称、修饰符等,与位置无关 + - 在java中,是通过“类名 + 序列化版本号”来进行类的区分的 + - serialVersionUID实际上是一种安全机制,在反序列化的时候,JVM会去检查存储Java对象的文件中的class的序列化版本号是否和当前Java程序中的class的序列化版本号是否一致,如果一致则可以反序列化。如果不一致则报错,`InvalidClassException`异常 + - 需要系列化的类,建议最好**手动定义版本号** + - 原因如下 + - 如果一个类实现了Serializable接口,还是建议将序列化版本号固定死,建议显示的定义出来,原因是:类有可能在开发中升级(改动),升级后会重新编译,如果没有固定死,编译器会重新分配一个新的序列化版本号,导致之前序列化的对象无法反序列化。显示定义序列化版本号的语法:`private static final long serialVersionUID = XXL;`为了保证显示定义的序列化版本号(名称、修饰符)不会写错,建议使用 @java.io.Serial 注解进行标注。并且使用它还可以帮助我们随机生成序列化版本号 + - **不参与序列化的属性**需要使用瞬时关键字修饰:`**transient**` + +## ObjectOutputStream + +- 构造方法:`ObjectOutputStream(OutputStream out)` +- 常用方法 + - `writeObject(Object obj)` 写入对象 + +## ObjectInputStream + +- 构造方法:`ObjectInputStream(InputStream in)` +- 常用方法 + + - `readObject()` 返回对象 + - 判断读到文件结尾? + + ```SQL + while (true) { + try { + Object obj = objectInputStream.readObject(); + // 处理读取到的对象 + } catch (EOFException e) { + // 到达文件末尾,退出循环 + break; + } + } + ``` \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/IO流/打印流.md b/posts/blog/编程技术/java/JavaSE/IO流/打印流.md new file mode 100644 index 0000000..11a77ff --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/打印流.md @@ -0,0 +1,71 @@ +--- +title: 打印流 +date: 2024-08-20 +tags: [IO流] +--- + + +## 概述 + +- **包装流**,用于输出数据到不同目标(如控制台、文件、网络连接等)的类,常用于打印文本数据,常见实例是 `System.out` + - 原理和效果与字符流(==类似FileWriter==)差不多,只不过先**将数据包装成字符串**处理 + - 控制台可以理解为一个文本编辑器,通过测试发现**控制台使用的是默认编码方式** +- 提供便捷的打印方法和格式化输出,主要打印内容到文件或控制台 + - 便捷在哪里? + - 直接输出各种数据类型,其实底层转成字符串处理 + + ![image 57.png](打印流/image57.png) + + - 自动刷新和自动换行(println方法) + - 支持字符串转义 + - 自动编码(自动根据环境选择合适的编码方式) + +## PrintStream + +- 构造方法: + - `PrintStream(OutputStream out)` 使用默认编码方式 + - `PrintStream(String fileName, Charset charset)` 指定编码规则 +- 常用方法:print原理: + - `print(Type x)` 写入字符 + - `println(Type x)` 写入字符+换行符 + - `write(byte buf[])` 写入字节 +- 格式化输出,调用printf方法 + - %s 表示字符串 + - %d 表示整数 + - %f 表示小数(%.2f 这个格式就代表保留两位小数的数字) + - %c 表示字符 + +## PrintWriter + +- 构造方法: + - `PrintWriter(OutputStream out)` + - `PrintWriter(Writer out)` + - `PrintWriter(String fileName, Charset charset)` +- ==**无写入字节的方法**== +- 同样,也可以支持格式化输出,调用`printf`方法。 + +## 标准输出流 + +- `System.out`获取到的`PrintStream`就是一个标准输出流。 +- 特点: + - 标准输出流是用来向**控制台上输出**的(普通的输出流,是向文件和网络等输出的) + - 标准输出流==不需要关闭====(==它是一个系统级的全局的流,JVM负责最后的关闭),也不需要手动刷新 +- 应用 + - 基本的打印输出 + - 生成日志,修改输出流的方向(`System.setOut(PrintStream out)`),让其指向文件 + +## 标准输入流 + +- `System.in`获取到的`**InputStream**`就是一个标准输入流 +- 特点 + - 标准输入流是用来接收**控制台上的输入**的(普通的输入流,是获得文件或网络中的数据) + - 标准输入流不需要关闭(它是一个系统级的全局的流,JVM负责最后的关闭) +- 使用和**字节流**一样,读取的内容是系统默认编码(UTF-8)下的字节,也可以使用**BufferedReader**对标准输入流进行包装,这样可以方便的接收用户在控制台上的输入(这种方式太麻烦了,因此JDK中提供了更好用的==**Scanner**==) + +```Java + +BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); +String s = br.readLine(); +``` + +- 当然,你也可以修改输入流的方向(System.setIn()),让其指向文件,无意义 \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/IO流/打印流/image57.png b/posts/blog/编程技术/java/JavaSE/IO流/打印流/image57.png new file mode 100644 index 0000000..60e513b Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/IO流/打印流/image57.png differ diff --git a/posts/blog/编程技术/java/JavaSE/IO流/数据流.md b/posts/blog/编程技术/java/JavaSE/IO流/数据流.md new file mode 100644 index 0000000..5c8e93f --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/数据流.md @@ -0,0 +1,57 @@ +--- +title: 数据流 +date: 2024-08-20 +tags: [IO流] +--- + + +## 概述 + +- **包装流**,读写 Java 各种基本类型数据专用的流 +- 特点: + - 直接将程序中的数据对应的二进制一个字节一个字节写入文件,除了字符串,其他不需要转码,效率高,程序中是什么样子,原封不动的写出去,文件中直接存储的是二进制 + - 使用`DataOutputStream`写的文件,只能使用`DataInputStream`去读取,并且**读取的顺序需要和写入的顺序一致**,这样才能保证数据恢复原样 + +## DataOutputStream + +- 构造方法:`DataOutputStream(OutputStream out)` +- 写的方法:数值统一为1 + + |方法|描述|文件内容(16进制表示)| + |---|---|---| + |writeByte(int v)|写入整数对应的二进制(1个字节)|01| + |writeShort(int v)|写入整数对应的二进制(2个字节)|00 01| + |writeInt(int v)|写入整数对应的二进制(4个字节)|00 00 00 01| + |writeLong(long v)|写入整数对应的二进制(8个字节)|00 00 00 00 00 00 00 01| + |writeFloat(float v)|写入浮点数对应的二进制(4个字节)|3f 80 00 00| + |writeDouble(double v)|写入浮点数对应的二进制(8个字节)|3f f0 00 00 00 00 00 00| + |writeBoolean(boolean v)|写入布尔值对应的二进制(0、1:1个字节)|01| + |writeChar(int v)|写入字符对应的二进制(UTF-16编码:2个字节)|00 31| + |writeUTF(String str)|写入字符串对应的二进制(UTF-8编码:3个字节以上)|00 01 31| + + - 将整数(反码)、浮点数(特定规则)、字符(unicode码点:一个整数)对应的二进制,进行按位操作,**一个字节一个字节进行写** + + - `writeChar(int v)`详解 + - 传入字符(unicode码点)自动转成int值(4个字节),将多余的字节,通过按位操作进行移除,然后分为2个字节(UTF-16:2个字节)直接写入 + - 所以使用UTF-16 LE编码打开纯字符的文件,可以直接查看 + - `writeUTF(String str)`详解 + - 写入:2个字节+x个字节,其中2个字节表示x(即字节个数) + - 底层原理: + - 字符串对应的字符数组,每个字符(UTF-16编码)做为**码点**进行转码为==多个字节==(UTF-8编码:1-4字节),, 最后将多个字节存入文件 + +## DataInputStream + +- 构造方法:`DataInputStream(InputStream in)` +- 读的方法: + + |方法|描述| + |---|---| + |readByte()|读出byte类型的整数| + |readShort()|读出short类型的整数| + |readInt()|读出int类型的整数| + |readLong()|读出long类型的整数| + |readFloat()|读出float类型的浮点数| + |readDouble()|读出double类型的浮点数| + |readBoolean()|读出boolean类型的值| + |readChar()|读出char类型的值| + |readUTF()|读出String类型的值| \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/IO流/文件路径问题.md b/posts/blog/编程技术/java/JavaSE/IO流/文件路径问题.md new file mode 100644 index 0000000..e5f3e88 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/文件路径问题.md @@ -0,0 +1,34 @@ +--- +title: 文件路径问题 +date: 2024-08-16 +tags: [IO流] +--- + + +- 绝对路径:`"D:/result/IO/a.txt"` +- 相对路径: + + - 在IDEA工具中,默认的当前路径是项目的根下,与`src`同一个级别 + - 多个项目,则需要加上项目名 + + ```Java + ---src + ---file1 + ---A.txt + //使用相对路径表示file1:"file1" + //使用相对路径表示A.txt:"file1/A.txt" + ``` + + - 获取文件的绝对路径,**动态的获取路径**,移植性强 + - 优点:动态的获取路径 + - 缺点:资源必须放在类路径中 + + ```Java + //获取classpath位置 + Thread.currentThread().getContextClassLoader().getResource(".").getPath(); + Thread.currentThread().getContextClassLoader().getResource("文件名").getPath(); + //获得当前线程的类加载器:Thread.currentThread().getContextClassLoader() + //表示从类的根路径下开始加载资源:getResource("文件名") + ``` + + - 从(编译后)类文件位置加载,可以是文件/文件夹,方便加载资源文件 \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/IO流/缓冲流.md b/posts/blog/编程技术/java/JavaSE/IO流/缓冲流.md new file mode 100644 index 0000000..9e98737 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/缓冲流.md @@ -0,0 +1,52 @@ +--- +title: 缓冲流 +date: 2024-08-18 +tags: [IO流] +--- + + +## 概述 + +- 属于**处理流**(包装类)来实现缓冲读取,从而提高性能(测试:效率提升百倍以上) + - 实现原理:在内存中准备了一个缓存(byte数组),在内存中准备了一个缓存,读的时候从缓存中读,写的时候将缓存中的数据一次写出,目的是==减少和磁盘的交互次数== + + |读|写| + |---|---| + |它会自动从底层输入流中读取一定大小的数据,并将数据存储到缓冲区中|当缓冲区达到一定大小或者需要刷新缓冲区时,再将数据一次性输出到目标设备一次写出| + + ![image 56.png](缓冲流/image56.png) + + jdk17中,默认容量 + + - 关闭流只需要关闭最外层的处理流即可,通过源码就可以看到,当关闭处理流时,底层节点流也会关闭 + + ![image 1 34.png](缓冲流/image134.png) + + close方法源码 + +- ==特有方法==,允许我们在(输入流:read)读取数据流时回退到原来的位置(重复读取数据时用) + - `void mark(int readAheadLimit);` 标记位置 + - 在Java21\17版本中,参数无意义。低版本JDK中参数表示在标记处最多可以读取的字符数量,如果你读取的字符数超出的上限值,则调用reset()方法时出现IOException + - `void reset();` 重新回到上一次标记的位置 + - 这两个方法有先后顺序:`先mark再reset`,另外这两个方法不是在所有流中都能用,有些流中有这个方法,但是不能用,使用`markSupported()`进行检查 + +## BufferedInputStream + +- 构造方法:需要传入`InputStream`的子类 +- 常用方法: + +## BufferedOutputStream + +- 构造方法:需要传入`OutputStream`的子类 +- 常用方法: + +## BufferedReader + +- 构造方法:需要传入`Reader`的子类 +- 常用方法: + - 新添方法:`**readLine()**` 读一行(直到遇到回车) + +## BufferedWriter + +- 构造方法:需要传入`Writer`的子类 +- 常用方法: \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/IO流/缓冲流/image134.png b/posts/blog/编程技术/java/JavaSE/IO流/缓冲流/image134.png new file mode 100644 index 0000000..961bf83 Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/IO流/缓冲流/image134.png differ diff --git a/posts/blog/编程技术/java/JavaSE/IO流/缓冲流/image56.png b/posts/blog/编程技术/java/JavaSE/IO流/缓冲流/image56.png new file mode 100644 index 0000000..c548235 Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/IO流/缓冲流/image56.png differ diff --git a/posts/blog/编程技术/java/JavaSE/IO流/装饰器设计模式(Decorator Pattern).md b/posts/blog/编程技术/java/JavaSE/IO流/装饰器设计模式(Decorator Pattern).md new file mode 100644 index 0000000..e29fe6f --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/装饰器设计模式(Decorator Pattern).md @@ -0,0 +1,69 @@ +--- +title: 装饰器设计模式(Decorator Pattern) +date: 2024-08-20 +tags: [IO流] +--- + +## 概述 + +- **装饰器设计模式**是GoF23种设计模式之一,属于==结构型设计模式==(结构型设计模式通常处理对象和类之间的关系,使程序员能够更好地组织代码并更好地利用现有代码) + +## 思考 + +- 如何扩展一个类的功能?继承确实也可以扩展对象的功能,但是接口下的实现类很多,每一个子类都需要提供一个子类,就需要编写大量的子类来重写父类的方法,会导致子类数量至少翻倍,会导致类爆炸问题 + +## 详解 + +- 装饰器设计模式作用:装饰器模式可以做到在不修改原有代码的基础之上,完成功能扩展,符合OCP原则,并且避免了使用继承带来的类爆炸问题 +- IO流中使用了大量的装饰器设计模式 + - 以下是 Java IO 流中常用的使用了装饰器设计模式的类: + + ### 1. `FilterInputStream` 和 `FilterOutputStream` + + - `FilterInputStream` 和 `FilterOutputStream` 是装饰器模式的基类,它们通过组合而不是继承来扩展 `InputStream` 和 `OutputStream` 的功能。它们为所有装饰器类提供了基本的包装机制。 + - 子类: + - `BufferedInputStream`:提供缓冲功能,减少实际读取操作的次数。 + - `DataInputStream`:提供了从输入流中读取 Java 基本类型数据的方法。 + - `PushbackInputStream`:允许回推一个字节到输入流中。 + - `LineNumberInputStream`(已过时):跟踪从输入流中读取的行号。 + - `BufferedOutputStream`:提供缓冲功能,减少实际写操作的次数。 + - `DataOutputStream`:提供了将 Java 基本类型数据写入输出流的方法。 + + ### 2. `FilterReader` 和 `FilterWriter` + + - 类似于 `FilterInputStream` 和 `FilterOutputStream`,`FilterReader` 和 `FilterWriter` 是用于字符流的装饰器基类。 + - 子类: + - `BufferedReader`:提供了缓冲读取功能,并增加了像 `readLine()` 这样的便捷方法。 + - `LineNumberReader`:跟踪读取的行号。 + - `PushbackReader:允许回推字符到输入流中。 + - `BufferedWriter:提供了缓冲写入功能。 + - `PrintWriter`:提供了打印格式化表示的功能,并且不抛出 `IOException`。 + + ### 3. `InputStreamReader` 和 `OutputStreamWriter` + + - `InputStreamReader`:将字节流转换为字符流,是一个装饰器,包装了一个 `InputStream`。 + - `OutputStreamWriter`:将字符流转换为字节流,是一个装饰器,包装了一个 `OutputStream`。 + + ### 4. `PrintStream` + + - `PrintStream` 是一个装饰器类,包装了 `OutputStream`,并增加了便捷的打印功能(如 `print` 和 `println` 方法)。它还提供了自动刷新功能,并且不抛出 `IOException`。 + + ### 5. `ObjectInputStream` 和 `ObjectOutputStream` + + - `ObjectInputStream`:从输入流中反序列化对象,包装了一个 `InputStream`。 + - `ObjectOutputStream`:将对象序列化并写入输出流,包装了一个 `OutputStream`。 + + ### 6. `PushbackInputStream` 和 `PushbackReader` + + - `PushbackInputStream`:允许从字节流中回退一个或多个字节,以便重新读取。 + - `PushbackReader`:允许从字符流中回退一个或多个字符,以便重新读取。 +- 装饰器设计模式中涉及到的角色包括: + + - 装饰者和被装饰者的公共接口/公共抽象类(Component) + - 被装饰者(ConcreteComponent) + - 抽象的装饰者(Decorator) + - 具体的装饰者1、具体的装饰者2(ConcreteDecorator) + + ![image-20251011194657543](装饰器设计模式(Decorator Pattern)/image-20251011194657543.png) + + - 假设有一个接口,多个实现类,实现类中将接口方法都进行了实现,现在要给所有接口下的方法增加一个功能,可以编写一个增强类,属性是该接口,并继承该接口,构造器中要求传入一个实现类并赋值给接口,接口方法的实现进行增加功能+调用实现类的方法,多个增强类都要实现接口+接口属性,所以直接抽象出来 \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/IO流/装饰器设计模式(Decorator Pattern)/image-20251011194657543.png b/posts/blog/编程技术/java/JavaSE/IO流/装饰器设计模式(Decorator Pattern)/image-20251011194657543.png new file mode 100644 index 0000000..9105964 Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/IO流/装饰器设计模式(Decorator Pattern)/image-20251011194657543.png differ diff --git a/posts/blog/编程技术/java/JavaSE/IO流/装饰器设计模式(Decorator Pattern)/image58.png b/posts/blog/编程技术/java/JavaSE/IO流/装饰器设计模式(Decorator Pattern)/image58.png new file mode 100644 index 0000000..2375578 Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/IO流/装饰器设计模式(Decorator Pattern)/image58.png differ diff --git a/posts/blog/编程技术/java/JavaSE/IO流/读取属性配置文件.md b/posts/blog/编程技术/java/JavaSE/IO流/读取属性配置文件.md new file mode 100644 index 0000000..b012e2e --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/读取属性配置文件.md @@ -0,0 +1,62 @@ +--- +title: 读取属性配置文件 +date: 2024-08-20 +tags: [IO流] +--- + + +## 概述 + +- xxx.**properties**文件称为属性配置文件 + - 属性配置文件的格式: + + ```Java + \#注释 + key1=value1 + key2=value2 + key3=value3 + ``` + + - 注意:使用 `#`进行注释,key不能重复,**key重复则value覆盖**,key和value之间用等号分割等号两边不要有空格 + - 属性配置文件可以配置一些简单的信息,例如连接数据库的信息通常配置到属性文件中。这样可以做到在不修改java代码的前提下,切换数据库 +- 读取文件的方式:**Properties**(配置文件类) + IO使用、**ResourceBundle**(资源绑定类) + +## Properties + +- 构造方法:`Properties()` +- 常用方法:**load**传入流是关键 + - `load(InputStream inStream)` + - `load(Reader reader)` + - `properties.getProperty("key")` 获取value +- 遍历:使用**Enumeration**类相当于Iterator迭代器 + + - `propertyNames()` 获取迭代器 + - `hasMoreElements()` 是否有元素 + - `nextElement()` 获取key + + + + +```Java +Properties properties = new Properties(); +try (InputStream input = Main.class.getClassLoader().getResourceAsStream("config.properties")) { + if (input == null) { + System.out.println("Unable to find config.properties"); + return; + } + properties.load(input); + System.out.println(properties.getProperty("key")); // 输出 key1 的值 + } catch (IOException ex) { + ex.printStackTrace(); + } +``` + + + +## ResourceBundle + +- 获取实例:`ResourceBundle.getBundle("xxx")` + - 注意:文件**只能在src文件夹下**,路径格式:**com.qgs.xxx**也可以是`**/**`代替`.`,不需要后缀 + - 底层实现了流,不用自己传入 +- 常用方法: + - `getString("key")`获取value \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/IO流/转换流.md b/posts/blog/编程技术/java/JavaSE/IO流/转换流.md new file mode 100644 index 0000000..52ad93e --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/IO流/转换流.md @@ -0,0 +1,44 @@ +--- +title: 转换流 +date: 2024-08-19 +tags: [IO流] +--- + + +## 概述 + +- 属于**包装流**,是==**字符流的父类**==,对多个字符集进行了实现,提升通用性,减少乱码 + - 在文件读取文件时,涉及到解码的过程,可自行指定字符集,由FileReader类可知,它使用系统默认格式解码,通过构造方法可指定字符集,FileReader属于转换流的一种实现 + +## InputStreamReader + +- 常用的构造方法:==需要传入节点流== + `InputStreamReader(InputStream in, String charsetName)` // 指定字符集 + `InputStreamReader(InputStream in)` // 采用平台默认字符集 +- 乱码是如何产生的?文件的字符集和构造方法上指定的字符集不一致。 + +- **FileReader**是InputStreamReader的子类,本质上以下代码是一样的 + + - Reader reader = new InputStreamReader(new FileInputStream(“file.txt”)); + - Reader reader = new FileReader(“file.txt”); //都是采用平台默认字符集 + - Reader reader = new InputStreamReader(new FileInputStream(“file.txt”), “GBK”); + - Reader reader = new FileReader("e:/file1.txt", Charset.forName("GBK")); + + 因此FileReader的出现简化了代码的编写。 + + +## OutputStreamWriter + +- 常用构造方法: + - `OutputStreamWriter(OutputStream out, String charsetName)` // 使用指定的字符集 + - `OutputStreamWriter(OutputStream out)` //采用平台默认字符集 +- 乱码是如何产生的?文件的字符集与程序中构造方法上的字符集不一致。 + +- **FileWriter**是OutputStreamWriter的子类。以下代码本质上是一样的 + + - Writer writer = new OutputStreamWriter(new FileOutputStream(“file1.txt”)); // + - Writer writer = new FileWriter(“file1.txt”); // 采用平台默认字符集 + - Writer writer = new OutputStreamWriter(new FileOutputStream(“file1.txt”), “GBK”); + - Writer writer = new FileWriter(“file1.txt”, Charset.forName(“GBK”)); + + 因此FileWriter的出现,简化了代码。 \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/JDBC.md b/posts/blog/编程技术/java/JavaSE/JDBC.md new file mode 100644 index 0000000..dac71b5 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/JDBC.md @@ -0,0 +1,137 @@ +--- +title: JDBC +date: 2024-11-09 +tags: [JDBC] +--- + + +## 连接MySQL数据库 + +- 加载MySQL的jar包(驱动) +- **DriverManager** +- **Connection** +- **Statement** + + ```Java + //1. 通过DriverManager来获得数据库连接 + try (Connection connection = DriverManager.getConnection("连接URL","用户名","密码"); + //2. 创建一个用于执行SQL的Statement对象 + Statement statement = connection.createStatement()){ //注意前两步都放在try()中,因为在最后需要释放资源! + //3. 执行SQL语句,并得到结果集 + ResultSet set = statement.executeQuery("select * from 表名"); + //4. 查看结果 + while (set.next()){ + ... + } + }catch (SQLException e){ + e.printStackTrace(); + } + //5. 释放资源,try-with-resource语法会自动帮助我们close + ``` + + +## 执行DQL语句 + +- `statement`的`excuteQuary` 方法,返回结果集对象`ResultSet` ,操作类似迭代器 +- 执行`excuteQuary`方法,默认关闭上一个`ResultSet` 对象;也就是一个`statement`每执行一个程序,上一个结果集就失效了,要同时多个查询,需要创建多个`statement` +- 列索引从1开始 + + ```Java + while (set.next()){ + System.out.println(set.getString(1)); + System.out.println(set.getTime(2)); + System.out.println(set.getBoolean(3)); + } + ``` + + +## 执行DDL语句 + +- 通过`excuteUpdate`方法,返回int值,表示执行n条数据,无则为0 + +## excute + +- 通用方法,返回值为布尔类型,表示执行是否成功 +- 对于DQL语句,可以通过`getResultSet`方法获取结果集 +- 对于DDL语句,可以通过`getUpdateCount`方法获取生效个数 + +## 批处理操作(非DQL) + +- 当一次性要执行多条sql语句,如批量修改、删除;可以一次性执行多条指令,而不是一条一条执行 + + ```Java + statement.addBatch("insert into user values ('f', 1234)"); + statement.addBatch("insert into user values ('e', 1234)"); //添加每一条批处理语句 + statement.executeBatch(); //一起执行 + ``` + + +## 将结果映射为对象 + +- 通过反射将结果集映射为对象,最好一个完整形参、属性都是对象 + + ```Java + @SuppressWarnings("unchecked") + static T to(ResultSet set,Class tClass) { + try { + Constructor[] declaredConstructors = tClass.getDeclaredConstructors(); + //所有参数 + Class[] parameterTypes = declaredConstructors[0].getParameterTypes(); + Object[] parameter = new Object[parameterTypes.length]; + for (int i = 0; i < parameter.length; i++) { + parameter[i] = set.getObject(i + 1); + if(parameter[i]==null) continue; + if (parameter[i].getClass() != parameterTypes[i]) + if(parameterTypes[i]==Boolean.class){ + Integer integer = (Integer) parameter[i]; // 获取为 Integer 类型 + // 转换为 Boolean + parameter[i] = integer == 1; + } + else throw new SQLException("错误的类型转换:" + parameter[i].getClass() + " -> " + parameterTypes[i]); + } + return (T) declaredConstructors[0].newInstance(parameter); + }catch (SQLException | InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + ``` + + +## 使用preparedStatement防止SQL注入 + +- 当数据是由用户输入时,使用字符串拼接的SQL语句很容易出问题,比如:1=1 ‘ - - +- 为了避免这个问题,使用 `preparedStatement`代替`Statement` ,预编译SQL指令 + + ```Java + --创建,此类为Statement子类 + PreparedStatement preparedStatement = connection.prepareStatement("select * from users") + --设置参数 + statement.setString(1, scanner.nextLine()); + statement.setString(2, scanner.nextLine()); + ``` + + +## 管理事务 + +- 默认自动提交,和MySQL一样 + + ```Java + connection.setAutoCommit(false);//关闭自动提交后相当于开启事务 + // SQL语句 + // SQL语句 + // SQL语句 + connection.commit();或 connection.rollback(); + ``` + +- 设置锚点,锚点之间的不生效 + + ```Java + Savepoint savepoint = connection.setSavepoint(); //创建回滚点 + statement.executeUpdate("insert into user values ('b', 1234)"); + connection.rollback(savepoint); //回滚到回滚点,撤销前面全部操作 + ``` + + +## 结果集操作 + +- 查询完结果后,直接修改 \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/JavaSE.md b/posts/blog/编程技术/java/JavaSE/JavaSE.md new file mode 100644 index 0000000..7a15f79 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/JavaSE.md @@ -0,0 +1,28 @@ +--- +title: JavaSE +date: 2024-08-14 +tags: [JavaSE] +--- + + +[[编码详解]] + +[[IO流]] + +[[nio]] + +[[多线程]] + +[[反射]] + +[[注解]] + +[[网络编程]] + +[[Lambda表达式]] + +[[Stream]] + +[[新特性]] + +[[JDBC]] \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/Java基础/Java.md b/posts/blog/编程技术/java/JavaSE/Java基础/Java.md new file mode 100644 index 0000000..ede722e --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/Java基础/Java.md @@ -0,0 +1,141 @@ +### Java语言特点 + +- 面向对象 +- **跨平台**(平台无关性) + +不同平台使用对应的Java虚拟机(JVM),使其可以一份代码,各个平台使用一次编译,到处运行: + +Java程序编译成字节码,不是机器码(1010),字节码可以运行在任意的Java虚拟机中,所以,JVM将各个平台的差异性消除了! + +- 高性能:多线程… +- **自动垃圾回收机制**(GC) + + + +jdk下载 + +![GetImage (1)](Java/GetImage (1).png) + +bin + +- Java.exe 运行命令 使用:java 类名 +- Javac.exe 编译命令 使用:javac Java源文件的路径 + +lib + +- src.zip jdk源码 + + + +### Java的加载和执行 + +编译和运行,可以在不同的操作系统中 + +- 编译阶段:词法分析、语法分析、语义分析、优化和字节码生成 + +Javac命令:检查语法错误 + +Java源代码(.java)---javac--->字节码文件(.class) + +- 运行阶段: + +Java命令:启动JVM运行 + +字节码文件(.class)---java--->JVM开始执行--->类加载器(classLoader)--->加载对应class文件 + +最终生成(解释)对应操作系统的机器码 + + + +jdk、jre、jvm + +> jdk(jre(jvm)) + + + +- jdk:Java Development kit Java开发工具包(用于开发人员) +- jre:Java Runtime Environment Java运行环境(用于普通用户) +- jvm: Java Virtual Machine Java虚拟机 + + + +### path、classpath + +path:在Windows系统中,控制台命令行会先在当前路径寻找指令,若没有则在PATH环境变量中寻找。为了在任何路径下都能使用Java命令,可以将Java的路径添加到PATH环境变量中 + + + +classpath:jvm启动后,类加载器(classloader)依据classpath寻找类,若没有则在当前路径寻找。但是如果配置,会导致固定路径;解决方法,不配置或 “.;xxx路径”,.表示当前路径;表分割 + + + +编译器乱码 + +javac(Java 编译器)的默认编码方式是基于系统的默认字符编码(即平台编码) + +中文一般是GBK + +将编译器和文件编码方式保持一致,即解决乱码 + +javac -encoding UTF-8 Java源文件 + + + +### 注释 + +- // 单行注释 +- /**/ 多行注释 +- /** javadoc注释 + +\* + +\* 编写在这里的信息,javadoc命令解析生成帮助文档 + +\* + +*/ + + ![GetImage (2)](Java/GetImage (2).png) + + + +### 查看字节码 + +javap -c xxx.class + +javap -v xxx.class + +- bipush 10:将10这个字面量压进操作数栈中 +- istore_1:将操作数栈顶元素弹出,然后将其存储到局部变量表的1号槽位 +- iinc 2,1:将局部变量表的2号槽位上的数据加1 +- iload_1:将局部变量表1号槽位上的数据复制一份,压入操作数栈 + + + +![GetImage (3)](Java/GetImage (3).png) + + + +#### 注意: + +- main方法中变量名(args)可以修改 +- 一个Java文件可以有多个类,但公共类要和文件名一致 +- Java文件中可以没有公共类 +- 每个类都可以编写main方法 + + + +#### 自展 + +- jvm是由C++编写的,javac是由Java语言编写的 +- 最初的Java编译器最初是由C或C++编写的,然后再由Java编写编译 + + + +#### 程序编译逻辑 + +- jvm是编译好的程序,javac也是编译好的字节码 +- 启动javac是jvm解释执行javac,javac运行处理源程序,生成字节码 +- 启动java解释执行字节码,jvm再次运作 + + \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/Java基础/Java/GetImage (1).png b/posts/blog/编程技术/java/JavaSE/Java基础/Java/GetImage (1).png new file mode 100644 index 0000000..b2a4074 Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/Java基础/Java/GetImage (1).png differ diff --git a/posts/blog/编程技术/java/JavaSE/Java基础/Java/GetImage (2).png b/posts/blog/编程技术/java/JavaSE/Java基础/Java/GetImage (2).png new file mode 100644 index 0000000..047523b Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/Java基础/Java/GetImage (2).png differ diff --git a/posts/blog/编程技术/java/JavaSE/Java基础/Java/GetImage (3).png b/posts/blog/编程技术/java/JavaSE/Java基础/Java/GetImage (3).png new file mode 100644 index 0000000..2bccc53 Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/Java基础/Java/GetImage (3).png differ diff --git a/posts/blog/编程技术/java/JavaSE/Java基础/package和import.md b/posts/blog/编程技术/java/JavaSE/Java基础/package和import.md new file mode 100644 index 0000000..e69de29 diff --git a/posts/blog/编程技术/java/JavaSE/Java基础/控制语句.md b/posts/blog/编程技术/java/JavaSE/Java基础/控制语句.md new file mode 100644 index 0000000..e69de29 diff --git a/posts/blog/编程技术/java/JavaSE/Java基础/数据类型.md b/posts/blog/编程技术/java/JavaSE/Java基础/数据类型.md new file mode 100644 index 0000000..e69de29 diff --git a/posts/blog/编程技术/java/JavaSE/Java基础/方法(method).md b/posts/blog/编程技术/java/JavaSE/Java基础/方法(method).md new file mode 100644 index 0000000..e69de29 diff --git a/posts/blog/编程技术/java/JavaSE/Java基础/运算符.md b/posts/blog/编程技术/java/JavaSE/Java基础/运算符.md new file mode 100644 index 0000000..e69de29 diff --git a/posts/blog/编程技术/java/JavaSE/Java基础/通用语法.md b/posts/blog/编程技术/java/JavaSE/Java基础/通用语法.md new file mode 100644 index 0000000..e69de29 diff --git a/posts/blog/编程技术/java/JavaSE/Lambda表达式.md b/posts/blog/编程技术/java/JavaSE/Lambda表达式.md new file mode 100644 index 0000000..306baf0 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/Lambda表达式.md @@ -0,0 +1,111 @@ +--- +title: Lambda表达式 +date: 2024-09-27 +tags: [Lambda表达式] +--- + + +- jdk1.8的一个新特性,可以简化代码的书写,是对函数式编程的支持,在函数式编程语言中,函数(方法)是基础,所以Lambda对应的就是函数式接口 +- 使用要求:该接口为函数式接口 + - 必须是接口 + - 只有一个抽象方法 +- 可以使用`@FunctionalInterface` 注解,编译检查是否为函数式接口 + +## Lambda和匿名内部类的区别 + +- 所需类型不同 + - Lambda表达式:只能是接口 + - 匿名内部类:接口、抽象类、普通类 +- 使用限制不同 + - Lambda表达式:只能是有一个抽象方法 + - 匿名内部类:对抽象方法个数没有限制 +- 实现原理不同 + - Lambda表达式:编译之后,无其他操作 + - 匿名内部类:编译之后,会单独生成.class字节码文件 + +## Lambda表达式的使用 + +- (形参列表) -> {方法体} + +```Java +//线程类 +Runnable run=()->{System.out.println("Hello World!");}; +//比较器 +Comparator comparator=(o1,o2)->{return o1-o2;}; +``` + +### 语法简化 + +- 小括号省略:一个形参 +- 大括号可以省略:只有一行语句(包含return语句,则return需要一起省略) +- 形参类型省略:无要求(如果要省略,则每个形参的类型都要省略) + +## 四个基本的函数式接口 + +- java提供的函数式接口 + + |**名字**|**接口名**|**对应的抽象方法**| + |---|---|---| + |消费|Consumer|void accept(T t);| + |生产|Supplier|T get();| + |转换|Function|R apply(T t);| + |判断|Predicate|boolean test(T t);| + + - 黄色部分常常用于Stream中,形参往往就是当前元素 + +## 方法引用 + +- 在Lambda表达式的基础上,进一步的简化 +- 使用条件:抽象方法的形参与返回值 和 某个方法一致时,可以代替,==相当于在方法中调用传入方法,参数共用== +- 使用:对象::方法() + + ```Java + Consumer consumer=(str)->System.out.println(str); + Consumer consumer=System.out::println; + Predicate predicate="1"::equals(); + ``` + + - 实例方法引用:对象::实例方法 + - 静态方法引用:类名::静态方法 + - 特殊方法引用(例如:比较器):第一个参数的类名::实例方法 + - 条件:==第一个参数作为方法调用者,后面的参数作为方法参数== + + ```Java + Comparator com=(o1,o2)->o1.compareTo(o2); + Comparator com=Integer::compareTo; + ``` + + - 构造方法引用:类名::new + - ==如果有形参,则需提供参数列表一致的构造器既可== + - 数组引用:数组类型::new + - 条件:抽象方法中只有一个参数(int类型),该参数用于设置数组空间长度 + + ```Java + Function function=(i)->new int[i]; + Function function=int[]::new; + ``` + + +## Lambda在集合中使用 + +### forEach方法 + +- 遍历元素 + +```Java +list.forEach(System.out::println); +set.forEach(System.out::println); +map.forEach((k,v)-> System.out.println(k+":"+v)); +``` + +### removeIf方法 + +- 删除满足条件的元素 + +```Java + list.removeIf("1"::equals); + set.removeIf(LambdaTest::isOk); + static boolean isOk(int i){ + return i>15; + } +``` \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/Stream.md b/posts/blog/编程技术/java/JavaSE/Stream.md new file mode 100644 index 0000000..0717c31 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/Stream.md @@ -0,0 +1,196 @@ +--- +title: Stream +date: 2024-09-28 +tags: [Stream] +--- + + +- 1.8引入的新特性,真正把函数式编程风格运用到Java语言中,Stream中的api可以更方便我们操作数据,使得开发更加简洁、易读 +- 特点: + - 不存储元素,只对元素进行计算 + - 不改变数据对象,返回结果为新的Stream + - 延迟操作,只有需要结果才会执行 +- 操作步骤 + - 创建Stream + - 中间操作:每次操作返回一个Stream类,可以链式操作 + - 终止操作 +- **Stream和Collection的区别** + - Stream:强调的是处理(计算)元素 + - Collection:强调的是存储元素 + +## 创建Stream的方式 + +- 提高Stream接口的静态方法`of(T…values)`创建 + + ```Java + Stream s = Stream.of("12", "!2", "s"); + ``` + +- 通过==Collection接口==下的`stream()`方法 + + ```Java + Stream strema=list.stream(); + Stream stream=set.stream(); + ``` + +- 通过Arrays类下的静态方法`stream(数组)` 包装创建 + + ```Java + + String[] arr1 = {"aa", "bb", "cc"}; + Stream stream = Arrays.stream(arr1); + + int[] arr2 = {11, 22, 33, 44}; + IntStream intStream = Arrays.stream(arr2); + + long[] arr3 = {11, 22, 33, 44}; + LongStream longStream = Arrays.stream(arr3); + + double[] arr4 = {1.0, 2.0, 3.0}; + DoubleStream doubleStream = Arrays.stream(arr4); + ``` + + > [!important] 注意:Stream、IntStream、LongStream和DoubleStream都继承于BaseStream接口 + + +## 中间操作 + +### 筛选(filter) + +- 本质是判断接口(Predicate),用于筛选元素 + +```Java +stream.filter(i->i>2);//保留大于2的元素 +``` + +### 映射(map) + +- 本质是转换接口(Function),用于处理元素或==获取元素的某个属性== + +```Java +stream.map(Student::getName);//获取name属性 +``` + +- flatMap方法,将每个元素映射为一个流,然后将所有的流合并为一个流,适用于引用类型二维数组、list嵌套 + +```Java +//调用每个元素的stream方法,将元素合并 +stream.flatMap(List::stream); +``` + +### 除重(distinct) + +- 去除重复元素,内部实现实际上是基于HashSet,所以要重写equals和hashCode方法 + +```Java +stream.distinct(); +``` + +### **排序(sorted)** + +- 可以选择传入比较器,实现比较接口可以不传入 + +```Java +stream().sorted((o1, o2) -> o1.getId()- o2.getId()); +``` + +### **合并(concat)** + +- 静态方法,将多个Stream数据合成一个 + +```Java +Stream.concat(stream1,stream2); +``` + +### **截断和跳过** + +- `skip`跳过和`limit`选择应用个数 + +```Java +stream.skip(2).limit(2);//跳过2个,选中2个 +``` + +## 终止操作 + +### 遍历(forEach) + +- 遍历元素 + +```Java +stream.forEach(System.out::println); +``` + +### 匹配(match) + +- 判断是否有某个元素,==返回值为boolean型== + +```Java +stream.allMatch(i->i==1);//是否全部为1 +stream.anyMatch(i->i==1);//是否包含1 +stream.noneMatch(i->i==1);//是否没有1 +``` + +### 归约(reduce) + +- 对数据进行累积操作,参数中的第一个元素表示初始值(前一个计算的结果),返回单个值为引用类型,则由`Optional` 类封装 +- 应用:计算总和、拼接字符串、对象组合等 +- count(个数)、max(最大值)、min(最小值),底层是reduce + +```Java +stream.reduce((i1, i2) -> i1 + i2);//计算总和 +//初始值,可以自定义 +stream.reduce(1,(i1, i2) -> i1 + i2);// +``` + +### 收集(collect) + +- 该方法有很多功能,通过`Collectors`工具类决定了`collect()`的行为 +- **归集(toList/toSet/toMap)** + + - 数据 —> 集合、数组(toArray) + + ```Java + //数组使用toArray方法,具体数组类型需要写明 + Object[] objects = stream.toArray(); + Student[] students = stream.toArray(Student[]::new); + //集合使用Collectors.toXXX,具体类型使用Collectors.toCollection并写明类型 + List list = stream.collect(Collectors.toList()); + Set set = stream.collect(Collectors.toSet()); + ArrayList arrayList = stream.collect(Collectors.toCollection(ArrayList::new)); + LinkedList linkedList = stream.collect(Collectors.toCollection(LinkedList::new)); + //map使用toMap,需要传入转化规则 + Map map= stream.collect(Collectors.toMap(str -> str.substring(0, str.indexOf(":")), str -> str.substring(str.indexOf(":")))); + ``` + +- **分组(groupingBy)** + + - **分成一个map**,以某个元素为key,满足条件的元素组成value,以list的形式 + + ```Java + //按性别分类 + Map> collect4 = stream.collect(Collectors.groupingBy(Student::getSex)); + //分组+统计 + Map collect = stream.collect(Collectors.groupingBy(Student::getId, Collectors.counting())); + ``` + +- **接合(joining)** + + - 结合元素,先映射(转成String),再选择分割符再接合 + + ```Java + stream.map(Student::getName).collect(Collectors.joining(","))); + ``` + +- **统计(counting/averaging)** + + - ==平均==、最大、最小、个数、求和、==全部数据==封装在实例中(使用reduce中的方法可代替) + - 不需要映射(map) + + ```Java + stream.collect(Collectors.counting());//个数 + stream.collect(Collectors.maxBy((o1, o2) -> o1.getId()-o2.getId());//最大 + stream.collect(Collectors.minBy((o1, o2) -> o1.getId()-o2.getId());//最小 + stream.collect(Collectors.averagingInt(Student::getId));//平均数 + stream.collect(Collectors.summingInt(Student::getId));//求和 + stream.collect(Collectors.summarizingInt(Student::getId));//全部数据 + ``` \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/nio.md b/posts/blog/编程技术/java/JavaSE/nio.md new file mode 100644 index 0000000..6a98c3c --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/nio.md @@ -0,0 +1,21 @@ +--- +title: nio +date: 2024-11-03 +tags: [nio] +--- + + +Channel & Buffer + +Selector + +path/Files + + + +写数据的过程(直接使用堆外内存,可以省去一次内存拷贝) + +- 堆内存 —> 堆外内存(避免GC) —> 网络缓冲区 + - 用户态 —> 用户态 —> 内核态 +- 堆外内存 —> 网络缓冲区 + - 用户态 —> 内核态 \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/反射.md b/posts/blog/编程技术/java/JavaSE/反射.md new file mode 100644 index 0000000..9b7a1c5 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/反射.md @@ -0,0 +1,295 @@ +--- +title: 反射 +date: 2024-08-23 +tags: [反射] +--- + + +## 概述 + +- 反射(Reflection),可以在运行时**动态加载类**或**获取、修改类的信息**,可以理解为另一种**类加载的方法**(是对**class文件**进行操作的一种方式 + - 详解 + - 静态加载:我们正常编写的代码,都是静态加载的,程序在运行时,当JVM**首次遇到对某个类的使用**(比如创建该类的对象或调用该类的静态方法或静态变量),JVM就会立即加载这个类,这个加载过程在程序运行期间只会发生一次,之后同一个类不会再被重新加载 + - 动态加载:是在程序运行期间,可以**加载同一个项目包下或网络传输的class文件内容**,通过这种方式,程序可以在运行时做出更灵活的决策,延迟加载、加载外部插件、使用反射等场景都可以通过动态加载实现,这种机制使得Java程序能够更加模块化、可扩展,并能够有效地管理资源 + - 优势:更适合编写工具,动态加载可以在class文件不在本地,进行加载运行,即编译时不需要class文件 +- 主要核心就是**Class类、Constructor类、Field类、Method类**等API,通过反射机制,程序员能够获得在编译期间不被知晓的类、属性、方法等信息,但是反射机制的性能较低,常常被认为是一种牺牲性能换取灵活性的实现方式 +- 应用: 反射机制主要应用于框架开发、动态代理、ORM框架、JDBC驱动等方面。 + +- Java反射机制核心包:java.lang.reflect.* + + Java反射机制核心类: + + - java.lang.Class + - java.lang.reflect.Field + - java.lang.reflect.Method + - java.lang.reflect.Constructor + - java.lang.reflect.Modifier + +> [!important] 反射允许在**运行时获取类的结构信息并动态调用类的方法**,应用于框架、工具开发等。 +> +> 将Java文件编译成clas文件还是要javac来处理的 + +## Class类 + +- 是**对类信息的一种封装**,在类加载阶段将Class文件中的类信息(元信息)放在方法区,生成一个Class实例,**存储在堆中** + - 详情 + - JVM方法区和堆中的Class对象,**Class对象是存放在堆区的**,不是方法区,这点很多人容易犯错,类的元数据**(元数据并不是类的Class对象!Class对象是加载的最终产品,类的方法代码,变量名,方法名,访问权限,返回值等等都是在方法区的)**才是存在方法区的。 + +![image 48.png](反射/image48.png) + +- Java中获取Class对象有以下三种方式: + - 使用Class类的forName()方法,静态方法 + + - 可以使用Class类的forName()方法来获取Class对象,例如: + + ```Java + //传入的是Java文件所在位置的全限定名,jvm根据classpath位置加载对应class + Class clazz = Class.forName("java.lang.Object"); + ``` + + - 使用“类.class”语法,静态属性 + + 可以使用“类.class”语法来获取Class对象,例如: + + Class clazz = Object.class; + + - 调用Object类的getClass()方法,实例方法 + + 可以通过对象的getClass()方法来获取Class对象,例如: + + Object obj = new Object(); + + Class clazz = obj.getClass(); + +- 常用方法 + + |方法|描述| + |---|---| + |String getName()|获取完整类名| + |String getSimpleName()|获取类名| + |int getModifiers()|获取修饰符| + |ClassLoader getClassLoader()|返回该类的类加载器| + +- 修饰符对应int值表 + + |修饰符|int值| + |---|---| + |public|1| + |private|2| + |protected|4| + |static|8| + |final|16| + |synchronized|32| + |volatile|64| + |transient|128| + |native|256| + |interface|512| + |abstract|1024| + |strict|2048| + + +--- + +## Field类 + +## Method类 + +## Constructor类 + +- 对字段、方法、构造器的封装,包括修饰符、字段名、类型、返回值等 + +- 都是通过Class类,获取各种类的**实例对象** + + ```Java + //传入字段的名字即可 + Field field1 = clazz.getField("fieldName"); // 通过属性名获取公共属性 + Field field2 = clazz.getDeclaredField("fieldName"); // 通过属性名获取私有属性 + Field[] fields = clazz.getDeclaredFields(); // 获取所有的属性,包括私有的 + //获取方法类,传入方法名,传入对应参数的Class类 + Method method1 = clazz.getMethod("methodName"); + Method method2 = clazz.getDeclaredMethod("methodName",paramTypes); + Method[] methods = clazz.getDeclaredMethods(); + //获取构造方法时,传入对应参数Class + Constructor constructor1 = clazz.getConstructor(paramTypes); + Constructor constructor2 = clazz.getDeclaredConstructor(); + Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); + ``` + +- 通过字段名 + +- 通过方法名、参数类型 + +- 通过参数类型 + +> [!important] 通过Class类获取 +> +> **私有的**Field、Method、Constructor类使用`get``**Declared**``XXXX` + +- 私有修饰共同点 + +```Java +//修改权限 +field.setAccessible(true);//只需要修改一次 +method.setAccessible(true);//只需要修改一次 +constructor.setAccessible(true);//只需要修改一次 +``` + +> [!important] 使用 +> +> `**Declared**`获取的实例对象(私有属性修饰),使用必须修改权限 + +- 创建实例:`newInstance` + + ```Java + Object myObject = constructor.newInstance(args);//创建实例 + //推荐使用 Constructor.newInstance() + ``` + +- 字段的访问、修改,需要实例对象:`get`、`set` + + ```Java + Object fieldValue = field.get(myObject); // 读取某个对象的属性值 + field.set(myObject, newValue); // 修改某个对象的属性值 + ``` + +- 方法的调用,需要实例对象:`invoke` + + ```Java + Object result = method.invoke(myObject, args);//args参数列表 + ``` + + +--- + +## 类加载机制 + +- 类加载的过程 + - 装载(loading) + + ①类加载器负责将类的class文件读入内存,并创建一个java.lang.Class对象 + + - 连接(linking) + + ①验证(Verify) + + - 确保加载类的信息符合JVM规范。 + + ②准备(Prepare) + + - 正式为静态变量在方法区中开辟存储空间并设置默认值 + - public static int k = 10; 此时:k会赋值0 + - public static final int f = 10; 此时: f会赋值10 + + ③解析(Resolve) + + - 将虚拟机常量池内的符号引用替换为直接引用(地址)的过程。 + + - 初始化(initialization) + + ①静态变量赋值,静态代码块执行 + + +![image 1 28.png](反射/image128.png) + +- **类加载器** + + **1. 启动类加载器(Bootstrap ClassLoader): 负责加载核心Java类库(如 rt.jar)** + **2. 扩展类加载器(Extension ClassLoader): 负责加载Java的扩展库(通常在 jre/lib/ext 目录下),例如解析XML** + **3. 应用程序类加载器(Application ClassLoader): 负责加载用户类路径(classpath)上的类** + + - JVM按需加载类,而不是一次性加载所有类。 + - 类在首次主动使用时才会被加载。 + - 依赖的类会在需要时加载。 + - 这种按需加载的策略提高了程序的启动速度和运行效率。 + + ==低版本的JDK中类加载器==的名字: + + - 启动类加载器:负责加载rt.jar + - 扩展类加载器:ext/*.jar + - 系统类加载器:classpath + +- 通过类加载获取Class对象 + + ```Java + //获取类加载器对象(系统类加载器/应用类加载器) + ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); + //使用loadClass方法加载类 + Class aClass = systemClassLoader.loadClass("类全限定名称"); + ``` + + - 使用类加载器与forName方法加载类有什么不同? + + 使用类加载器`loadClass`加载**不进行初始化**!!! + +- 获取类加载器可以通过 `getParent()`方法一级一级获取 + + ![image 2 19.png](反射/image219.png) + + +## 双亲委派机制 + +- 需要查找类或资源时,`ClassLoader` 实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。 +- 某个类加载器接收到加载类的任务时,通常委托给“父 类加载”完成加载,最“父 类加载器”无法加载时,一级一级向下委托加载任务 +- 另外,类加载器之间的父子关系一般不是以继承的关系来实现的,而是通常使用**组合关系**来复用父加载器的代码。 + + ```Java + public abstract class ClassLoader { + ... + // 组合 + private final ClassLoader parent; + protected ClassLoader(ClassLoader parent) { + this(checkCreateClassLoader(), parent); + } + ... + } + ``` + +- 作用: + - **保护程序的安全** + - **防止类加载重复** + +![image 3 16.png](反射/image316.png) + +**[打破双亲委派模型方法](https://javaguide.cn/java/jvm/classloader.html#%E6%89%93%E7%A0%B4%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%A8%A1%E5%9E%8B%E6%96%B9%E6%B3%95)** + +--- + +## 反射中的泛型 + +- Type类,获取Type实例,判断是否是泛型(`ParameterizedType`:参数化数据类型) + + ```Java + Type type; + System.out.println(type instanceof Class);//不是泛型 + System.out.println(type instanceof ParameterizedType);//是泛型 + //将参数转出为数组 + Type[] types=(ParameterizedType)type.getActualTypeArguments(); + //查看完整类名 + String name=types[0].getTypeName(); + ``` + +- 父类的泛型:`**getGenericSuperclass**` 接口的泛型:`**getGenericSuperclass**` + + ```Java + Type genericSuperclass = aClass.getGenericSuperclass(); + Type[] genericInterfaces = aClass.getGenericInterfaces(); + ``` + +- 属性上的泛型:`**getGenericType**` + + ```Java + Type type=field.getGenericType(); + ``` + +- 方法参数上的泛型:`**getGenericParameterTypes**` 方法返回值的泛型:`**getGenericReturnType**` + + ```Java + Type[] typs= method.getGenericParameterTypes(); + Type type = method.getGenericReturnType(); + ``` + +- 构造方法参数上的泛型:`**getGenericParameterTypes**` + + ```Java + Type[] typs= constructor.getGenericParameterTypes(); + ``` \ No newline at end of file diff --git a/posts/blog/编程技术/java/JavaSE/反射/image128.png b/posts/blog/编程技术/java/JavaSE/反射/image128.png new file mode 100644 index 0000000..72adffc Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/反射/image128.png differ diff --git a/posts/blog/编程技术/java/JavaSE/反射/image219.png b/posts/blog/编程技术/java/JavaSE/反射/image219.png new file mode 100644 index 0000000..0d9d0df Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/反射/image219.png differ diff --git a/posts/blog/编程技术/java/JavaSE/反射/image316.png b/posts/blog/编程技术/java/JavaSE/反射/image316.png new file mode 100644 index 0000000..24a50a6 Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/反射/image316.png differ diff --git a/posts/blog/编程技术/java/JavaSE/反射/image48.png b/posts/blog/编程技术/java/JavaSE/反射/image48.png new file mode 100644 index 0000000..caeac74 Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/反射/image48.png differ diff --git a/posts/blog/编程技术/java/JavaSE/多线程.md b/posts/blog/编程技术/java/JavaSE/多线程.md new file mode 100644 index 0000000..27e3c1d --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/多线程.md @@ -0,0 +1,383 @@ +--- +title: 多线程 +date: 2024-08-23 +tags: [多线程] +--- + + +## 基本概念 + +[[计算机操作系统]] + +### **进程** + +- 是指操作系统中的一段==**程序**==,它是一个==**正在执行中的程序实例**==,具有独立的内存空间和系统资源,如文件、网络端口等。在计算机程序执行时,先创建进程,再在进程中进行程序的执行。一般来说,进程是==线程的容器==,一个进程可以包含多个线程,至少有一个线程 + +### 线程 + +- 是指==**进程中的一个执行单元**==,是进程的一部分,它负责在进程中执行程序代码。每个线程都有==自己的栈和程序计数器==,并且可以共享进程的资源。多个线程可以在同一时刻执行不同的操作,从而提高了程序的执行效率 + +> 通常一个进程都是可以启动多个线程的,称为:多线程并发 + +> [!important] 现代的操作系统是支持多进程的,也就是可以启动多个软件,一个软件就是一个进程。称为:多进程并发 + +**多线程的作用?** + +- 提高处理效率(多线程的优点之一是能够使 CPU 在处理一个任务时同时处理多个线程,这样可以充分利用 CPU 的资源,提高 CPU 的利用效率。) + +- **JVM规范中规定:** + + - **堆内存、方法区 是线程共享的** + - ==虚拟机栈(VM)、本地方法栈、程序计数器== 是每个线程私有的 + + ![image 47.png](多线程/image47.png) + +- **关于Java程序的运行原理** + + 1.“java HelloWorld”执行后,会启动JVM,JVM的启动表示一个进程启动了。 + + 2.JVM进程会首先启动一个==**主线程(main-thread)**==,主线程负责调用main方法。因此main方法是在主线程中运行的。 + + 3.除了主线程之外,还启动了一个==垃圾回收线程==。因此启动JVM,至少启动了两个线程。 + + 4.在main方法的执行过程中,程序员可以手动创建其他线程对象并启动。 + + +### 并发(concurrent) + +- 是指具备处理多个任务的能力,但不一定要同时 +- 是指在单核CPU上同时运行多个程序,并发不是真正意义上的“同时进行”,而是通过抢占CPU时间片去执行;单核cpu下,线程实际是**串行执行**的。只是由于CPU在线程间(时间片很短)的切换非常快,让人感觉像是同时运行的;所以这种==线程轮流使用 CPU== 的做法称为并发 + + | | | | | | + |---|---|---|---|---| + |CPU|时间片1|时间片2|时间片3|时间片4| + |core|线程1|线程2|线程3|线程4| + + +### 并行(parallellism) + +- 是指具备同时处理多个任务的能力 +- 是指在多个CPU或者一个多核CPU上==同时运行多个程序==,每个CPU或者多核CPU可以分别运行程序,两个程序互不抢占CPU资源,可以真正做到同时进行,这种方式我们称之为并行:Parallel + + | | | | | | + |---|---|---|---|---| + |CPU|时间片1|时间片2|时间片3|时间片4| + |core1|线程1|线程2|线程3|线程4| + |core2|线程2|线程4|线程2|线程1| + + +> [!important] 并发和并行是共同存在的 + +- 并发编程与并行编程 + - 在CPU比较繁忙(假设为**单核CPU**),如果开启了很**多个线程**,则只能为一个线程分配仅有的CPU资源,这些线程就会为自己尽量多抢时间片,这就是通过多线程实现**并发**,线程之间会竞争CPU资源争取执行机会。 + - 在CPU资源比较充足的时候,一个进程内的**多个线程**,可以被分配到**不同的CPU资源**,这就是通过多线程实现**并行**。 + - 至于多线程实现的是并发还是并行?上面所说,所写多线程可能被分配到一个CPU内核中执行,也可能被分配到不同CPU执行,分配过程是操作系统所为,不可人为控制。所以,如果有人问我我所写的多线程是==并发还是并行的?==我会说,==都有可能==。 + - 总结:单核CPU上的多线程,只是由操作系统来完成多任务间对CPU的运行切换,并非真正意义上的并发。随着多核CPU的出现,也就意味着不同的线程能被不同的CPU核得到真正意义的并行执行,故而多线程技术得到广泛应用。 + - 不管并发还是并行,都提高了程序对CPU资源的利用率,最大限度地利用CPU资源,而我们使用多线程的目的就是为了提高CPU资源的利用率。 + +## 线程的调度模型 + +如果多个线程被分配到一个CPU内核中执行,则同一时刻只能允许有一个线程能获得CPU的执行权,那么进程中的多个线程就会抢夺CPU的执行权,这就是涉及到线程调度策略。 + +- **分时调度模型** + 所有线程轮流使用CPU的执行权,并且==平均的分配每个线程占用的CPU的时间==。 +- **抢占式调度模型** + 让==优先级高的线程以较大的概率优先获得CPU的执行权==,如果线程的优先级相同,那么就会随机选择一个线程获得CPU的执行权,而==Java采用的就是抢占式调用==。 + +## Thread启动线程 + +- Thread类中`run`方法默认实现是没有任务的,即`target`为null的,所以要在`run`方法中添加任务(`target`是`Runnable`接口) + + ![image 1 27.png](多线程/image127.png) + + ![image 2 18.png](多线程/image218.png) + +- 解决方法 + - 方法一:**继承Thread**,重写run方法 + - 方法二:**传入Runnable接口实现类**(匿名内部类也可以) + - 为了解耦合,常常通过匿名内部类,将任务调用放在run方法中,而不实现接口 + +- **每个Thread类的实例,代表一个线程**,调用实例方法`start`**创建线程**(底层依赖c++中的方法开启线程),开启线程后,争夺到时间片,执行`run`方法 + +> [!important] 开启线程只能通过Thread的start方法,其他方法底层依赖该方法 + +- 内存模型 + + ![image 3 15.png](多线程/image315.png) + +- 构造方法: + - `Thread(Runnable target)` 传入任务 + - `Thread(Runnable target, String name)` 创建时给线程起名字 +- 常用方法 + + |静态方法|描述| + |---|---| + |Thread currentThread()|获取当前线程实例| + |void sleep(long millis)|当前线程在指定的毫秒数内==休眠(====**占用锁🔒**====)==| + |void yield()|当前线程提出==让步请求==| + |boolean interrupted()|**清除中断标记复位**(返回是否有中断标记)| + |实例方法|描述| + |String getName()|获取线程名称| + |void setName(String name)|设置线程名称| + |void interrupt()|**添加一个中断标记**| + |boolean isInterrupted()|判断是否有中断标记| + |void setPriority(int priority)|设置线程**优先级**| + |void join()|让线程先执行| + |void join(long millis)|让线程先执行设置秒数| + |boolean isAlive()|判断线程是否存活| + |void setDaemon(boolean on)|将线程设置为**守护线程**| + |state getState()|查看线程状态| + + +### 线程休眠与打断 + +- 休眠:使用`sleep`或`TimeUnit`的sleep方法,使线程进入等待状态,休眠时间到了,自动进入可运行状态; +- 打断睡眠:使用`interrupt()` 方法,添加中断标记(不会停止线程),如果**此时线程正在休眠**则会触发`InterruptedException`异常,并且会清除中断标记; + - 可以使用该方法**中断线程** + + - ~~stop方法,强行终止,数据丢失~~ + - 使用`interrupt`方法添加中断标记,使用`isInterrupted` 方法判断是否标记,发现被标记则结束线程;如果不想中断线程使用该`interrupted` 方法进行标记清除 + + ```Java + public void run() { + while (!Thread.currentThread().isInterrupted()){ + try { + Thread.sleep(1000); + System.out.println(1); + } catch (InterruptedException e) { + e.printStackTrace(); + //清除标记,比较优雅? + Thread.currentThread().isInterrupted(); + break; + } + } + } + ``` + + +### 线程让步与合并 + +- 让步:调用 `yield()` 方法,当前线程进入可运行状态,重新争夺时间片;因为让步线程还有可能被线程调度程序再次选中,所有**无法保证一定能让步** +- 合并:调用`join`方法,主线程等待调用`join`方法的线程执行完毕,主线程才能继续(相当于合并线程),两个线程合并(有点像插队),让主线程堵塞,让join的实例线程执行,可以设置`join`(时间),时间到了,自动分开,时间没到,如果实例线程结束,也会分开 + - 当主线程需要等待其他线程先运行完,自己再运行时 + + ```Java + thread1.start(); + thread2.start(); + thread1.join();//表示先暂停主线程,此时线程1、2继续执行 + thread2.join();//表示先暂停主线程,此时线程1、2继续执行 + //这样可以使,主线程不结束,但不占用资源 + ``` + + 原理: + + +### 线程优先级 + +- 线程执行是优先级越高执行概率越大,线程默认是5,范围是1-10,通过`setPriority` 进行修改 + +### 守护线程 + +- 功能:执行时机与普通线程一样,结束则在其他所有的非守护线程结束之后,守护线程自动结束,垃圾回收器线程属于守护线程 +- 默认创建的都是普通线程,使用`setDaemon(true)`设置守护线程 + +## 线程的生命周期 + +![image 4 14.png](多线程/image414.png) + +线程状态图 + +- 新建状态(NEW) + - 线程实例创建完,调用`start`方法之前,称为新建状态 +- 运行状态(RUNNABLE) + + 调用`start`方法后,进入就绪状态 + + - 就绪/可运行状态(RUNNABLE):表示该线程可以抢夺时间片 + - 运行状态(RUNNABLE):运行`run`方法,使用时间片,时间片使用完,进入就绪状态 + +- 等待状态:该状态下,会放弃运行状态下占用的时间片 + - 阻塞状态(BLOCKED) + - 当线程运行时发现对象锁已经被占有时,则进入阻塞状态,它会被放入该**锁对象的等待队列**中;当锁释放时,继续争夺锁,只有争夺到锁,才能进入可运行状态 + - 超时等待状态(TIMED_WAITING) + - 当线程调用`sleep`或`wait`等线程等待方法时,线程进入超时等待状态,(全局计时器判断)时间到了,进入可运行状态重写抢夺时间片 + - 等待状态(WAITING) + - 当线程执行I/O操作时(类似监视器),线程将进入无限时等待,直到被**其他线程唤醒或等待到内容**,等待才能解除 + +- 死亡状态(TERMINATED) + - 线程任务结束或遇到异常终止线程时,线程死亡 + +## 线程安全 + +- 什么情况考虑线程安全? + - 多线程的并发发环境,有共享数据,且共享数据涉及**修改**操作 + - 比如:集合类元素修改、懒汉式式线程安全问题(不能保证单例)、银行存取钱问题 +- 一般情况下: + - 局部变量不存在线程安全问题(引用类型可能有问题,比如:内容被修改) + - 实例变量可能出现线程安全(在堆中,堆中是线程共享的) + - 静态变量可能出现线程安全(在堆中,堆中是线程共享的) +- 线程执行方式:线程同步(排队)、线程异步(不排队) +- 解决线程安全方案 + - 阻塞式的解决方案:`synchronized`,`Lock` + - 非阻塞式的解决方案:原子变量 + +### 线程同步机制(使用synchronized) + +- 本质:线程排队执行就是同步机制(被称为互斥锁),保证了**同步代码块的原子性** +- 原理:多个线程争夺一把锁,执行完同步代码块会释放锁 +- 语法格式:synchronized + + ```Java + synchronized(必须是需要同步的线程共享的对象){ + //需要同步的代码 + } + //"必须是需要同步的线程共享的对象"效率高 + ``` + +- 锁一定要公共(`static`类型特别满足)共享,同步代码块内容准确些,尽量小 +- 在实例方法上增加锁,共享对象的**对象锁为this**,多个实例则多把锁,这种方式就没意义了 + + ```Java + public synchronized void method(){ + //同步内容 + //同步内容 + //同步内容 + } + ``` + + ```Java + public void method(){ + synchronized(this){ + //同步内容 + } + } + ``` + + > [!important] 使用Runnable接口创建任务,通常创建一个实例(保证this唯一),多个Thread实例 + + > [!important] 通过继承Thread添加任务,此时需要声明对象锁,因为多个Thread实例 + +- 在静态方法上增加锁,对象锁为**类锁(Class类)**,该锁是唯一的,可以保证静态变量修改安全 + +### 死锁 + +- 原理:当两个线程各持有一把锁,并寻找对方锁时,则死锁 + + ![image 5 13.png](多线程/image513.png) + +- 解决方案:所以尽量少将`synchronized`进行嵌套,如果非要嵌套则,将线程==获取锁的顺序保持一致== + +## 线程通信 + +- 进行通信的线程需要共用一把锁,当两个线程各自持有自己的锁,它们将无法相互通信 +- 来自`**Object**`的三个方法:`wait()`、`notify()`、`notifyAll()` + + - 为什么在`Object`中? + + - 以下方法**必须在同步代码块中或同步方法**中,调用方法的实例必须为🔒**锁对象**,否则报`IllegalMonitorStateException`异常 + + - `wait()` :调用该方法,线程进入”**等待状态**“ + - wait(毫秒):调用该方法,线程进入”==超时等待状态==“ + - wait(毫秒,纳秒):调用该方法,线程进入”==超时等待状态==“ + - `wait`方法和`sleep`方法的不同 + - 都会暂停线程,`wait`释放锁,`sleep`占用锁 + - `wait`是object下的方法,`sleep`是Thread的静态方法 + - `wait`进入等待状态,需要其他线程唤醒,`sleep`进入超时等待状态,时间到了自动醒 + - `wait`必须在同步代码块中使用,`sleep`没要要求 + - `notify()` :唤醒优先级高的线程,一样则随机,只唤醒一个 + - `notifyAll()` :唤醒所有线程 + - 唤醒的线程,需要争夺锁,即进入**阻塞状态**,拿到锁代码才能继续执行 +- 书写技巧:==**判断等待—>任务执行—>唤醒线程**== + - 判断通常使用`while`,使用`if`当线程唤醒时,条件还是不足,属于假唤醒 +- 应用:两个线程交替打印A、B + +### **生产者与消费者** + +- 是通过一个容器来解决生产者和消费者的强耦合问题。通俗的讲,就是生产者在不断的生产,消费者也在不断的消费,可是消费者消费的产品是生产者生产的,这就必然存在一个中间容器 +- **容器类**提供生成、消费商品的方法,设置最大容量;生产者、消费者线程调用容器中的方法即可 +- 关键点:容器中的方法需要加锁,方法逻辑: + - **生产者逻辑**: + - 当缓冲区满时,生产者调用 `wait()`,进入等待状态,直到消费者从缓冲区取走数据。 + - 当缓冲区有空间时,生产者生成数据并将其放入缓冲区,之后调用 `notify()` 唤醒消费者。 + - **消费者逻辑**: + - 当缓冲区为空时,消费者调用 `wait()`,进入等待状态,直到生产者放入新数据。 + - 当缓冲区有数据时,消费者取走数据并处理,之后调用 `notify()` 唤醒生产者。 + +## 其他创建线程的方式 + +[[多线程]] + +### Callable接口 + +- [[JUC]] +- 实现`Callable`接口,可以**获得线程的返回值** +- 创建”未来任务”`FutureTask` 实例,传入Callable接口实现类,将`FutureTask` 实例传入`Thread`进行启动 + - `FutureTask` 类继承了Runnable方法,`run`方法中调用的是`Callable`中的`call`方法,并记录了返回值,`FutureTask` 相当于==中间层== + +- 使用:使用`FutureTask` 的`**get**`方法获取返回值,如果线程还未结束,阻塞当前线程 + + ```Java + Callable callable=new Callable() { + @Override + public String call() throws Exception { + return "1234"; + } + }; + //未来任务 + FutureTask futureTask = new FutureTask<>(callable); + Thread task=new Thread(futureTask); + task.start(); + System.out.println(futureTask.get()); + ``` + + +### 定时任务类 + +- 定时器+定时任务,可以设置每隔xx时间执行 + - java.util.==Timer== :定时器 + - 属性中的`TimerThread`(维护着任务队列)继承`Thread`类 + - java.util.==TimerTask== ==:==定时任务 + - 是一个抽象类,实现了`Runnable`接口 + +- 使用:在`TimerTask`中写下任务 + + - `Timer`可以添加多个实例任务,同一个 `TimerTask` 实例不能添加多次,因为 `TimerTask` 在执行后会进入**取消状态** + - Timer常用方法 + + | | | + |---|---| + |方法|描述| + |schedule(TimerTask task, Date time)|在指定的时间执行一次任| + |schedule(TimerTask task, long delay):|在指定的延迟后执行一次任务| + |schedule(TimerTask task, long delay, long perio)|在指定的延迟后开始执行任务,然后以固定的时间间隔重复执行| + |scheduleAtFixedRate(TimerTask task, long delay, long period):|在指定的延迟后开始执行任务,然后以固定的速率重复执行| + |scheduleAtFixedRate(TimerTask task, Date firstTime, long period)|在指定的首次时间执行任务,并以固定的速率重复执行| + |cancel()|终止此计时器,丢弃所有当前已安排的任务| + |purge()|从任务队列中移除所有已取消的任务| + + + ```Java + //定时器 + Timer timer=new Timer();//传入true则为守护线程 + //定时任务 继承实现、匿名实现 + TimerTask task=new TimerTask() { + @Override + public void run() { + System.out.println(123); + } + }; + SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + Date date= simpleDateFormat.parse("2024/09/06 16:10:00"); + //调用定时器实例的schedule方法,传入定时任务、时间类 + timer.schedule(task,date); + ``` + + +### 线程池 + + + +## 进阶 + +[[JUC]] + diff --git a/posts/blog/编程技术/java/JavaSE/多线程/image127.png b/posts/blog/编程技术/java/JavaSE/多线程/image127.png new file mode 100644 index 0000000..dc5654d Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/多线程/image127.png differ diff --git a/posts/blog/编程技术/java/JavaSE/多线程/image218.png b/posts/blog/编程技术/java/JavaSE/多线程/image218.png new file mode 100644 index 0000000..880294e Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/多线程/image218.png differ diff --git a/posts/blog/编程技术/java/JavaSE/多线程/image315.png b/posts/blog/编程技术/java/JavaSE/多线程/image315.png new file mode 100644 index 0000000..b25db2f Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/多线程/image315.png differ diff --git a/posts/blog/编程技术/java/JavaSE/多线程/image414.png b/posts/blog/编程技术/java/JavaSE/多线程/image414.png new file mode 100644 index 0000000..cebad20 Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/多线程/image414.png differ diff --git a/posts/blog/编程技术/java/JavaSE/多线程/image47.png b/posts/blog/编程技术/java/JavaSE/多线程/image47.png new file mode 100644 index 0000000..62d6aa6 Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/多线程/image47.png differ diff --git a/posts/blog/编程技术/java/JavaSE/多线程/image513.png b/posts/blog/编程技术/java/JavaSE/多线程/image513.png new file mode 100644 index 0000000..08edddd Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/多线程/image513.png differ diff --git a/posts/blog/编程技术/java/JavaSE/新特性.md b/posts/blog/编程技术/java/JavaSE/新特性.md new file mode 100644 index 0000000..3a2e766 --- /dev/null +++ b/posts/blog/编程技术/java/JavaSE/新特性.md @@ -0,0 +1,495 @@ +--- +title: 新特性 +date: 2024-09-30 +tags: [新特性] +--- + + +## 常用新特性 + +### jShell命令 + +- Java9引进的新特性,交互性编程模式 +- 使用:在命令行输入`jshell` 便可以开启 + +![image 51.png](新特性/image51.png) + +### try-with-resources + +- Java7新特性,自动关闭流 + +```Java +try (FileInputStream fis = new FileInputStream(srcFile); + FileOutputStream fos = new FileOutputStream(destFile)) { + //.... +} catch (Exception e) { + e.printStackTrace(); + } +``` + +- Java9升级 + +```Java +FileInputStream fis = new FileInputStream(srcFile); +FileOutputStream fos = new FileOutputStream(destFile); +try (fis;fosw) { + //.... +} catch (Exception e) { + e.printStackTrace(); + } +``` + +### 局部变量类型判断 + +- Java10新特性,**局部变量**可以不写类型,自动判断(方法形参不能使用) + + ```Java + var num = 123; + var str = "hello world"; + var arr = new int[] {11, 22, 33}; + ``` + + +### instanceof的模式匹配 + +- Java14新特性,简化了自动转型的过程 + + ```Java + Animal animal = new Dog(); + // 如果animal是Dog类的实例,则向下转型后就命名为dog + if (animal instanceof Dog dog) { + // 调用Dog类特有的show()方法 + dog.show(); + } + ``` + + +### switch表达式 + +- Java14新特性,省略了break语句,允许有返回值(必须有默认值) + + ```Java + //省略break写法 + switch(month){ + case 1,2,3 -> System.out.println("春天"); + case 4,5,6 -> System.out.println("夏天"); + case 7,8,9 -> System.out.println("秋天"); + case 10,11,12 -> System.out.println("冬天"); + } + //有返回值写法 + String season = switch (month) { + case 12, 1, 2 -> "春天"; + case 3, 4, 5 -> "夏天"; + case 6, 7, 8 -> "秋天"; + case 9, 10, 11 -> "冬天"; + default -> throw new RuntimeException("没有该月份。。。"); + }; + ``` + + +## 文本块 + +- Java12的新特性,可以更好的操作HTML、XML、SQL的字符串 +- Java14进行了增强,”`\`“取消一行结尾换行,”`\s`“增加空格 + + ```Java + //保存样式、自动换行 + String html=""" + + +

Hello, world

+ + + """; + ``` + + +### **Record** + +- Java14的新特性,作为数据的载体,省去编写载体类的繁琐 +- Record类的特点 + - 属性默认采用`private`和`final`修饰 + - 默认重写`equals()`、`hashCode()`、`toString()`方法 + - 提供全参构造方法、get方法(无set方法) + - 默认final修饰,并且继承于`java.lang.Record`类 + - 可以自定义静态属性、静态方法、实现接口、实例方法、无参构造,==但是创建对象时====**不能调用无参构造方法**====,而是====**通过全参构造方法创建对象**====的时候,====**默认就会调用**====Record类中的无参构造方法== +- 使用:**适用于不可修改的实例** + + ```Java + //实例属性,定义到record上面 + public record Tiger(String name, int age) { + //无参构造 + public Tiger(){} + //静态属性 + static int score; + //静态方法 + static void show(){ + System.out.println("show. .."); + } + //实例方法 + void method(){ + System.out.println("method ..."); + } + } + ``` + + +### 密封类 + +- Java15的新特性,可以指定哪些类、接口可以继承该类 + - 维护类层次结构的封闭性 + - 预防代码的意外扩展 + - 增强代码的可读性和可维护性 +- 使用`sealed`关键字修饰类、接口,子类必须使用`sealed`、`final`或`non-sealed` 修饰(接口用法类似) + + ```Java + // 密封类必须被继承,并且使用permits来指定哪些子类可以被继承 + sealed class Animal permits Dog, Bird, Tiger { } + // 注意:密封类的子类必须使用sealed、final或non-sealed来修饰 + // final关键字修饰的子类,则该子类不能被继承 + final class Tiger extends Animal { } + // non-sealed修饰的子类,则该子类就是一个普通类 + non-sealed class Bird extends Animal { } + // sealed修饰的子类,则该类就必须被继承,否则就会编译错误 + sealed class Dog extends Animal {} + non-sealed class SmallDog extends Dog {} + ``` + + +## API层面的变化 + +### String的变化 + +- **存储结构** + - 从Java9开始,`String`底层从`char`数组改为`byte`数组,节约了存储空间 + +- **新增方法** + + | | | | + |---|---|---| + |方法|描述|版本| + |isBlank()|判断是否为空(空格、制表符、换行都认为是空)|11| + |strip()|去除“前后”的中文空格|11| + |repeat(int x)|将字符串复制x遍|11| + |lines()|将字符串以换行符分割|11| + |indent(int x)|在字符串前面加x个空格|12| + + +### 接口新特性 + +- Java8中,接口支持`public`修饰的静态方法和默认方法 +- java9中,接口支持`private`修饰的静态方法和成员方法 + - 默认方法(`default`修饰的方法,默认是`public`) + - 当接口扩展时,所有类都要实现该方法,则可以添加默认方法静态方法,按需重写 + - 静态方法(`public`修饰):只能通过接口调用 + - 静态方法(`private`修饰):接口本身自己使用 + +### 标识符名字的变化 + +- Java9中规定“_”不能独立命名标识符了 + +### **简化编译运行程序** + +- java11之后,使用一条语句就可以编译、运行代码 + + ```Java + //旧 + javac Test.java + java Test + //新 + java Test.java + ``` + + +### 创建不可变集合 + +- Java9之后,可以通过List、Set、Map接口的`of(E…elements)`静态方法创建不可变集合 + + ```Java + // 创建不可变List集合 + List list = List.of(1, 2, 3, 4, 5); + // 创建不可变Set集合 + // 注意:如果Set集合中有相同的元素,则就会抛出IllegalArgumentException异常。 + Set set = Set.of(1, 2, 3, 4, 5, 4); + // 创建不可变Map集合 + Map map = Map.of(123, "武汉", 456, "成都"); + ``` + +- `Arrays.asList`与`List.of`的区别: + + - `Arrays.asList` 创建的集合可以修改集合中的元素 + + ```Java + // 通过Arrays.asList()方法创建不可变集合 + List list1 = Arrays.asList(1, 2, 3, 4, 5); + // list1.add(6); // 抛出UnsupportedOperationException异常 + // list1.remove(2); // 抛出UnsupportedOperationException异常 + list1.set(2, 33); // 没有问题 + ``` + + +### Optional API + +- 在Java8引入该类,优雅简洁的对null值进行处理 +- 创建:使用静态方法`of(T value)`或`ofNullable(T value)` + - 如果参数可能为null,使用`ofNullable(T value)` 创建 +- 常用方法 + + |**方法名**|**描述**| + |---|---| + |public T get()|如果值不为null,则直接取出该值;如果值为null,则抛出空指针异常。| + |public T orElse(T other)|如果值不为null,则直接取出该值;如果值为null,则取出的就是参数other的值。| + +- 使用:使用该类,存变量,使用`orElse`方法存一个备用变量,当主变量为null时,返回备用变量 + + ```Java + //常规写法 + String text="XXXX"; + if(text==null){ + text="内容为空"; + } + //使用Optional + Optional optional = Optional.ofNullable(text); + String result = optional.orElse("内容为空"); + ``` + + +## 新特性总结 + +- 一个新特性通常会经历3个阶段,也有些功能是直接发布,跳过这个过程 + 1. ==孵化器(====**Incubator**====)阶段==:这是新特性最早的开发和试验阶段,此时新特性只能作为一个单独的模块或库出现,而不会包含在Java SE中。在这个阶段,特性的设计可能会有些不稳定,而且会经常调整和变更。 + 2. ==预览(====**Preview**====)阶段==:在经过了孵化器阶段的验证和修改后,新特性进入了预览阶段,这是一种在Java SE内部实现的,开发人员可以使用并对其提供反馈的渠道。此时特性可能被包含在Java SE版本中,但是它默认是未开启的,需要通过特定的命令行参数或其他方式进行启用。 + 3. ==正式版(====**GA**====)阶段==:在经过了预览阶段的反复测试和修复后,新特性最终会在Java SE的稳定版本中发布。此时,特性被默认开启,成为Java SE的一部分,并可以在各个Java应用程序中使用 + +### Java9新特性 + +Java9经过4次推迟,历经曲折的Java9最终在2017年9月21日发布,提供了超过150项新功能特性。 + +- JEP 261: Module System + - JDK 9 开始引入的一种全新的模块化编程方式。JPMS 的目的是为了更好地支持大型应用程序的开发和维护,同时也可以使 Java 程序在更为动态、可移植和安全的环境下运行。 +- JEP 222: jshell: The Java Shell (Read-Eval-Print Loop) + - 一种交互式的 Java Shell,可以在命令行上快速地进行 Java 代码的编写、验证和执行,从而提高开发者的生产力。 +- JEP 213: Milling Project Coin(细化工程改进,该计划旨在引入小型语言特性来提高代码的简洁性和可读性) + - 在Java 9中,@SafeVarargs注解可以用于一个私有实例方法上。在Java 7和Java 8中,@SafeVarargs注解只能用于静态方法、final实例方法和构造函数。 + - 在Java 9中,可以将效果等同于final变量作为try-with-resources语句块中的资源来使用。在Java 7/8中,try-with-resources语句块中的资源必须是显式的final或事实上的final(即变量在初始化后未被修改),否则编译器会报错。这个限制限制了Java程序员使用try-with-resources语句块的能力,特别是在涉及lambda表达式、匿名类或其他读取外部变量的代码段时。 + - Java 9允许在匿名类实例化时使用钻石操作符(<>)来简化代码,但参数类型必须是具体的、可推导的类型。 + - 从Java9开始,不能使用一个单一的“_”作为标识符了。 + - 从Java9开始,接口中支持定义私有方法。 +- JEP 224: HTML5 Javadoc + - 从Java9开始,javadoc开始支持HTML5的语法。 +- JEP 254: Compact Strings + - 一种新的字符串表示方式,称为紧凑型字符串,以提高Java应用程序的性能和内存利用率。通过String源码得知:char[] 变成了 byte[]。 +- JEP 269: Convenience Factory Methods for Collections + - 更加方便的创建只读集合:List.of("abc", "def", "xyz"); +- JEP 269:对Stream API进行了增强 + - 其中最显著的是引入了四个新的方法,分别是 `takeWhile()`, `dropWhile()`, `ofNullable()` 和 `iterate()` +- JEP 110:一个新的HTTP客户端API,名为HttpClient,它是一种基于异步和事件驱动的方式,更加高效和灵活的HTTP客户端。 + +[![](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=TYrI5&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=TYrI5&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +### Java10新特性 + +2018年3月21日,Oracle官方宣布JAVA10正式发布。JAVA10一共定义了109个新特性,其中包含JEP,对开发人员来说,真正的新特性也就一个,还有一些新的API和JVM规范以及JAVA语言规范上的改动。 + +- JEP 286:局部变量类型推断 +- JEP 296:将 JDK 森林合并到单个存储库中 +- JEP 304:垃圾收集器接口 +- JEP 307:G1 的并行完整 GC +- JEP 310:应用程序类数据共享 +- JEP 312:线程局部握手 +- JEP 313:删除本机头生成工具 (javah) +- JEP 314:附加 Unicode 语言标签扩展 +- JEP 316:替代内存设备上的堆分配 +- JEP 317:基于 Java 的实验性 JIT 编译器 +- JEP 319:根证书 +- JEP 322:基于时间的发布版本控制 + +[![](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=XUWRt&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=XUWRt&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +### Java11新特性 + +2018年9月26日,Oracle官方发布JAVA11。这是JAVA大版本周期变化后的第一个长期支持版本,官方支持到2026年。 + +- JEP 181:基于 Nest 的访问控制 +- JEP 309:动态类文件常量 +- JEP 315:改进 Aarch64 内部函数 +- JEP 318:Epsilon:无操作垃圾收集器 +- JEP 320:删除 Java EE 和 CORBA 模块 +- JEP 321:HTTP 客户端(标准) +- JEP 323:本地变量语法LAMBDA参数 +- JEP 324:与Curve25519密钥协商和Curve448 +- JEP 327:Unicode的10 +- JEP 328:飞行记录器 +- JEP 329:ChaCha20和Poly1305加密算法 +- JEP 330:启动单文件源代码程序 +- JEP 331:低开销堆纹 +- JEP 332:传输层安全性 (TLS) 1.3 +- JEP 333:ZGC:可扩展的低延迟垃圾收集器(实验性) +- JEP 335:弃用 Nashorn JavaScript 引擎 +- JEP 336:弃用 Pack200 工具和 API + +[![](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=qckOc&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=qckOc&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +### Java12新特性 + +2019年3月19日,java12正式发布。 + +- JEP 189:Shenandoah:一个低暂停时间的垃圾收集器(实验性) +- JEP 230:微基准套件 +- JEP 325:switch表达式(预览) +- JEP 334:JVM 常量 API +- JEP 340:一个 AArch64 端口 +- JEP 341:默认 CDS 档案 +- JEP 344:G1 支持可中断的 Mixed GC +- JEP 346:及时从 G1 返回未使用的已提交内存 + +### Java13新特性 + +- JEP 350:动态 CDS 档案 +- JEP 351:ZGC:取消提交未使用的内存 +- JEP 353:重新实现旧的 Socket API +- JEP 354:开关表达式(预览) +- JEP 355:文本块(预览) + +[![](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=YJFb9&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=YJFb9&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +### Java14新特性 + +- JEP 305:instanceof 的模式匹配(预览) +- JEP 343:包装工具(孵化器) +- JEP 345:G1 的 NUMA 感知内存分配 +- JEP 349:JFR 事件流 +- JEP 352:非易失性映射字节缓冲区 +- JEP 358:有用的空指针异常 +- JEP 359:记录(预览) +- JEP 361: switch表达式(标准) +- JEP 362:弃用 Solaris 和 SPARC 端口 +- JEP 363:删除并发标记清除 (CMS) 垃圾收集器 +- JEP 364:macOS 上的 ZGC +- JEP 365:Windows 上的 ZGC +- JEP 366:弃用 ParallelScavenge + SerialOld GC 组合 +- JEP 367:删除 Pack200 工具和 API +- JEP 368:文本块(第二次预览) +- JEP 370:外部内存访问 API(孵化器) + +[![](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=D9416&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=D9416&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +### Java15新特性 + +- JEP 339:爱德华兹曲线数字签名算法 (EdDSA) +- JEP 360:密封类(预览) +- JEP 371:隐藏类 +- JEP 372:删除 Nashorn JavaScript 引擎 +- JEP 373:重新实现旧版 DatagramSocket API +- JEP 374:禁用和弃用偏向锁定 +- JEP 375:instanceof 的模式匹配(第二次预览,无改动) +- JEP 377:ZGC:可扩展的低延迟垃圾收集器(确定正式版) +- JEP 378:文本块(确定正式版) +- JEP 379:Shenandoah:一个低暂停时间的垃圾收集器(确定正式版) +- JEP 381:删除 Solaris 和 SPARC 端口 +- JEP 383:外内存访问API(第二孵化器) +- JEP 384:记录(第二次预览) +- JEP 385:弃用 RMI 激活以进行删除 + +### Java16新特性 + +- JEP 338:Vector API(孵化器) +- JEP 347:启用 C++14 语言功能 +- JEP 357:从 Mercurial 迁移到 Git +- JEP 369:迁移到 GitHub +- JEP 376:ZGC:并发线程栈处理 +- JEP 380:Unix 域套接字通道 +- JEP 386:Alpine Linux 端口 +- JEP 387:弹性元空间 +- JEP 388:Windows/AArch64 端口 +- JEP 389:外链 API(孵化器) +- JEP 390:基于值的类的警告 +- JEP 392:打包工具 +- JEP 393:外内存访问API(第三孵化器) +- JEP 394:instanceof 的模式匹配 +- JEP 395:记录 +- JEP 396:默认情况下强封装JDK内部 +- JEP 397:密封类(第二次预览) + +[![](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=Pdrk5&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=Pdrk5&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +### Java17新特性 + +2021年9月14日,java17正式发布(LTS)。长期支持版,支持到2029年。Oracle 宣布,从JDK17开始,后面的JDK都全部免费提供。 + +- JEP 306:恢复始终严格的浮点语义 +- JEP 356:增强型伪随机数发生器 +- JEP 382:新的 macOS 渲染管线 +- JEP 391:macOS/AArch64 端口 +- JEP 398:弃用 Applet API 以进行删除 +- JEP 403:强封装JDK内部 +- JEP 406:switch模式匹配(预览) +- JEP 407:删除 RMI 激活 +- JEP 409:密封类(正式确定) +- JEP 410:删除实验性 AOT 和 JIT 编译器 +- JEP 411:弃用安全管理器以进行删除 +- JEP 412:外部函数和内存 API(孵化器) +- JEP 414:Vector API(第二孵化器) +- JEP 415:上下文特定的反序列化过滤器 + +### Java18新特性 + +2022年3月22日发布。非长期支持版本。 + +- JEP 400:从JDK18开始,UTF-8是Java SE API的默认字符集。 +- JEP 408:从JDK18开始,引入了jwebserver这样一个简单的WEB服务器,它是一个命令工具。 +- JEP 416:使用方法句柄重新实现核心反射 +- JEP 418:互联网地址解析SPI +- JEP 413:Java API文档中的代码段(javadoc注释中使用
括起来的代码段会原模原样的生成到帮助文档中)
+- JEP 417:Vector API(第三孵化器)
+- JEP 419:Foreign Function & Memory API(第二孵化器)
+- JEP 420:switch 的模式匹配(第二次预览)
+- JEP 421:Object中的finalize()方法被移除
+
+### Java19新特性
+
+2022年9月20日发布。非长期支持的版本。直到 2023 年 3 月它将被 JDK 20 取代。
+
+- JEP 425:虚拟线程(预览版)
+    - 一种新的线程模型,即虚拟线程;"虚拟线程" 指的是一种轻量级线程,可以通过 JVM 进行管理和调度,而不需要操作系统进行支持
+- JEP 428:结构化并发(孵化器)
+    - 一组新的API和规范,用于优化并简化Java程序的并发编程
+- JEP 405:Record模式 (预览版)
+- JEP 427:switch语句中的模式匹配(第三次预览版)
+    - "switch语句中的模式匹配"表示该特性是针对 switch 语句的改进,可以使用模式匹配的方式处理 switch 语句中的分支
+- JEP 424:外部函数和内存API(预览版)
+    - “外部函数”指的是在Java程序中调用非Java语言编写的函数,比如C/C++函数
+    - “内存API”指的是在Java程序中直接操作内存的API
+- JEP 426:向量API(第四版孵化器)
+    - 一组专用于向量化处理的API,允许在Java程序中轻松高效地执行向量化计算
+
+### Java20新特性
+
+2023年3月21日发布。非长期支持版本。直到 2023 年 9月它将被 JDK 21 取代。
+
+- JEP 432: Record模式(第二次预览版)
+- JEP 433: switch的模式匹配 (第四次预览版)
+- JEP 434: 外部函数和内存API(第二次预览版)
+- JEP 438: 向量API (第五版孵化器)
+- JEP 429: Scoped Values (Incubator)
+- JEP 436: 虚拟线程(第二次预览版)
+- JEP 437: 结构化并发(第二版孵化器)
+
+### Java21新特性
+
+2023年9月19日发布。长期支持版本。
+
+- JEP 440:Record模式(正式确定)
+- JEP 441:switch的模式匹配(正式确定)
+- JEP 430:String Templates (Preview)
+- JEP 443:Unnamed Patterns and Variables (Preview)
+- JEP 445:Unnamed Classes and Instance Main Methods (Preview)
+- JEP 444:Virtual Threads(正式确定)
+- JEP 431:Sequenced Collections(正式确定)
+- JEP 452:Key Encapsulation Mechanism API
+- JEP 442:Foreign Function & Memory API (Third Preview)
+- JEP 453:Structured Concurrency (Preview)
+- JEP 446:Scoped Values (Preview)
+- JEP 448:Vector API (Sixth Incubator)
+- JEP 439:Generational ZGC
+- JEP 451:Prepare to Disallow the Dynamic Loading of Agents
\ No newline at end of file
diff --git a/posts/blog/编程技术/java/JavaSE/新特性/image51.png b/posts/blog/编程技术/java/JavaSE/新特性/image51.png
new file mode 100644
index 0000000..d500e03
Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/新特性/image51.png differ
diff --git a/posts/blog/编程技术/java/JavaSE/注解.md b/posts/blog/编程技术/java/JavaSE/注解.md
new file mode 100644
index 0000000..d932b54
--- /dev/null
+++ b/posts/blog/编程技术/java/JavaSE/注解.md
@@ -0,0 +1,130 @@
+---
+title: 注解
+date: 2024-09-22
+tags: [注解]
+---
+
+
+- `@Xxx`就是一个注解(annotation),jdk1.5才引入的
+- 可以标注在类、属性、方法等,编译阶段起作用
+- 作用:在不修改代码逻辑的前提下嵌入补充信息
+- 框架=反射+注解+设计模式
+
+## 常用注解
+
+- 使用:@Xxx(属性名=值,属性名=值…)
+
+- 详情
+    - 没有默认值的属性,一定要写
+    - 如果属性只有一个,并且属性名是value时,属性名可以不写
+    - 数组写法:@Xxx(属性名={值,值…}),一个元素可以省大括号
+
+- jdk内置注解
+  
+    |   |   |
+    |---|---|
+    |注解|描述|
+    |@Deprecated|用来标记过时的元素,在编译阶段遇到这个注解时会发出**提醒警告**,告诉开发者正在调用一个过时的元素比如过时的类、过时的方法、过时的属性等。|
+    |@Override|修饰实例方法,则该方法必须是个重写方法,否则就会编译失败。|
+    |@SuppressWarnings|在实际开发中,建议尽量不要忽略警告,而是真正的去解决警告|
+    |@SuppressWarnings("rawtypes")|抑制未使用泛型的警告|
+    |@SuppressWarnings("resource")|抑制未关闭资源的警告|
+    |@SuppressWarnings("deprecation")|抑制使用了已过时资源时的警告|
+    |@SuppressWarnings("all")|抑制所有警告|
+    |@FunctionalInterface|“函数式接口”的注解,该接口就有且**只能存在一个抽象方法**|
+    
+    - @Deprecated
+      
+        ```Java
+        //since 从那个版本开始已过时
+        //forRemoval true表示已移除
+        @Deprecated(since = "9",forRemoval = true)
+        ```
+        
+    - @FunctionalInterface
+      
+        这个是 JDK1.8 版本引入的新特性。使用@FunctionalInterface标注的接口,则该接口就有且只能存在一个抽象方法,否则就会发生编译错误。(注意:接口中的默认方法或静态方法可以有多个。)
+        
+
+## 自定义注解
+
+- 使用@interface定义注解,可以定义属性,**后面必须加括号**
+
+```Java
+//默认情况下,所有场景都可以使用
+public @interface MyAnnotation{
+		//属性,必须赋值不然报错,除非定义默认值
+		String name();
+		//默认值
+		boolean ok() default true;
+}
+```
+
+- 属性类型:8大基本数据类型、String、Class、枚举类型、注解类型,以上所有类型的一维数组形式;(注解不能嵌套)
+
+![image 49.png](注解/image49.png)
+
+- 所有自定义注解父类都是`Annotation` ,通过反编译注解可知
+
+## 元注解
+
+- 用来标注(自定义)注解的注解
+    - `**@Retention**` :设置注解的保持性
+        - 它表示注解存在阶段是保留在源代码(编译期),字节码(类加载)或者运行时(JVM中运行)
+        - 在@Retention注解中使用**枚举**`RetentionPolicy`来表示注解保留时期。
+            - `@Retention(RetentionPolicy.``**SOURCE**``)`:注解仅存在于源代码中,在字节码文件中不包含
+            - `@Retention(RetentionPolicy.``**CLASS**``)`:注解在字节码文件中存在,但运行时无法获得(**默认**)
+            - `@Retention(RetentionPolicy.``**RUNTIME**``)`:注解在字节码文件中存在,且运行时可通过**反射获取**
+    - `**@Target**` :设置注解可以出现的位置
+        - 用于描述注解可以出现的位置,使用**枚举**`ElementType`
+            - `@Target(ElementType.``**TYPE**``)`:作用于接口、类、枚举、注解
+            - `@Target(ElementType.``**FIELD**``)`:作用于属性、枚举的常量
+            - `@Target(ElementType.``**METHOD**``)`:作用于方法
+            - `@Target(ElementType.``**PARAMETER**``)`:作用于方法参数
+            - `@Target(ElementType.CONSTRUCTOR)`:作用于构造方法
+            - `@Target(ElementType.LOCAL_VARIABLE)`:作用于局部变量
+            - `@Target(ElementType.ANNOTATION_TYPE)`:作用于注解
+            - `@Target(ElementType.PACKAGE)`:作用于包
+            - `@Target(ElementType.TYPE_PARAMETER)`:作用于泛型,即泛型方法、泛型类和泛型接口。
+            - `@Target(ElementType.``**TYPE_USE**``)`:作用于任意类型。
+    - `@Document` :设置注解是否可以生成到帮助文档中
+        - 使用**javadoc.exe**工具可以从程序源代码中抽取类、方法、属性等注释形成一个源代码配套的API帮助文档,而该工具抽取时默认不包括注释内容。如果使用的注解被`@Documented`标注,那么该注解就能被javadoc.exe工具提取到API文档。
+    - `@Inherited` :设置注解是否支持继承
+        - Inherited意思是继承,一个被`@Inherited`注解了的注解修饰了一个父类,则它的**子类也继承了父类的注解**
+    - `@Repeatable` :设置注解在某一个元素上是否可以重复使用(Java8的新特性)
+        - Repeatable表示可重复的含义,该注解属于JDK1.8版本的新特性
+        - 使用:必须再定义一个复数形式的注解,且属性名为value的数组
+          
+            ![image129.png](注解笔记/image129.png)
+            
+
+## 反射注解
+
+- 需要反射的注解的保持性必须是`@Retention(RetentionPolicy.``**RUNTIME**``)`
+
+- 获取类上的所有注解、指定的某个注解
+  
+    ```Java
+    Annotation[] annotations = clazz.getAnnotations();
+    //判断是否有该注解
+    clazz.isAnnotationPresent(AnnotationTest01.class);
+    AnnotationTest01 an = clazz.getAnnotation(AnnotationTest01.class);
+    ```
+    
+- 获取属性上的所有注解、指定的某个注解
+  
+    ```Java
+    Annotation[] annotations = field.getAnnotations();
+    //判断属性是否有该注解
+    field.isAnnotationPresent(AnnotationTest02.class);
+    AnnotationTest02 an = field.getAnnotation(AnnotationTest02.class);
+    ```
+    
+- 获取方法上的所有注解、指定的某个注解
+  
+    ```Java
+    Annotation[] annotations = method.getAnnotations();
+    //判断方法是否有该注解
+    method.isAnnotationPresent(AnnotationTest02.class);
+    AnnotationTest02 an = method.getAnnotation(AnnotationTest02.class);
+    ```
\ No newline at end of file
diff --git a/posts/blog/编程技术/java/JavaSE/注解/image129.png b/posts/blog/编程技术/java/JavaSE/注解/image129.png
new file mode 100644
index 0000000..56ada0b
Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/注解/image129.png differ
diff --git a/posts/blog/编程技术/java/JavaSE/注解/image49.png b/posts/blog/编程技术/java/JavaSE/注解/image49.png
new file mode 100644
index 0000000..c173d3a
Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/注解/image49.png differ
diff --git a/posts/blog/编程技术/java/JavaSE/编码详解.md b/posts/blog/编程技术/java/JavaSE/编码详解.md
new file mode 100644
index 0000000..1077050
--- /dev/null
+++ b/posts/blog/编程技术/java/JavaSE/编码详解.md
@@ -0,0 +1,319 @@
+---
+title: 编码详解
+date: 2024-08-14
+tags: [编码详解]
+---
+
+
+## Unicode码
+
+- 是一种全世界通用的码表,是一种规范,为世界上的每个字符都分配了一个对应的值(码点), 范围从`**0**`到 `**0x10FFFF**`(十六进制),即 0 到 1,114,111 的十进制数,这涵盖了超过一百万个可能的字符位置,使用二进制表示最多需要 **21** 位(1占1位,其他占4位:1+4*5=21),当存储到计算机中时,由于最小的==数据存储单元==通常是字节(byte),所以会造成一定的浪费,选择合适的编码方式存储,减少浪费
+    - 码点:帅 —>U+5E05 😀 —> U+1F600(0x1F600)
+- 具体的 Unicode 范围和分类
+    - `U+0000` 到 `U+FFFF` 最常用的字符,2个字节即可表示
+      
+        Unicode 的码点被分为多个==平面==,每个平面包含 65,536 (2^16) 个码点。
+        
+        1. **基本多文种平面 (BMP, Basic Multilingual Plane)**:
+            - 范围:`U+0000` 到 `U+FFFF`
+            - 这是最常用的平面,包含了大多数常见字符,如拉丁字母、汉字、希腊字母、阿拉伯字母等。
+        2. **补充多文种平面 (Supplementary Multilingual Plane, SMP)**:
+            - 范围:`U+10000` 到 `U+1FFFF`
+            - 包含古代语言、少数民族文字、以及一些象形符号和乐谱符号等。
+        3. **补充表意平面 (Supplementary Ideographic Plane, SIP)**:
+            - 范围:`U+20000` 到 `U+2FFFF`
+            - 主要用于扩展的汉字字符集。
+        4. **补充专用平面 (Supplementary Special-purpose Plane, SSP)**:
+            - 范围:`U+E0000` 到 `U+EFFFF`
+            - 包含一些特殊用途的字符,例如用于标记和更改文本布局的字符。
+        5. **私人使用区 (Private Use Area, PUA)**:
+            - 范围:
+                - BMP 中的 `U+E000` 到 `U+F8FF`
+                - 平面 15 和 16 中的 `U+F0000` 到 `U+FFFFF` 以及 `U+100000` 到 `U+10FFFF`
+            - 这些区域不被分配给特定字符,可以由个人或组织自定义使用。
+
+## UTF-32编码
+
+- 是一种==不可变长度==的编码方式,它使用`**4**`个字节来表示一个 Unicode 码点,由于4个字节可以表示所有Unicode码,所以两个的二进制有效位完全一致
+    - 码点
+        - Unicode:帅 —>U+5E05 (101 1110 0000 0101)
+        - UTF-32: 帅 —>0x5E05 (0000 0000 0000 0000 0101 1110 0000 0101)
+- 字符长度固定,好处理,但是浪费空间
+
+## UTF-16编码
+
+- 是一种==可变长度==的编码方式,它可以使用`2或4` 个字节来表示一个 Unicode 码点,由于2个字节可以表示最常用字节,所以2个字节内的二进制有效位完全一致,2个字节(`U+FFFF`)以上的字符码点使用==代理对==表示(理解为两个码点)
+    - **高代理项(High Surrogate)**: 范围是 `U+D800` 到 `U+DBFF`。
+    - **低代理项(Low Surrogate)**: 范围是 `U+DC00` 到 `U+DFFF`。
+
+- 代理对的计算
+  
+    假设我们要计算 Unicode 码点 `U+1F600`(表情符号 `😀`)的代理对。
+    
+    1. **减去 0x10000**
+    
+    - 将 Unicode 码点减去 `0x10000`,因为代理对的编码范围从 `U+10000` 开始。
+    - 计算:
+        - `1F600 - 10000 = F600`
+        - `F600` 的二进制表示是 `0000 1111 0110 0000 0000`。
+    
+    2. **拆分高 10 位和低 10 位**
+    
+    - 将 `20` 位的二进制值分成高 `10` 位和低 `10` 位:
+        - 高 `10` 位:`0000111101` (`0x3D`)
+        - 低 `10` 位:`1000000000` (`0x260`)
+    
+    3. **添加偏移量(固定值)**
+    
+    - **高位代理**(High Surrogate):在高 `10` 位基础上加上 `0xD800`。
+        - 计算:
+            - `0x3D8 + 0xD800 = 0xD83D`
+    - **低位代理**(Low Surrogate):在低 `10` 位基础上加上 `0xDC00`。
+        - 计算:
+            - `0x260 + 0xDC00 = 0xDE00`
+    
+    4. **组合代理对**
+    
+    - 代理对的最终结果由高位代理和低位代理组成,表示为 `\uD83D\uDE00`。
+
+- 码点:
+    - Unicode:帅 —>U+5E05 (101 1110 0000 0101)
+    - UTF-16: 帅 —> 0x5E05 (0101 1110 0000 0101)
+    - Unicode:😀 —> U+1F600(1 1111 0110 0000 0000)
+    - UTF-16: 😀 —> 0xD83D0xDE00(1101 1000 0011 1101 1101 1110 0000 0000)
+- Windows、Java中的Char默认使用这种编码方式,对常用字符进行实现,使其更加高效
+
+## UTF-8编码
+
+- 是基于Unicode码的一种==可变长度==的编码方式,它用于将 Unicode 码点编码为字节序列(用于计算机存储或传输),使用 `1 到 4` 个字节来表示一个 Unicode 字符
+- 编码规则:根据Unicode码确认结构,将Unicode的二进制形式(前补0)插入结构即可
+    - 对于 **U+0000 到 U+007F** 的 Unicode 编码点(即标准 ASCII 范围),它们被直接编码为一个字节,二进制表示与原来的编码点相同。
+    - 对于 **U+0080 到 U+07FF** 的编码点,使用两个字节编码。
+        - 字节结构
+            - 第一字节:`110xxxxx`
+            - 第二字节:`10xxxxxx`
+    - 对于 **U+0800 到 U+FFFF** 的编码点,使用三个字节编码。
+        - 字节结构
+            - 第一字节:`1110xxxx`
+            - 第二字节:`10xxxxxx`
+            - 第二字节:`10xxxxxx`
+    - 对于 **U+10000 到 U+10FFFF** 的编码点,使用四个字节编码。
+        - 字节结构
+            - 第一字节:`11110xxx`
+            - 第二字节:`10xxxxxx`
+            - 第二字节:`10xxxxxx`
+- 编码
+    - Unicode:帅 —>U+5E05 (101 1110 0000 0101)
+    - UTF-8: 帅 —> 0xE50xB80xA5 (1110 0101 1011 1000 1000 0101)
+
+## 具体应用
+
+![image 45.png](编码详解/image45.png)
+
+- 文字的基本显示原理
+  
+    - 文件本质就是**一堆二进制**,点击文本文件,文本编辑器(应用程序)调用操作系统的IO操作,将二进制读到编辑器中,文本编辑器识别文件编码,在根据操作系统中显示文字的api中需要的编码格式(如:UTF-16)进行判断是否需要**解码或转码**,最后调用系统api,完成文字进行显示
+    
+    - 编辑器打开文件时如何识别文件编码?
+        - 编辑器打开文件时使用的编码通常取决于以下几个因素:
+          
+            1. **文件的 BOM (Byte Order Mark)**: 一些文件在开头包含一个 BOM,它是一种特殊的字节序列,用于标识文件的编码格式。如果文件有 BOM,编辑器通常会根据它来识别并使用相应的编码格式(如 UTF-8、UTF-16)。
+            2. **文件的内容**: 某些编辑器会根据文件内容尝试自动检测编码。例如,如果文件的内容包含常见的 ASCII 字符且没有特殊的非 ASCII 字符,编辑器可能会认为这是一个使用 UTF-8 或 ISO-8859-1 等常见编码的文件。
+            3. **编辑器的默认设置**: 如果文件没有 BOM,且编辑器无法通过内容明确识别编码,则编辑器会使用默认的编码设置来打开文件。这个默认设置通常可以在编辑器的配置或设置选项中进行修改。例如,许多编辑器的默认编码是 UTF-8。
+            4. **文件扩展名或类型**: 一些编辑器可能会根据文件的扩展名或类型来决定使用哪种编码。例如,某些特定类型的文件(如 HTML、XML)可能默认使用 UTF-8 编码。
+            5. **用户指定**: 在某些情况下,用户可以在打开文件时手动指定编码,或者在编辑器的设置中为特定类型的文件指定特定的编码。
+            
+            综上所述,当点开一个文件时,编辑器通常会按以下顺序决定使用哪种编码:BOM > 内容检测 > 默认设置 > 文件扩展名 > 用户指定的编码
+            
+
+## 读文件底层
+
+![image 1 25.png](编码详解/image125.png)
+
+UTF-8 → Unicode 码点 → UTF-16
+
+编码转换:_`decodeUTF8_UTF16` 方法内部_
+
+- 解码
+    - `decode3` :对3个字节进行解码,Unicode码点在U+0800 至 U+FFFF之间使用2个字节表示,使用`char`接收
+      
+        `decode4` :对4个字节进行解码,Unicode码点在U+10000 至 U+10FFFF之间使用4个字节表示,使用`int`接收
+    
+- 转UTF-16:
+    - 2个字节内的Unicode码,可以使用**char**表示(==Unicode码==UTF-16==)
+    - 2个字节以上的Unicode码,转UTF-16分成一对代理对,**两个char表示**
+        - `Character.`_`highSurrogate`_`(uc)` 、`Character.`_`lowSurrogate`_`(uc)` 返回char
+
+> ==**在Java中我们输入的字符,它会直接转成byte数组(UTF-16编码),这个过程不可见,是对字符的直接映射**==
+
+## 字符串存储
+
+- 由于字符串底层是byte数组,所以要将1个char转成2个byte
+    - `StringUTF16.putChar(byte[] val, int index, int c)` :传入byte数组、索引值、UTF-16编码值(int表示),对UTF-16编码值进行编码,编出的每个字节传入数组
+
+## 字符串输出
+
+将字符串输出:底层就是将byte数组变为char数组,然后将字符数组输出
+
+- `StringUTF16.getChar` :调用getChar方法,将2个byte值合并为1个char类型
+
+## 字符编码转换
+
+UTF16 —> Unicode —> 自定义编码
+
+- UTF-16一个单位(16位)转Unicode,两者一致
+- UTF-16代理对转Unicode
+
+![image 2 17.png](编码详解/image217.png)
+
+- Unicode转成自定义编码
+
+## 小结
+
+- 不同编码下,同一个字符对应的二进制不同
+    - 一个“中”字,对应的码点是 U+4E2D
+        - 在`UTF-8`中对应的是3个字节(E4 B8 AD)
+        - 在`UTF-16`中对应的是2个字节(4E 2D)
+- 一段二进制在不同格式中,含义不一致
+    - 一个字节:1111 1111
+        - 在`UTF-8`中是无效的(不存在)
+        - 在`ISO-8859-1`(又称Latin-1)编码中,对应的字符是`**ÿ**`
+- 在Java中,`char`类型使用的是**UTF-16小端编码**(Little Endian)
+    - 大端序或小端序是什么意思?
+      
+        - 大端序(Big Endian)**和**小端序(Little Endian)是计算机系统中存储和传输多字节数据(如整数、浮点数等)时的两种不同字节排列方式。
+        
+        ### 1. **大端序(Big Endian)**
+        
+        - **定义**:大端序是一种字节顺序的表示方法,其中最重要的字节(最高有效字节,MSB)存储在最低的内存地址,而最不重要的字节(最低有效字节,LSB)存储在最高的内存地址。
+        - **示例**:
+            - 假设有一个 32 位的整数 `0x12345678`,在大端序中存储的顺序是:
+                - 地址最低处存储 `0x12`
+                - 接下来是 `0x34`
+                - 然后是 `0x56`
+                - 地址最高处存储 `0x78`
+            - 内存布局:[0x12] [0x34] [0x56] [0x78]
+        
+        ### 2. **小端序(Little Endian)**
+        
+        - **定义**:小端序则是将最低有效字节(LSB)存储在最低的内存地址,而最高有效字节(MSB)存储在最高的内存地址。
+        - **示例**:
+            - 使用相同的 32 位整数 `0x12345678`,在小端序中存储的顺序是:
+                - 地址最低处存储 `0x78`
+                - 接下来是 `0x56`
+                - 然后是 `0x34`
+                - 地址最高处存储 `0x12`
+            - 内存布局:[0x78] [0x56] [0x34] [0x12]
+        
+        ### 3. **为什么存在大端序和小端序**
+        
+        - **历史与架构差异**:大端序和小端序之所以并存,部分原因是不同计算机架构的设计历史。某些处理器(如 IBM 早期的大型机)采用了大端序,而 Intel x86 架构则采用了小端序。选择不同的字节序有时是出于对硬件设计的考虑。
+        - **互操作性**:在网络协议中,通常采用大端序(也称为“网络字节序”),因为它在跨不同平台之间传输数据时更容易理解。
+        
+        ### 4. **字节序的影响**
+        
+        - **数据处理**:在处理多字节数据时,字节序的差异会影响如何解释和传输数据。例如,当一个小端序系统发送数据给大端序系统时,如果不做转换,接收方可能会错误地解释数据。
+        - **编程中的处理**:开发者在编写跨平台代码时需要特别注意字节序问题,尤其是在涉及文件读写、网络通信或低级内存操作时。
+        
+        ### 5. **识别和转换**
+        
+        - **转换工具和函数**:许多编程语言和操作系统提供了函数来识别当前系统的字节序,并在大端序和小端序之间进行转换,如 C/C++ 中的 `htonl`(host to network long)和 `ntohl`(network to host long)。
+        
+        总结来说,大端序和小端序是两种不同的字节存储顺序,了解它们的差异对于跨平台编程和数据传输非常重要。
+    
+- 文字复制、黏贴的原理
+    - 复制的是Unicode码(Unicode码对应的二进制具体看操作系统的要求)
+
+### Unicdoe-UTF-16-UTF-8u转换代码
+
+```Java
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+public class ThreeCode {
+    public static void main(String[] args) {
+        Scanner scanner=new Scanner(System.in);
+        System.out.print("请输入字符:");
+        String str = scanner.nextLine().trim();
+        System.out.println();
+        //String str = "邱😄";
+        final String H="\\u";
+        char[] chars_utf_16 = str.toCharArray();//UTF-16编码
+        List> list=new ArrayList<>();
+        //将utf-16码分为每个对应的字符
+        int i = 0;
+        while (i < chars_utf_16.length) {
+            List characters = new ArrayList<>(2);
+            list.add(characters);
+            if (i + 1 < chars_utf_16.length && Character.isSurrogatePair(chars_utf_16[i], chars_utf_16[i + 1])) {
+                // 当前字符和下一个字符形成代理对
+                characters.add(chars_utf_16[i]);
+                characters.add(chars_utf_16[i + 1]);
+                i += 2;  // 跳过下一个字符,因为已经配对处理
+            } else {
+                // 当前字符不是代理对中的一部分
+                characters.add(chars_utf_16[i]);
+                i++;
+            }
+        }
+        int len=list.size();
+        String[] unicode = new String[len];
+        String[] utf_8 = new String[len];
+        String[] utf_16 = new String[len];
+        for (int k = 0; k < list.size(); k++) {
+            List characters = list.get(k);
+            int size = characters.size();
+            if (size == 0) break;
+            StringBuilder utf16Builder = new StringBuilder();
+            StringBuilder utf8Builder = new StringBuilder();
+            if (size == 1) {
+                // 单个字符处理
+                char ch = characters.get(0);
+                // UTF-16
+                utf16Builder.append(H).append(Integer.toHexString(ch).toUpperCase());
+                // Unicode
+                unicode[k] = utf16Builder.toString();
+                // UTF-8
+                for (byte b : String.valueOf(ch).getBytes(StandardCharsets.UTF_8)) {
+                    utf8Builder.append(H).append(byte_utf8(b));
+                }
+            } else if (size == 2) {
+                // 代理对处理
+                char high = characters.get(0);
+                char low = characters.get(1);
+                // UTF-16
+                utf16Builder.append(H).append(Integer.toHexString(high).toUpperCase())
+                        .append(H).append(Integer.toHexString(low).toUpperCase());
+                // Unicode
+                int un = Character.toCodePoint(high, low);
+                unicode[k] = H + Integer.toHexString(un).toUpperCase();
+                // UTF-8
+                char[] surrogatePair = {high, low};
+                for (byte b : new String(surrogatePair).getBytes(StandardCharsets.UTF_8)) {
+                    utf8Builder.append(H).append(byte_utf8(b));
+                }
+            }
+            // 统一赋值
+            utf_16[k] = utf16Builder.toString();
+            utf_8[k] = utf8Builder.toString();
+        }
+        //
+        printArray("Unicode码",unicode);
+        printArray("UTF-16编码",utf_16);
+        printArray("UTF-8编码",utf_8);
+    }
+    static void printArray(String code,String[] strings){
+        System.out.print(code+":\t");
+        for (String string : strings) {
+            System.out.print(string+"\t");
+        }
+        System.out.println();
+    }
+    static String byte_utf8(byte b){
+        String str = Integer.toHexString(b);
+        if(str.length()==2)
+            return str.toUpperCase();
+        return str.substring(6, 8).toUpperCase();
+    }
+}
+```
\ No newline at end of file
diff --git a/posts/blog/编程技术/java/JavaSE/编码详解/image125.png b/posts/blog/编程技术/java/JavaSE/编码详解/image125.png
new file mode 100644
index 0000000..cc5f4eb
Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/编码详解/image125.png differ
diff --git a/posts/blog/编程技术/java/JavaSE/编码详解/image217.png b/posts/blog/编程技术/java/JavaSE/编码详解/image217.png
new file mode 100644
index 0000000..309ba64
Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/编码详解/image217.png differ
diff --git a/posts/blog/编程技术/java/JavaSE/编码详解/image45.png b/posts/blog/编程技术/java/JavaSE/编码详解/image45.png
new file mode 100644
index 0000000..458836d
Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/编码详解/image45.png differ
diff --git a/posts/blog/编程技术/java/JavaSE/网络编程.md b/posts/blog/编程技术/java/JavaSE/网络编程.md
new file mode 100644
index 0000000..0c31f73
--- /dev/null
+++ b/posts/blog/编程技术/java/JavaSE/网络编程.md
@@ -0,0 +1,137 @@
+---
+title: 网络编程
+date: 2024-09-26
+tags: [网络编程]
+---
+
+
+## 理论基础
+
+[[计算机网络]]
+
+## InetAddress类
+
+- `java.net.IntAddress`类用来封装计算机的**IP地址**和**DNS**(没有端口信息,底层会自动连接DNS服务器进行域名解析),是java对IP地址的高层表示大多数其它网络类都要用到这个类,包括Socket、ServerSocket、URL、DatagramSocket、DatagramPacket等
+- 常用**静态**方法:获取`InetAddress`对象
+  
+    - `getLocalHost()` 得到本机的InetAddress对象,其中封装了IP地址和主机名
+    
+    - `getByName(String host)` 传入目标主机的名字或IP地址得到对应的
+      
+        ```Java
+        //通过域名也可以解析
+        InetAddress inetAddress = InetAddress.getByName("www.baidu.com");
+        System.out.println(inetAddress.getHostAddress());
+        ```
+    
+- 常用实例方法
+    - `getHostAddress()` 获取IP地址
+    - `getHostName()` 获取主机名/域名
+
+## URL类
+
+- URL是**统一资源定位符**,对可以从互联网上得到的资源的位置和访问方法的一种**简洁的表示**,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它,在浏览器中访问网页时,**地址栏显示的地址就是URL**
+- URL由4部分组成:协议、存放资源的主机域名、端口号、资源文件名(如果未指定该端口号,则使用协议默认的端口)
+    - <协议>://<域名或IP>:<端口>/<路径>
+        - <协议>://<域名或IP>是必需的,<端口>/<路径>有时可省略,如:[https://www.baidu.com](https://www.baidu.com/)
+- 为了方便程序员编程,JDK中提供了URL类,该类的全名是`java.net.URL`,该类封装了大量复杂的涉及从远程站点获取信息的细节,可以使用它的各种方法来对URL对象进行分割、合并等处理
+- 构造方法:URL url = new URL(“[http://127.0.0.1:8080/oa/index.html?name=zhangsan#tip”](http://127.0.0.1:8080/oa/index.html?name=zhangsan#tip%E2%80%9D));
+- 常用方法:
+  
+    | 方法 | 作用 |
+    |---|---|
+    |getProtocol()|获取协议|
+    |getProtocol()|获取域名|
+    |getDefaultPort()|获取默认端口|
+    |getPort()|获取端口|
+    |getPath()|获取路径|
+    |getFile()|获取资源|
+    |getQuery()|获取数据|
+    |getRef()|获取锚点|
+    |openStream()|打开到此URL的连接,并返回一个用于从该连接读入的InputStream|
+    
+    - 使用URL类的openStream()方法可以打开到此URL的连接并返回一个用于从该连接读入的InputStream,实现最简单的网络爬虫
+
+## Socket套接字
+
+- 存在于传输层和应用层之间,是传输层供给应用层的编程接口
+    - 我们开发的网络应用程序位于应用层,**TCP和UDP属于传输层协议**,在应用层如何使用传输层的服务呢?在应用层和传输层之间,则是使用套接Socket来进行分离。  
+      
+
+- Socket就是应用层与传输层之间的桥梁。使用Socket编程可以开发客户机和服务器应用程序,可以在本地网络上进行通信,也可通过Internet在全球范围内通信。
+- TCP协议和UDP协议是传输层的两种协议,Socket是传输层供给应用层的编程接口,所以Socket编程就分为TCP编程和UDP编程两类。
+- `Socket`类是两台机器间通信的端点
+    - 构造方法:`Socket(InetAddress a, int p)` 创建套接字并连接到指定IP地址的指定端口号
+    - 实例方法:
+      
+        |方法|描述|
+        |---|---|
+        |InetAddress getInetAddress()|返回此套接字连接到的远程 IP 地址|
+        |InputStream getInputStream()|返回此套接字的输入流(接收网络消息)|
+        |OutputStream getOutputStream()|返回此套接字的输出流(发送网络消息)|
+        |void shutdownInput()|禁用此套接字的输入流|
+        |void shutdownOutput()|禁用此套接字的输出流。|
+        |void close()|关闭此套接字(默认会关闭IO流)|
+        
+        > [!important]
+        > 
+        > - 关闭流会导致socket流关闭
+        > - 使用**字节流、字符流**接收消息时,当对方消息发送完毕,但**没有关闭输出流**,此时服务端会在read方法中堵塞,使用`shutdownInput` 方法解决堵塞问题
+        
+
+### TCP协议下使用
+
+- 概念:
+- **ServerSocket**类:用于实现服务器套接字(Server服务端)。服务器套接字等待请求通过网络传入
+    - 构造方法:`ServerSocket(int port)` 传入开启服务的端口
+    - 实例方法:
+      
+        |方法|描述|
+        |---|---|
+        |Socket accept()|侦听要连接到此套接字并接受它|
+        |InetAddress getInetAddress()|返回此服务器套接字的本地地址|
+        |void close()|关闭此套接字|
+        
+
+![image 50.png](网络编程/image50.png)
+
+### UDP协议下使用
+
+- 概念:
+
+- UDP协议编程概述
+    - 在UDP通信协议下,两台计算机之间进行数据交互,**并不需要先建立连接**,发送端直接往指定的IP和端口号上发送数据即可,但是它并不能保证数据一定能让对方收到,也不能确定什么时候可以送达。
+
+- `java.net.DatagramSocket`类和`java.net.DatagramPacket`类是使用UDP编程中需要使用的两个类,并且发送端和接收端都需要使用这个俩类,并且发送端与接收端是两个独立的运行程序。
+    1. `DatagramSocket`:作为基于UDP协议的Socket,负责接收和发送数据,创建接收端时需要指定端口号
+        - 构造方法:  
+            DatagramSocket() 创建==发送端==的数据报套接字  
+            DatagramSocket(int port) 创建==接收端==的数据报套接字,并指定端口号
+        - 实例方法:  
+            send(DatagramPacket p) 发送数据报。  
+            receive(DatagramPacket p) 接收数据报。  
+            close() 关闭数据报套接字。
+    2. `DatagramPacket`:负责把数据打包(打包的数据为byte类型的数组),创建==发送端时需指定接收端的IP地址和端口==
+        - 构造方法:  
+            DatagramPacket(byte buf[], int offset, int length)
+            
+            - 创建接收端的数据报
+            
+            DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port)
+            
+            - 创建发送端的数据报,并指定接收端的IP地址和端口号。
+        - 实例方法:  
+            public synchronized byte[] getData()
+            
+            - 返回数据报中存储的数据
+            
+            public synchronized int getLength()
+            
+            - 获得发送或接收数据报中的长度
+
+## 应用
+
+- 当编写一个web服务器的时候,需要注意以下几点
+    - http请求是建立在tcp/ip协议的基础下的,所以使用ServerSocket
+    - get请求没有请求头,post请求有请求头
+    - 处理请求时,一个请求分为请求行、请求头、空格、请求体,请求体前面的部分会先flush过来,请求体再过来,所以可以先解析请求行、请求头、空格的内容
\ No newline at end of file
diff --git a/posts/blog/编程技术/java/JavaSE/网络编程/image50.png b/posts/blog/编程技术/java/JavaSE/网络编程/image50.png
new file mode 100644
index 0000000..431a201
Binary files /dev/null and b/posts/blog/编程技术/java/JavaSE/网络编程/image50.png differ
diff --git a/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础.md b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础.md
new file mode 100644
index 0000000..5fd9918
--- /dev/null
+++ b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础.md	
@@ -0,0 +1,795 @@
+---
+title: Java Web 开发基础
+date: 2025-01-26
+tags: [JavaWeb]
+---
+
+
+## Java Web 开发基础
+
+- C/S架构(Client/Server)
+    - 优点:
+        - 高性能、用户体验好
+    - 缺点:
+        - 需要安装且不方便更新
+        - 不方便维护
+    - 二层:客户端、服务器
+- B/S架构(Browser/Server)
+    - 优点:
+        - 不需要安装、使用方便
+        - 更新方便
+    - 缺点:
+        - 性能有一点限制
+    - 三层
+        - 表现层
+        - 业务逻辑层
+        - 数据访问层
+- Web 开发相关知识
+  
+    - 静态web资源:固定的文件,如视频、图片、文件等
+      
+        - 只需要web服务器处理
+        
+        ![image28.png](Java Web 开发基础/image28.png)
+        
+    - 动态web资源:根据用户要求动态生成的内容,所以依赖服务器的处理
+        - 需要处理需求的代码
+        - 返回json资源、生成的网页(jsp)
+    
+    ![image](Java Web 开发基础/image114.png)
+    
+    > 这种开发方式是因为之前的javaScript的发展不成熟,浏览器自身性能不足、对js的支持不统一、异步请求不成熟,所以动态页面全都靠生成响应
+    
+    > [!important] **Web应用开发好后,若想供外界访问,需要把web应用所在目录交给web服务器管理,这个过程称之为虚似目录的映射**
+    
+    ![1_Java_Web开发基础.pptx](Java Web 开发基础/1_Java_Web开发基础.pptx)
+
+
+
+![image28.png](Java Web 开发基础/image28.png)
+
+
+
+## 了解Tomcat
+
+- 文件位置
+    - bin中有启动程序
+    - config中有配置文件
+- 项目部署
+    - 将项目打包成war包,放在webapps中,并启动即可
+    - 静态文件有默认的servlet进行响应
+- 配置信息
+    - 乱码配置
+    - 管理配置
+
+## **Servlet 编程**
+
+- 基本作用
+    - 处理网络请求类
+    - 接收请求
+    - 响应内容
+- 生命周期:每个请求对应一个线程
+  
+    - 
+      
+        ![image 2 7.png](Java Web 开发基础/image27.png)
+        
+    - 加载并实例化到Tomcat
+    - 初始化,调用`init()`方法,可自定义内容
+    - 处理请求调用`service()`方法
+    - 销毁,服务器关闭自会调用`destroy()`方法,并释放资源
+    
+    ![image 3 6.png](Java Web 开发基础/image36.png)
+    
+- Servlet开发
+  
+    - 创建JavaEE项目
+    - 创建类实现Servlet接口(三种实现方式)
+      
+        - 实现Servlet接口
+        
+        - 继承GenericServlet(可用于其他请求的处理)
+          
+            GenericServlet 是 Servlet 接口的实现类,提供了部分方法的默认实现。它抽象化了请求的协议细节,因此适合开发非 HTTP 协议的 Servlet,也简化了代码书写。
+            
+            特点:
+            
+            - 实现了 Servlet 接口的所有方法,除了 service() 方法。
+            - 常用于不依赖 HTTP 的协议,例如 FTP 或其他自定义协议。
+        
+        - ==继承HttpServlet==
+    
+    - 配置地址映射(多种方式)
+      
+        - 使用web.xml文件
+        
+        ```XML
+        
+        		自定义
+        		类的全限定名
+        
+        
+        
+        		与自定义对应
+        		/hello
+        
+        ```
+        
+    - 使用注解的方式:
+        - `@WebServlet("/hello")` 等同于 xml配置
+            - 通配符:`@WebServlet("/*")` 、`@WebServlet("/*.html")`
+            - 多路径:`@WebServlet({"/login", "/signin"})`
+            - 启动设置:`@WebServlet(value = "/test",``**loadOnStartup**` `= 1)` 这样服务器启动时直接加载,值是优先级
+    
+    - 处理post请求
+      
+        ```Java
+        @Override
+        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+            //首先设置一下响应类型
+            resp.setContentType("text/html;charset=UTF-8");
+            //获取POST请求携带的表单数据
+            Map map = req.getParameterMap();
+            //判断表单是否完整
+            if(map.containsKey("username") && map.containsKey("password")) {
+                String username = req.getParameter("username");
+                String password = req.getParameter("password");
+        
+                //权限校验(待完善)
+            }else {
+                resp.getWriter().write("错误,您的表单数据不完整!");
+            }
+        }
+        ```
+        
+    - 文件下载、上传
+      
+        ```Java
+        @WebServlet("/file")
+        public class FileServlet extends HttpServlet {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+              resp.setContentType("image/png");
+              ServletOutputStream outputStream = resp.getOutputStream();
+              //将文件往流输出即可...
+            }
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+                Part part = req.getPart("表单名称");
+                InputStream inputStream = part.getInputStream();
+                //将上传文件接收即可....
+            }
+            
+        }
+        ```
+        
+    - 重定向
+      
+        ```Java
+        resp.sendRedirect("https://chat.deepseek.com/");
+        ```
+        
+    - 请求转发,==只能项目内部处理==,并且方法类型一致
+      
+        ```Java
+        req.getRequestDispatcher("/hh").forward(req, resp);;
+        ```
+        
+    - 常用类
+        - ServletConfig:每个Servlet对应的配置数据
+        - ServletContext:getServletContext()所有的Servlet公用的配置
+            - `getServletContext()` 获取
+            - 数据共享:context.setAtrribute(”key”,obj)
+            - 请求转发:
+              
+                ```Java
+                context.getRequestDispatcher("/time").forward(req,resp);
+                ```
+                
+    
+- maven项目启动Servlet
+    - 下载Tomcat
+    - 开maven项目
+        - 导入servlet依赖
+          
+            ```SQL
+            
+                  jakarta.servlet
+                  jakarta.servlet-api
+                  5.0.0 
+                  provided
+            
+            ```
+        
+    - 设置facet(将项目标记为web项目)
+      
+        ![image 4 6.png](Java Web 开发基础/image46.png)
+        
+    - 对项目进行打包
+      
+        ![image 5 5.png](Java Web 开发基础/image55.png)
+        
+    - 动态部署到Tomcat
+      
+        ![image 6 3.png](Java Web 开发基础/image63.png)
+        
+        - 调整url
+        
+        ![image 7 3.png](Java Web 开发基础/image73.png)
+        
+        关于facet和artifact
+        
+        ![image 8 3.png](Java Web 开发基础/image83.png)
+        
+          
+        
+          
+        
+
+## Cookie 与 Session
+
+- 作用:==保持会话、跟踪用户状态==
+
+**Cookie**
+
+- 浏览器访问服务器,服务器向浏览器发送的一段数据,浏览器保持并每次请求都会携带cookie
+- 通过这段数据用来识别用户,这就是cookie
+- 应用:记住我功能(设置cookie失效)
+
+- 使用
+  
+    ```Java
+    Cookie cookie = new Cookie("test", "yyds");
+    resp.addCookie(cookie);
+    resp.sendRedirect("time");
+    ```
+    
+    ```Java
+    for (Cookie cookie : req.getCookies()) {
+        System.out.println(cookie.getName() + ": " + cookie.getValue());
+    }
+    ```
+    
+    - 一些属性
+        - name - Cookie的名称,Cookie一旦创建,名称便不可更改
+        - value - Cookie的值,如果值为Unicode字符,需要为字符编码。如果为二进制数据,则需要使用BASE64编码
+        - **maxAge** - Cookie失效的时间,单位秒。如果为正数,则该Cookie在maxAge秒后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为-1。
+        - secure - 该Cookie是否仅被使用安全协议传输。安全协议。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false。
+        - path - Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”。
+        - domain - 可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“[google.com](http://google.com/)”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”。
+        - comment - 该Cookie的用处说明,浏览器显示Cookie信息的时候显示该说明。
+        - version - Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的RFC 2109规范
+
+**Session**
+
+- 浏览器每次请求服务器时,服务器会创建一个Session对象,表示该对话,与WebSocket的session不同
+- 原理:(**依赖于cookie**)在http请求上的应用,使用session,==tomcat会自动将SessionId通过cookie返回给浏览器==,通过这样保证session与浏览器匹配
+
+- 使用:保存登录状态
+  
+    ```Java
+    HttpSession session = req.getSession();
+    session.setAttribute("user", user);
+    ```
+    
+    ```Java
+    HttpSession session = req.getSession();
+    User user = (User) session.getAttribute("user");
+    if(user == null) {
+    resp.sendRedirect("login");
+    return;
+    }
+    ```
+    
+    - session也有过期时间,默认30分钟,即使sessionID存在
+      
+        ```Java
+        session.invalidate();
+        ```
+        
+
+> [!important] session并不依赖cookie,如果cookie被禁用,服务器可以通过响应体返回
+
+## Filter
+
+- 流程,放行静态资源
+  
+    ![image 9 3.png](Java Web 开发基础/image93.png)
+    
+    ![image 10 3.png](Java Web 开发基础/image103.png)
+    
+- 使用流程,创建过滤器类实现,只需要实现Filter/HttpFilter/接口(类似Servlet),并添加`@WebFilter("/*")`注解即可
+  
+    ```Java
+    @WebFilter("/*")   //路径的匹配规则和Servlet一致,这里表示匹配所有请求
+    public class TestFilter implements Filter {
+        @Override
+        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+             System.out.println("进入过滤器");
+            //放行
+            filterChain.doFilter(servletRequest, servletResponse);
+            System.out.println("出过滤器");
+        }
+    }
+    ```
+    
+    - 多个·过滤器的过滤顺序是按照==类名的自然排序进行==
+
+## Listener
+
+- 监听器,监听事件发生(==不常用==)
+- 常见监听器,一种是创建销毁的、一种是属性变化
+  
+    |关于ServletContext|关于HttpSession|关于ServletRequest|
+    |---|---|---|
+    |ServletContextListener|HttpSessionListener|ServletRequestListener|
+    |ServletContextAttributeListener|HttpSessionAtrributeListener|ServletRequestAtrributeListener|
+    
+- 演示
+  
+    ```Java
+    @WebListener
+    public class TestListener implements HttpSessionListener {
+    @Override
+    public void sessionCreated(HttpSessionEvent se) {
+    System.out.println("有一个Session被创建了");
+    }
+    }
+    ```
+    
+
+## Tomcat类加载机制
+
+为什么我们写好的servlet编译好,放到Tomcat的文件夹下就可以进行运行?
+
+- 规范了开发者的文件布局,并指定了放在那个文件夹下
+- Tomcat实现了一套自己的类加载器,启动后类加载器将文件夹下的class进行加载,包括依赖包,并**互相进行隔**
+
+它启动时构建出这样一棵**类加载器树**:
+
+```Plain
+Bootstrap ClassLoader
+  └── System ClassLoader
+        └── Common ClassLoader
+              ├── Shared ClassLoader
+              └── WebAppClassLoader (每个 web 应用独立一套)
+```
+
+### 📌 WebAppClassLoader 的职责:
+
+- 加载 `WEB-INF/classes/` 下的所有 `.class` 文件
+- 加载 `WEB-INF/lib/` 下所有 jar 包
+- 保证每个 web 应用的类**互相隔离**
+
+> 所以你会发现:
+> 
+> **A 项目里的类不会影响 B 项目,即便 jar 重名,版本不同也互不冲突。**
+
+- 📌 2️⃣ 类命名空间的本质:
+  
+    在 JVM 里,一个类由:
+    
+    ```Plain
+    Class对象 = (ClassLoader实例 + 类的全限定名)
+    ```
+    
+    唯一确定。
+    
+    所以:
+    
+    - `A类` 由 ClassLoaderA 加载 和 `A类` 由 ClassLoaderB 加载
+    - 即使字节码一样,JVM 也视为**两个不同的 Class 实例**
+    
+      
+    
+- 将class文件变成了一个个class类
+
+- 手写小demo
+  
+    好!你这个需求就是**手写一个自定义 ClassLoader + 简单的命令行容器**,我来给你直接做个可跑 demo,完全满足你的需求:
+    
+    ---
+    
+    ## 📖 📦 功能:
+    
+    - `A.java`、`B.java` 编译好放到某个文件夹(比如 `./app-classes/`)
+    - 写个自定义 `ClassLoader` 动态加载目录下 class 文件
+    - 控制台输入 `A` → 调用 A 的 main 方法
+    - 输入 `B` → 调用 B 的 main 方法
+    
+    ---
+    
+    ## 📦 目录结构:
+    
+    ```Plain
+    项目/
+    ├── ManagerApp.java      # 管理容器
+    ├── MyClassLoader.java   # 自定义 ClassLoader
+    ├── app-classes/         # 存放编译好的 A.class、B.class
+    │   ├── A.class
+    │   └── B.class
+    ```
+    
+    ---
+    
+    ## 📌 1️⃣ A.java / B.java(单独写好,放在 `app-classes` 目录,编译成 .class)
+    
+    A.java:
+    
+    ```Java
+    public class A {
+        public static void main(String[] args) {
+            System.out.println("A");
+        }
+    }
+    ```
+    
+    B.java:
+    
+    ```Java
+    public class B {
+        public static void main(String[] args) {
+            System.out.println("B");
+        }
+    }
+    ```
+    
+    编译:
+    
+    ```Shell
+    javac A.java B.java
+    # 放到 app-classes/
+    ```
+    
+    ---
+    
+    ## 📌 2️⃣ 自定义 ClassLoader
+    
+    MyClassLoader.java
+    
+    ```Java
+    import java.io.*;
+    
+    public class MyClassLoader extends ClassLoader {
+        private String classDir;
+    
+        public MyClassLoader(String classDir) {
+            this.classDir = classDir;
+        }
+    
+        @Override
+        protected Class findClass(String name) throws ClassNotFoundException {
+            try {
+                // 读取 .class 文件字节
+                String filePath = classDir + name + ".class";
+                FileInputStream fis = new FileInputStream(filePath);
+                byte[] bytes = fis.readAllBytes();
+                fis.close();
+                // 定义类
+                return defineClass(name, bytes, 0, bytes.length);
+            } catch (IOException e) {
+                throw new ClassNotFoundException("找不到类: " + name);
+            }
+        }
+    }
+    ```
+    
+    ---
+    
+    ## 📌 3️⃣ 管理容器主程序
+    
+    ManagerApp.java
+    
+    ```Java
+    import java.lang.reflect.Method;
+    import java.util.Scanner;
+    
+    public class ManagerApp {
+        public static void main(String[] args) throws Exception {
+            String classDir = "./app-classes/";  // class 文件目录
+            MyClassLoader loader = new MyClassLoader(classDir);
+    
+            Scanner scanner = new Scanner(System.in);
+            System.out.println("请输入类名 (A/B):");
+    
+            while (true) {
+                String input = scanner.nextLine();
+                if ("exit".equalsIgnoreCase(input)) {
+                    break;
+                }
+    
+                try {
+                    // 动态加载类
+                    Class clazz = loader.loadClass(input);
+                    // 反射调用 main 方法
+                    Method mainMethod = clazz.getMethod("main", String[].class);
+                    mainMethod.invoke(null, (Object) new String[]{});
+                } catch (Exception e) {
+                    System.out.println("执行出错:" + e.getMessage());
+                }
+            }
+    
+            System.out.println("已退出");
+        }
+    }
+    ```
+    
+    ---
+    
+    ## 📌 4️⃣ 运行方式
+    
+    1️⃣ 编译 A、B:
+    
+    ```Shell
+    javac A.java B.java
+    ```
+    
+    放入 `./app-classes/`
+    
+    2️⃣ 编译管理器:
+    
+    ```Shell
+    javac MyClassLoader.java ManagerApp.java
+    ```
+    
+    3️⃣ 运行:
+    
+    ```Shell
+    java ManagerApp
+    ```
+    
+    👉 输入:
+    
+    ```Plain
+    A
+    ```
+    
+    输出:
+    
+    ```Plain
+    A
+    ```
+    
+    👉 再输入:
+    
+    ```Plain
+    B
+    ```
+    
+    输出:
+    
+    ```Plain
+    B
+    ```
+    
+    ---
+    
+    ## 📖 总结:
+    
+    ✅ 你现在就实现了:
+    
+    - 手写 ClassLoader 动态加载 class 文件
+    - 动态执行 main 方法
+    - 控制台命令动态调度
+    
+    ---
+    
+    ## 📌 扩展进阶:
+    
+    你现在这就是 Tomcat 的 WebAppClassLoader 的简化版!
+    
+    要不要我再帮你做个**多线程执行+ThreadLocal隔离+热更新 class 文件**版?要就说一声👌
+    
+
+在这套规则下,才让我们可以编写servlet然后直接部署,通过tomcat管理
+
+## JSP 编程
+
+- 动态Web页面,基本上已经淘汰,js足够强大,不需要因为部分数据的更新,而更新整个页面
+- 结构:html+java 结合(<% … %>)
+- 本质:
+  
+    - JSP ——> Servlet
+    - JSP 通过**JSP 编译器** 生成Servlet (继承自 `HttpServlet`),调用javac对Servlet进行编译,后使用反射进行加载
+    
+    - 运行时调用Java编译器
+      
+        在 Java 程序中调用 `javac` 来编译 Java 文件,通常是通过执行系统命令来实现的。可以使用 `java.lang.ProcessBuilder` 或 `Runtime.getRuntime().exec()` 来执行 `javac` 命令。这些方法允许 Java 程序启动一个外部进程(在这种情况下是 `javac` 编译器)来编译 Java 源文件。
+        
+        ### 1. **使用** `**ProcessBuilder**` **执行** `**javac**` **编译命令**
+        
+        `ProcessBuilder` 是 Java 提供的一种启动和管理外部进程的工具类。通过它,你可以设置命令参数,并捕获命令执行的输出和错误信息。
+        
+        ### 示例代码:
+        
+        ```Java
+        import java.io.*;
+        import java.util.*;
+        
+        public class JavacCompiler {
+            public static void main(String[] args) {
+                try {
+                    // 指定 javac 命令路径,假设 javac 已经在系统的 PATH 中
+                    List command = new ArrayList<>();
+                    command.add("javac");  // 运行 javac 编译器
+                    command.add("HelloWorld.java");  // 要编译的 Java 文件
+        
+                    // 创建 ProcessBuilder 并执行命令
+                    ProcessBuilder processBuilder = new ProcessBuilder(command);
+                    processBuilder.redirectErrorStream(true); // 合并标准输出和错误输出
+        
+                    // 启动进程并执行 javac
+                    Process process = processBuilder.start();
+        
+                    // 获取编译过程的输出
+                    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        System.out.println(line); // 输出编译过程中的信息
+                    }
+        
+                    // 等待编译完成
+                    int exitCode = process.waitFor();
+                    if (exitCode == 0) {
+                        System.out.println("Compilation successful.");
+                    } else {
+                        System.out.println("Compilation failed.");
+                    }
+        
+                } catch (IOException | InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        
+        ```
+        
+        ### 解释:
+        
+        - `**ProcessBuilder**`:创建一个外部进程来执行 `javac` 命令。
+        - `**command.add("javac")**`:指定要执行的命令,即 `javac`。
+        - `**command.add("HelloWorld.java")**`:指定要编译的 Java 文件。
+        - `**process.waitFor()**`:等待外部进程执行完毕(即 `javac` 编译完 Java 文件)。
+        - `**process.getInputStream()**`:获取编译过程中的输出信息(如错误信息或警告)。
+        
+        如果编译成功,程序会输出 `Compilation successful`,否则会输出 `Compilation failed`。
+        
+        ### 2. **使用** `**Runtime.getRuntime().exec()**` **执行** `**javac**` **编译命令**
+        
+        `Runtime.getRuntime().exec()` 方法也可以用来启动外部进程,执行 `javac` 编译命令。
+        
+        ### 示例代码:
+        
+        ```Java
+        import java.io.*;
+        
+        public class JavacCompiler {
+            public static void main(String[] args) {
+                try {
+                    // 执行 javac 编译命令
+                    String command = "javac HelloWorld.java";
+                    Process process = Runtime.getRuntime().exec(command);
+        
+                    // 获取编译过程的输出
+                    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        System.out.println(line); // 输出编译过程中的信息
+                    }
+        
+                    // 等待编译完成
+                    int exitCode = process.waitFor();
+                    if (exitCode == 0) {
+                        System.out.println("Compilation successful.");
+                    } else {
+                        System.out.println("Compilation failed.");
+                    }
+        
+                } catch (IOException | InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        
+        ```
+        
+        ### 解释:
+        
+        - `**Runtime.getRuntime().exec(command)**`:执行命令 `javac HelloWorld.java` 启动编译进程。
+        - `**process.waitFor()**`:等待 `javac` 进程执行完毕。
+        - `**process.getInputStream()**`:获取编译的输出,便于显示或调试。
+        
+        ### 3. **处理编译过程中出现的错误**
+        
+        `javac` 编译器在编译时,可能会输出错误或警告信息。你可以通过读取进程的错误流 (`process.getErrorStream()`) 来捕获并处理这些信息。
+        
+        ### 修改后的代码(捕获错误流):
+        
+        ```Java
+        import java.io.*;
+        import java.util.*;
+        
+        public class JavacCompiler {
+            public static void main(String[] args) {
+                try {
+                    // 指定 javac 命令路径,假设 javac 已经在系统的 PATH 中
+                    List command = new ArrayList<>();
+                    command.add("javac");
+                    command.add("HelloWorld.java");
+        
+                    ProcessBuilder processBuilder = new ProcessBuilder(command);
+                    processBuilder.redirectErrorStream(true); // 合并标准输出和错误输出
+        
+                    Process process = processBuilder.start();
+        
+                    // 获取编译过程的输出和错误
+                    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        System.out.println(line); // 输出编译过程中的信息或错误
+                    }
+        
+                    int exitCode = process.waitFor();
+                    if (exitCode == 0) {
+                        System.out.println("Compilation successful.");
+                    } else {
+                        System.out.println("Compilation failed.");
+                    }
+        
+                } catch (IOException | InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        ```
+        
+        ### 4. **指定多个文件或目录进行编译**
+        
+        如果你想编译多个 Java 文件或者整个目录中的所有 Java 文件,可以这样做:
+        
+        ```Java
+        import java.io.*;
+        public class JavacCompiler {
+            public static void main(String[] args) {
+                try {
+                    // 编译当前目录下的所有 Java 文件
+                    String command = "javac *.java";
+                    Process process = Runtime.getRuntime().exec(command);
+        
+                    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        System.out.println(line);  // 输出编译过程中的信息
+                    }
+        
+                    int exitCode = process.waitFor();
+                    if (exitCode == 0) {
+                        System.out.println("Compilation successful.");
+                    } else {
+                        System.out.println("Compilation failed.");
+                    }
+        
+                } catch (IOException | InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        ```
+        
+        ### 5. **编译指定的源文件和类路径**
+        
+        如果你有外部的依赖或者类路径需要指定,可以在 `javac` 命令中传递 `**-cp**` 或 `**-classpath**` 参数。
+        
+        例如,假设需要编译 `HelloWorld.java` 并指定类路径:
+        
+        ```Java
+        String command = "javac -cp /path/to/libs/* HelloWorld.java";
+        ```
+        
+        ### 总结:
+        
+        1. `**ProcessBuilder**` 和 `**Runtime.getRuntime().exec()**` 是在 Java 程序中调用外部命令(如 `javac` 编译器)的常用方法。
+        2. `**javac**` **编译器** 会将 Java 源代码文件(`.java`)编译成字节码文件(`.class`)。
+        3. 可以通过 `**ProcessBuilder**` 或 `**exec()**` 启动一个外部进程来执行 `javac` 命令,并通过输入流和输出流处理编译过程中的信息。
+        
+        这种方式对于 Java 程序动态编译其他 Java 文件非常有效,尤其是在某些工具或者自动化编译系统中。
\ No newline at end of file
diff --git a/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/1_Java_Web开发基础.pptx b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/1_Java_Web开发基础.pptx
new file mode 100644
index 0000000..51b2f88
Binary files /dev/null and b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/1_Java_Web开发基础.pptx differ
diff --git a/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image103.png b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image103.png
new file mode 100644
index 0000000..b13a7c8
Binary files /dev/null and b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image103.png differ
diff --git a/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image114.png b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image114.png
new file mode 100644
index 0000000..ccd34e8
Binary files /dev/null and b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image114.png differ
diff --git a/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image27.png b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image27.png
new file mode 100644
index 0000000..5501c54
Binary files /dev/null and b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image27.png differ
diff --git a/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image28.png b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image28.png
new file mode 100644
index 0000000..523c4fe
Binary files /dev/null and b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image28.png differ
diff --git a/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image36.png b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image36.png
new file mode 100644
index 0000000..75b70c8
Binary files /dev/null and b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image36.png differ
diff --git a/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image46.png b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image46.png
new file mode 100644
index 0000000..a17e497
Binary files /dev/null and b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image46.png differ
diff --git a/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image55.png b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image55.png
new file mode 100644
index 0000000..98983bb
Binary files /dev/null and b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image55.png differ
diff --git a/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image63.png b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image63.png
new file mode 100644
index 0000000..8957ece
Binary files /dev/null and b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image63.png differ
diff --git a/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image73.png b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image73.png
new file mode 100644
index 0000000..6df7661
Binary files /dev/null and b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image73.png differ
diff --git a/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image83.png b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image83.png
new file mode 100644
index 0000000..a7c2ee5
Binary files /dev/null and b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image83.png differ
diff --git a/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image93.png b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image93.png
new file mode 100644
index 0000000..b6b3b58
Binary files /dev/null and b/posts/blog/编程技术/java/JavaWeb/Java Web 开发基础/image93.png differ
diff --git a/posts/blog/编程技术/java/netty/netty笔记.md b/posts/blog/编程技术/java/netty/netty笔记.md
new file mode 100644
index 0000000..182fa51
--- /dev/null
+++ b/posts/blog/编程技术/java/netty/netty笔记.md
@@ -0,0 +1,282 @@
+---
+title: netty笔记
+date: 2024-11-15
+tags: [netty]
+---
+
+
+## 概述
+
+- 基于nio设计的网络框架
+- 异步、事件驱动型框架
+
+  
+
+## 组件
+
+### EventLoop
+
+- 事件循环对象
+- 本质是一个单线程执行器(包含一个Selector), 里面有 run 方法处理 Channel 上源源不断的 io 事件
+- 继承关系
+    - 继承线程池 `j.u.c.ScheduledExecutorService`
+    - 继承 netty 自己的 OrderedEventExecutor(`EventExecutor`)
+        - 提供了boolean `inEventLoop`(Thread thread) 方法,判断线程是否属于此EventLoop
+        - 提供了 parent 方法来看看自己属于哪个 EventLoopGroup
+
+### EventLoopGroup
+
+- 事件循环组,构造方法可指定`EventLoop`的个数
+- EventLoopGroup 是一组 `EventLoop`,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop,后续这个 Channel 上的 io 事件都由此 EventLoop 来处理(保证了 io 事件处理时的线程安全)
+- 继承自 netty 自己的 EventExecutorGroup
+    - 实现了 Iterable 接口提供遍历 EventLoop 的能力
+    - 另有 next 方法获取集合中下一个 EventLoop
+
+**优雅关闭**
+
+- 优雅关闭 `shutdownGracefully` 方法。该方法会首先切换 `EventLoopGroup` 到关闭状态从而拒绝新的任务的加入,然后在任务队列的任务都处理完成后,停止线程的运行。从而确保整体应用是在正常有序的状态下退出的
+
+**在netty中的使用**
+
+- Bootstrap的group方法中添加,可以添加多个组
+- 在`nioSocketChannel.pipeline().addLast` 方法中添加EventLoopGroup
+    - **handler 执行中如何换组?**
+        - 关键代码
+          
+            `io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead()`
+            
+            ```Java
+            // 下一个 handler 的事件循环是否与当前的事件循环是同一个线程
+                EventExecutor executor = next.executor();
+                
+                // 是,直接调用
+                if (executor.inEventLoop()) {
+                    next.invokeChannelRead(m);
+                } 
+                // 不是,将要执行的代码作为任务提交给下一个事件循环处理(换人)
+                else {
+                    executor.execute(new Runnable() {
+                        @Override
+                        public void run() {
+                            next.invokeChannelRead(m);
+                        }
+                    });
+                }
+            ```
+            
+
+- 处理普通任务,处理耗时较长的任务
+  
+    ```Java
+    NioEventLoopGroup nioWorkers = new NioEventLoopGroup(2);
+    
+    log.debug("server start...");
+    Thread.sleep(2000);
+    nioWorkers.execute(()->{
+        log.debug("normal task...");
+    });
+    ```
+    
+- 处理定时任务
+  
+    ```Java
+    NioEventLoopGroup nioWorkers = new NioEventLoopGroup(2);
+    
+    log.debug("server start...");
+    Thread.sleep(2000);
+    nioWorkers.scheduleAtFixedRate(() -> {
+        log.debug("running...");
+    }, 0, 1, TimeUnit.SECONDS);
+    ```
+    
+
+### Channel
+
+- 与nio中的channel类似
+- 常用方法
+    - pipeline方法添加处理器
+    - write方法将数据写入
+    - writeAndFlush方法将数据写入并刷出
+    - close异步关闭channel
+    - closeFuture处理关闭channel后的操作,返回ChannelFuture对象
+        - sync同步等待channel关闭
+        - addListener方法是异步等待channel关闭
+
+### ChannelFuture
+
+- 调用BootStrap的`connect` 方法时返回,由于连接是异步操作,可以利用该实例等待连接
+- 继承自juc的Future接口,并进行了增强,包含了`Channel`对象
+
+### Future & Promise
+
+- 常用于异步操作
+- 首先要说明 netty 中的 Future 与 jdk 中的 Future 同名,但是是两个接口,netty 的 Future 继承自 jdk 的 Future,而 Promise 又对 netty Future 进行了扩展
+  
+    - jdk Future 只能同步等待任务结束(或成功、或失败)才能得到结果
+    - netty Future 可以同步等待任务结束得到结果,也可以异步方式得到结果,但都是要等任务结束
+    - netty Promise 不仅有 netty Future 的功能,而且脱离了任务独立存在,只作为两个线程间传递结果的容器
+    
+    |   |   |   |   |
+    |---|---|---|---|
+    |**功能/名称**|**jdk Future**|**netty Future**|**Promise**|
+    |cancel|取消任务|-|-|
+    |isCanceled|任务是否取消|-|-|
+    |isDone|任务是否完成,不能区分成功失败|-|-|
+    |get|获取任务结果,阻塞等待|-|-|
+    |getNow|-|获取任务结果,非阻塞,还未产生结果时返回 null|-|
+    |await|-|等待任务结束,如果任务失败,不会抛异常,而是通过 isSuccess 判断|-|
+    |sync|-|等待任务结束,如果任务失败,抛出异常|-|
+    |isSuccess|-|判断任务是否成功|-|
+    |cause|-|获取失败信息,非阻塞,如果没有失败,返回null|-|
+    |addLinstener|-|添加回调,异步接收结果|-|
+    |setSuccess|-|-|设置成功结果|
+    |setFailure|-|-|设置失败结果|
+    
+
+### **Handler & Pipeline**
+
+- ChannelHandler 用来处理 Channel 上的各种事件,分为入站、出站两种,所有 ChannelHandler 被连成一串,就是 Pipeline
+    - 入站处理器通常是 ChannelInboundHandlerAdapter 的子类,主要用来读取客户端数据,写回结果
+    - 出站处理器通常是 ChannelOutboundHandlerAdapter 的子类,主要对写回结果进行加工
+
+  
+
+### **ByteBuf**
+
+- 对nio的ByteBuffer封装增强
+- 创建
+  
+    ```Java
+    ByteBufAllocator.DEFAULT.buffer(10);
+    channelHandlerContext.alloc().buffer();
+    ```
+    
+- 直接内存、堆内存
+  
+    可以使用下面的代码来创建池化基于堆的 ByteBuf
+    
+    ```Plain
+    ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(10);
+    ```
+    
+    也可以使用下面的代码来创建池化基于直接内存的 ByteBuf
+    
+    ```Plain
+    ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10);
+    ```
+    
+    - 直接内存创建和销毁的代价昂贵,但读写性能高(少一次内存复制),适合配合池化功能一起用
+    - 直接内存对 GC 压力小,因为这部分内存不受 JVM 垃圾回收的管理,但也要注意及时主动释放
+- 池化、非池化
+  
+    池化的最大意义在于可以重用 ByteBuf,优点有
+    
+    - 没有池化,则每次都得创建新的 ByteBuf 实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加 GC 压力
+    - 有了池化,则可以重用池中 ByteBuf 实例,并且采用了与 jemalloc 类似的内存分配算法提升分配效率
+    - 高并发时,池化功能更节约内存,减少内存溢出的可能
+    
+    池化功能是否开启,可以通过下面的系统环境变量来设置
+    
+    ```Plain
+    -Dio.netty.allocator.type={unpooled|pooled}
+    ```
+    
+    - 4.1 以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现
+    - 4.1 之前,池化功能还不成熟,默认是非池化实现
+
+- 组成
+  
+    ![image 32.png](netty笔记/image32.png)
+    
+- 扩容
+  
+    再写入一个 int 整数时,容量不够了(初始容量是 10),这时会引发扩容
+    
+    ```Plain
+    buffer.writeInt(6);
+    log(buffer);
+    ```
+    
+    扩容规则是
+    
+    - 如何写入后数据大小未超过 512,则选择下一个 16 的整数倍,例如写入后大小为 12 ,则扩容后 capacity 是 16
+    - 如果写入后数据大小超过 512,则选择下一个 2^n,例如写入后大小为 513,则扩容后 capacity 是 2^10=1024(2^9=512 已经不够了)
+    - 扩容不能超过 max capacity 会报错
+- **retain & release**
+  
+    由于 Netty 中有堆外内存的 ByteBuf 实现,堆外内存最好是手动来释放,而不是等 GC 垃圾回收。
+    
+    - UnpooledHeapByteBuf 使用的是 JVM 内存,只需等 GC 回收内存即可
+    - UnpooledDirectByteBuf 使用的就是直接内存了,需要特殊的方法来回收内存
+    - PooledByteBuf 和它的子类使用了池化机制,需要更复杂的规则来回收内存
+    
+    > 回收内存的源码实现,请关注下面方法的不同实现
+    > 
+    > `protected abstract void deallocate()`
+    
+    Netty 这里采用了引用计数法来控制回收内存,每个 ByteBuf 都实现了 ReferenceCounted 接口
+    
+    - 每个 ByteBuf 对象的初始计数为 1
+    - 调用 release 方法计数减 1,如果计数为 0,ByteBuf 内存被回收
+    - 调用 retain 方法计数加 1,表示调用者没用完之前,其它 handler 即使调用了 release 也不会造成回收
+    - 当计数为 0 时,底层内存会被回收,这时即使 ByteBuf 对象还在,其各个方法均无法正常使用
+    
+    谁来负责 release 呢?
+    
+    不是我们想象的(一般情况下)
+    
+    ```Plain
+    ByteBuf buf = ...
+    try {
+        ...
+    } finally {
+        buf.release();
+    }
+    ```
+    
+    请思考,因为 pipeline 的存在,一般需要将 ByteBuf 传递给下一个 ChannelHandler,如果在 finally 中 release 了,就失去了传递性(当然,如果在这个 ChannelHandler 内这个 ByteBuf 已完成了它的使命,那么便无须再传递)
+    
+    基本规则是,**谁是最后使用者,谁负责 release**,详细分析如下
+    
+    - 起点,对于 NIO 实现来讲,在 io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe\#read 方法中首次创建 ByteBuf 放入 pipeline(line 163 pipeline.fireChannelRead(byteBuf))
+    - 入站 ByteBuf 处理原则
+        - 对原始 ByteBuf 不做处理,调用 ctx.fireChannelRead(msg) 向后传递,这时无须 release
+        - 将原始 ByteBuf 转换为其它类型的 Java 对象,这时 ByteBuf 就没用了,必须 release
+        - 如果不调用 ctx.fireChannelRead(msg) 向后传递,那么也必须 release
+        - 注意各种异常,如果 ByteBuf 没有成功传递到下一个 ChannelHandler,必须 release
+        - 假设消息一直向后传,那么 TailContext 会负责释放未处理消息(原始的 ByteBuf)
+    - 出站 ByteBuf 处理原则
+        - 出站消息最终都会转为 ByteBuf 输出,一直向前传,由 HeadContext flush 后 release
+    - 异常处理原则
+        - 有时候不清楚 ByteBuf 被引用了多少次,但又必须彻底释放,可以循环调用 release 直到返回 true
+
+  
+
+  
+
+### LTC解码器0
+
+- 起始位
+- 长度
+- 长度后,跳过N个字节
+- 去掉总长度数的前N个字节
+
+  
+
+```Java
+不同 Channel 的 AttributeMap 是相互独立的,每个 Channel 都有自己的 AttributeMap 实例,因此不同 Channel 之间对 AttributeKey 的操作不会产生线程安全问题。
+让我们通过一个示例来说明:
+这是安全的,因为:
+独立的 AttributeMap:
+每个 Channel 实例都有自己独立的 AttributeMap
+不同 Channel 的属性操作互不影响
+2. Channel 线程安全性:
+Channel 的操作都是由其关联的 EventLoop 线程处理
+即使多个线程同时操作不同的 Channel,也不会产生线程安全问题
+EventLoop 线程模型:
+每个 Channel 都绑定到特定的 EventLoop
+EventLoop 确保 Channel 的操作都在同一个线程中执行
+但是需要注意的是:
+```
+
+![image 1 17.png](netty笔记/image117.png)
\ No newline at end of file
diff --git a/posts/blog/编程技术/java/netty/netty笔记/image117.png b/posts/blog/编程技术/java/netty/netty笔记/image117.png
new file mode 100644
index 0000000..1bc53c9
Binary files /dev/null and b/posts/blog/编程技术/java/netty/netty笔记/image117.png differ
diff --git a/posts/blog/编程技术/java/netty/netty笔记/image32.png b/posts/blog/编程技术/java/netty/netty笔记/image32.png
new file mode 100644
index 0000000..3bdfb4d
Binary files /dev/null and b/posts/blog/编程技术/java/netty/netty笔记/image32.png differ
diff --git a/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记.md b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记.md
new file mode 100644
index 0000000..9044da8
--- /dev/null
+++ b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记.md
@@ -0,0 +1,55 @@
+---
+title: JAR包笔记
+date: 2024-09-24
+tags: [JAR包]
+---
+
+## 使用JAR包
+
+- 打开项目结构
+  
+    ![image 31.png](JAR包笔记/image31.png)
+    
+- 导入JAR包
+  
+    ![image 1 16.png](JAR包笔记/image116.png)
+    
+- 可以选择本地文件夹上的任何位置的JAR包,但为了规范,==在项目根目录下创建lib文件夹,将JAR放入==,最后**通过上面的方法将包引入**
+
+![image 2 9.png](JAR包笔记/image29.png)
+
+## 普通项目打成JAR包
+
+- 打开项目结构
+    ![image 3 8.png](JAR包笔记/image38.png)
+    
+- 选择主类,没有主类直接确定(记得点应用)
+    ![image 4 8.png](JAR包笔记/image48.png)
+    
+- 在构建中构建
+
+![image 5 7.png](JAR包笔记/image57.png)
+
+- 选择构建,就完成了
+  
+    ![image 6 5.png](JAR包笔记/image65.png)
+    
+
+  
+
+## 读取配置文件
+
+- 读取jar包内的配置文件,jar包中的配置文件不能修改
+    ```Java
+      try (InputStream input = Client.class.getClassLoader().getResourceAsStream("config.properties")) {
+                if (input == null) {
+                    println("找不到配置文件!");
+                    return;
+                }
+                properties.load(input);
+               
+            } catch (IOException e) {
+                println("找不到配置文件!");
+                return;
+            }
+    ```
\ No newline at end of file
diff --git a/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image116.png b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image116.png
new file mode 100644
index 0000000..1c5c1aa
Binary files /dev/null and b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image116.png differ
diff --git a/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image29.png b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image29.png
new file mode 100644
index 0000000..6232241
Binary files /dev/null and b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image29.png differ
diff --git a/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image31.png b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image31.png
new file mode 100644
index 0000000..d31bd99
Binary files /dev/null and b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image31.png differ
diff --git a/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image38.png b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image38.png
new file mode 100644
index 0000000..2f80974
Binary files /dev/null and b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image38.png differ
diff --git a/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image48.png b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image48.png
new file mode 100644
index 0000000..193c179
Binary files /dev/null and b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image48.png differ
diff --git a/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image57.png b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image57.png
new file mode 100644
index 0000000..64db2e2
Binary files /dev/null and b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image57.png differ
diff --git a/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image65.png b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image65.png
new file mode 100644
index 0000000..b1acf89
Binary files /dev/null and b/posts/blog/编程技术/java/小知识点/JAR包/JAR包笔记/image65.png differ
diff --git a/posts/blog/编程技术/java/小知识点/jwt/jwt笔记.md b/posts/blog/编程技术/java/小知识点/jwt/jwt笔记.md
new file mode 100644
index 0000000..6e3701b
--- /dev/null
+++ b/posts/blog/编程技术/java/小知识点/jwt/jwt笔记.md
@@ -0,0 +1,185 @@
+---
+title: jwt笔记
+date: 2025-05-09
+tags: [jwt]
+---
+
+
+依赖
+
+```Shell
+
+            com.auth0
+            java-jwt
+            4.3.0
+        
+```
+
+使用步骤:
+
+- 编写jwt工具类:解析jwt、生成jwt
+  
+    ```Java
+    @Data
+    @Component
+    //读取配置类,依赖注入使用
+    @ConfigurationProperties(prefix = "jwt")
+    public class JWTUtils {
+        // 颁发者
+        private  String issuer;
+        // 密钥
+        private  String secret;
+        // 到期时间
+        private  int expiration;
+        //单位
+        private int time;
+    
+        /**
+         * 生成token 传入map
+         */
+    
+        public  String getToken(Map map) {
+    
+            // 到期时间
+            Calendar instance = Calendar.getInstance();
+            //日
+            instance.add(time, expiration);
+    
+    
+            //创建jwt builder
+            JWTCreator.Builder builder = JWT.create();
+    
+            // payload
+            map.forEach(builder::withClaim);
+    
+            return builder.withIssuer(issuer)       //签发者
+                    .withIssuedAt(new Date())               //签发时间
+                    .withExpiresAt(instance.getTime())      //到期时间
+                    .sign(Algorithm.HMAC256(secret));
+        }
+    
+        /**
+         * 验证token  合法性
+         */
+        public  boolean verify(String token) {
+            try {
+                JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
+            }catch (JWTVerificationException e){
+                return false;
+            }
+            return true;
+        }
+    
+        /**
+         * 获取token信息方法
+         */
+        public DecodedJWT getTokenInfo(String token) {
+            try {
+                return JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
+            } catch (SignatureVerificationException e) {
+                throw new TokenException("Token 签名验证错误");
+            } catch (TokenExpiredException e) {
+                throw new TokenException("Token 已过期");
+            } catch (AlgorithmMismatchException e) {
+                throw new TokenException("算法不匹配");
+            } catch (InvalidClaimException e) {
+                throw new TokenException("无效的声明");
+            } catch (Exception e) {
+                throw new TokenException("未知错误");
+            }
+        }
+    }
+    ```
+    
+- 结合过滤器,编写过滤逻辑
+  
+    ```Java
+    //验证token  接口作用,每个请求只会执行一次 过滤操作
+    @Component
+    public class CheckTokenFilter extends OncePerRequestFilter {
+        //url为空,则赋值对象
+        @Autowired
+        private FilterConfig filterConfig;
+        @Autowired
+        private JWTUtils jwtUtils;
+    
+    
+    //    true 放行    false不放行要检查
+        @Override
+        protected boolean shouldNotFilter(HttpServletRequest request) {
+            String uri = request.getRequestURI();
+            for (String s : filterConfig.getUrls()) {
+                if(uri.equals(s)||uri.startsWith(s)){
+                    return true;
+                }
+            }
+            return false;
+        }
+    
+    
+        @Override
+        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+            try {
+                checkToken(request); //有问题抛异常
+            }catch (TokenException tokenException){
+                // 构造返回内容(可使用你自定义的 Result),写回响应
+                response.setContentType("application/json;charset=UTF-8");
+                String json = new ObjectMapper().writeValueAsString(Result.error(401, tokenException.getMessage()));
+                response.getWriter().write(json);
+                return; // 不再继续往下执行
+            }
+            filterChain.doFilter(request,response);
+        }
+    
+        protected void checkToken(HttpServletRequest request){
+            String token = request.getHeader("token");
+            if(StringUtils.isEmpty(token)){
+                throw new TokenException("请传递token");
+            }
+            //验证token
+            if(!jwtUtils.verify(token)){
+                throw new TokenException("token无效");
+            }
+    
+            //解析token
+            DecodedJWT tokenInfo = jwtUtils.getTokenInfo(token);
+            String userId = tokenInfo.getClaim("accountId").asString();
+            request.setAttribute("accountId", userId); // 存入请求属性
+        }
+    }
+    ```
+    
+- 注册到过滤链
+  
+    ```Java
+    @Configuration
+    public class FilterConfig {
+        @Bean
+        public FilterRegistrationBean checkTokenFilterRegistration(CheckTokenFilter filter) {
+            FilterRegistrationBean registration = new FilterRegistrationBean<>();
+            registration.setFilter(filter);
+            registration.addUrlPatterns("/*"); // 配置拦截路径
+            registration.setOrder(1); // 优先级,数字越小越优先
+            return registration;
+        }
+    }
+    ```
+    
+
+这是普通用法,结合SpringSecurity做法
+
+1、注册到SpringSecurity的过滤链中
+
+- code
+  
+    ```Java
+    @Bean
+    public SecurityFilterChain securityFilterChain(HttpSecurity http, CheckTokenFilter checkTokenFilter) throws Exception {
+        return http
+            .addFilterBefore(checkTokenFilter, UsernamePasswordAuthenticationFilter.class)
+            .build();
+    }
+    ```
+    
+
+2、过滤逻辑,对token验证通过的用户除了放行,还要告知SpringSecurity通过验证
\ No newline at end of file
diff --git a/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记.md b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记.md
new file mode 100644
index 0000000..098fc73
--- /dev/null
+++ b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记.md
@@ -0,0 +1,979 @@
+---
+title: MyBatis笔记
+date: 2025-02-14
+tags: [MyBatis]
+---
+
+
+## 简介
+
+- 高效的持久层框架
+
+## XML语言
+
+- 语法与html类似
+  
+    - 转移字符
+    
+    ![image 34.png](MyBatis笔记/image34.png)
+    
+- java读取xml文件
+  
+    ```Java
+    // 创建DocumentBuilderFactory对象
+    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+    // 创建DocumentBuilder对象
+    try {
+        DocumentBuilder builder = factory.newDocumentBuilder();
+        Document d = builder.parse("file:mappers/test.xml");
+        // 每一个标签都作为一个节点
+        NodeList nodeList = d.getElementsByTagName("test");  // 可能有很多个名字为test的标签
+        Node rootNode = nodeList.item(0); // 获取首个
+    
+        NodeList childNodes = rootNode.getChildNodes(); // 一个节点下可能会有很多个节点,比如根节点下就囊括了所有的节点
+        //节点可以是一个带有内容的标签(它内部就还有子节点),也可以是一段文本内容
+    
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            Node child = childNodes.item(i);
+            if(child.getNodeType() == Node.ELEMENT_NODE)  //过滤换行符之类的内容,因为它们都被认为是一个文本节点
+                System.out.println(child.getNodeName() + ":" +child.getFirstChild().getNodeValue());
+            // 输出节点名称,也就是标签名称,以及标签内部的文本(内部的内容都是子节点,所以要获取内部的节点)
+        }
+    } catch (Exception e) {
+        e.printStackTrace();
+    }
+    ```
+    
+
+**配置与使用**
+
+> [!important] 配置连接信息.xml、实体类对象、sql语句.xml
+
+- 导入mybatis依赖、配置xml文件,连接信息
+  
+    ```XML
+      
+          org.mybatis
+          mybatis
+          3.5.6
+        
+        
+          mysql
+          mysql-connector-java
+          8.0.33
+        
+    ```
+    
+    ```XML
+    
+    
+    
+    
+        
+            
+    
+                
+    
+                
+                    
+                    
+                    
+                    
+                
+            
+        
+    
+    ```
+    
+- **获取**`**SqlSeesion**`,通过工厂类获得,==每个SqlSeesion相当于一次会话(请求),==**相当于 JDBC 中的** `**Connection**` **对象,**封装了多个接口方法
+  
+    ```Java
+     SqlSessionFactory sqlSessionFactory= new SqlSessionFactoryBuilder().build(
+         new FileInputStream("E:\\IDEA\\Files\\mybatis_student\\src\\main\\resources\\config.xml")
+     );
+     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)){
+    }
+    ```
+    
+    ![image 1 19.png](MyBatis笔记/image119.png)
+    
+
+**创建mapper**
+
+- 创建实体类、以及对应的Mapper.xml文件,**id相当于方法名**
+  
+    ```XML
+    
+    
+            
+    
+        
+    
+    ```
+    
+- 在配置xml文件中,导入mapper.xml
+  
+    ```XML
+    
+        
+            
+        
+    ```
+    
+- 根据类型选方法,调用具体方法并传入mapper.xml写好的方法id
+  
+    ```Java
+    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)){
+         List selectAll = sqlSession.selectList("selectAll");
+         selectAll.forEach(System.out::println);
+     }
+    ```
+    
+
+> [!important] 这样表多了时不方便,因为id容易重复,还是通过接口指定更通用
+
+![image 2 11.png](MyBatis笔记/image211.png)
+
+## 基本使用
+
+### 查询
+
+- **结果集**
+    - 结果集简写,在配置xml文件中添加备注
+      
+        ```XML
+        
+            
+                
+            
+        ```
+        
+    - 数据库字段名a_xx,自动转驼峰aXX,配置xml文件
+      
+        ```XML
+        
+           
+        
+        ```
+        
+    - 结果集的属性名需要一致
+    - 数据库属性名与实体类属性不一致,mapper.xml配置使其对应
+      
+        ```XML
+        
+          
+          
+        
+        ```
+    
+- **参数**可以不写,会自动判断
+    - 参数占位符`#{}` ,可以防止sql注入,将参数的特殊字符进行转义
+    - **多参数传入**,==传入map对象==,<属性,值>类型是String;或者传入==封装对象==
+      
+        ```Java
+         try (SqlSession sqlSession = SqlSessionUtil.getSqlSession(true)){
+            Mapword = sqlSession.selectOne("selectTwo",
+                        Map.of("id","1","word","pole")    );
+                    System.out.println(word);
+          }
+        ```
+    
+- selectMap的使用,设置key,返回值
+  
+    ```Java
+    try (SqlSession sqlSession = SqlSessionUtil.getSqlSession(true)) {
+        Map word = sqlSession.selectMap("selectAll", "id");
+        System.out.println(word);
+     }
+    ```
+    
+- 设置map为结果集,结果为<属性,值>
+  
+    ```XML
+    
+    ```
+    
+    ```Java
+    try (SqlSession sqlSession = SqlSessionUtil.getSqlSession(true)){
+                Mapword = sqlSession.selectOne("selectMapById",69);
+                System.out.println(word);
+            }
+    ```
+    
+
+### 指定构造方法
+
+- 使用Java读取xml文件,结果集是通过==**无参改造反射出的对象**==**(没有无参构造会调用列数对应的构造器,顺序必须一致)**
+
+![image 3 10.png](MyBatis笔记/image310.png)
+
+- 使用带参构造函数,mybatis也能解析,当如果有多个带参数的构造函数将会报错,mybatis不能解析;
+    - 解决方法,添加一个无参构造或者全参改造
+    - 通过xml配置对应构造方法,这样可以在==构造方法中对数据进行加工==
+      
+        ```XML
+        
+        	
+               
+                
+          
+        
+        ```
+        
+
+### 接口绑定
+
+- 编写接口与xml绑定
+  
+    ```XML
+    
+    
+    ```
+    
+- 在接口中编写抽象方法,**方法名字与xml中的id相同**,通过SqlSession进行使用
+  
+    ```Java
+    try (SqlSession sqlSession = SqlSessionUtil.getSqlSession(true)) {
+         WordMapper wordMapper = sqlSession.getMapper(WordMapper.class);
+         System.out.println(wordMapper.selectAll());
+    }
+    ```
+    
+- 原理就是动态代理,对接口进行实现了
+- **多参数问题**
+  
+    - 传参为Map进行解决 
+      
+        ```Java
+        Word selectTwo(Map map);
+        ```
+        
+    - 方法设置多个参数 _`Word selectTwo(int id,String word);`_需要修改xml文件
+      
+        ```XML
+        
+        ```
+        
+    - 在方法中设置`**@Param,**`不需要修改xml
+      
+        ```Java
+        Word selectTwo(@Param("id") int id, @Param("word") String word);
+        ```
+        
+
+### 复杂查询
+
+**一对一**
+
+- 外键类作为主键类的属性
+  
+    ```Java
+    class A{
+    	Adetail a;
+    }
+    ```
+    
+- 处理结果集,`**association**`
+    - 配置resultMap的映射关系,每个属性都要配置, `<``**association**` `property="testDetail">` 是关键
+      
+        ```XML
+        
+          
+          
+          
+              
+              
+              
+          
+        
+        ```
+        
+    - ==通过2次查询将结果拼接==,编写好2个查询语句,`select`指定外键的sql,以及`column`外键
+      
+        ```XML
+        
+               
+               
+               
+        
+        ```
+        
+
+> [!important] 在配置类中使用`` 查看详细日志
+
+**一对多**
+
+- 在一(主类)中填加,集合属性
+- 结果集,`**collection**`
+    - 设置结果集 `****`
+      
+        - `**ofType**` **为集合元素类型**
+        
+        ```XML
+         
+               
+               
+               
+                   
+                   
+               
+           
+        ```
+        
+        - 数据表样式 —> 对应一个对象
+          
+            ![image 4 10.png](MyBatis笔记/image410.png)
+        
+    - 通过2次查询,设置`select` 语句,指定`column`属性
+      
+        ```XML
+        
+        ```
+        
+        - 子查询,必须要是条件查询
+          
+            ```JavaScript
+            
+            ```
+            
+
+**DML操作(数据操作)**
+
+**insert**
+
+- ==接口的返回值一定要是数字(int),可以自己传对象,会自动判断,多参数使用@Param备注==
+  
+    ```XML
+     
+            insert into test(word) values (#{word})
+    
+    ```
+    
+- 插入返回主键,`useGeneratedKeys`、`keyColumn`、`keyProperty`
+  
+    ```XML
+    
+         insert into test(word) values (#{word})
+    
+    ```
+    
+
+**update**
+
+- 条件和上面一样
+  
+    ```XML
+    
+       update test set word = #{word} where id = #{id};
+    
+    ```
+    
+
+delete
+
+- 条件和上面一样
+  
+    ```XML
+    
+        delete from test where id = #{id};
+    
+    ```
+    
+
+> [!important] 增删改查
+> 
+> - 增删改,返回值为数字
+> - 查询,返回值为对象和集合时,都要设置返回类型`resultType` ,复杂的使用`resultMap`
+> - 一个传参可以直接使用,多个参数使用@Param标注;传入集合、数组使用动态sql,不使用默认名也要使用@Param注解
+
+### 事务操作
+
+- 在获取SqlSeesion时,将_`**sqlSessionFactory**`_的==openSession设置为false==,关闭自动提交
+- 操作完数据后,sqlSession调用commit进行提交、rollback进行回滚
+- 如果不提交,会自动回滚事务
+
+### 批处理
+
+- 在获取SqlSeesion时,将_`**sqlSessionFactory**`_的==openSession设置为==  
+    `ExecutorType.`_`BATCH`_,设置为批处理;操作完成后进行提交
+    
+    ```Java
+    TestMapper mapper = session.getMapper(TestMapper.class);
+    for (int i = 1; i <= 5; i++) {
+        mapper.deleteUserById(i);
+    }
+    session.flushStatements();//或者调用commit
+    ```
+    
+    ![image 5 9.png](MyBatis笔记/image59.png)
+    
+    减少通信开销
+    
+
+### 动态SQL
+
+- 提供了条件判断、循环等条件;类似存储过程用法
+- **if**,先写死,再动态,if、test
+  
+    ```XML
+     
+    ```
+    
+- **choose**,多条件,choose、when、test
+  
+    ```XML
+     
+    ```
+    
+- **foreach**,in和批量添加常用,collection填参数类型(array、list、map、set)、直接填对象名称(==参数名需要使用@Param注解,==)
+  
+    ```XML
+     
+    ```
+    
+    ```XML
+    
+        insert into test(id, word) values
+        
+             (#{t.id},#{t.word})
+         
+    
+    ```
+    
+
+### 缓存机制
+
+- 目的是提高获取数据的效率
+  
+    ![image 6 7.png](MyBatis笔记/image67.png)
+    
+
+**一级缓存(本地缓存)**
+
+- 强制使用,不能被关闭
+- 同一`**SqlSession**`(单一会话),相同的查询方法,多次调用返回的是相同的对象,因为是==查询结果放在一级缓存==中,如果数据中途修改,再次查询还是走数据库
+- 但是其他SqlSession(会话)修改,一级缓存不会影响,导致缓存数据是无效(错误)的
+
+> [!important] 如果修改查询结果对象,通过缓存查询的对象会被影响,因为是共用的
+
+**二级缓存(作用域更大)**
+
+- 默认关闭,在mapper.xml中开启`` ,作用域是mapper类
+- 缓存配置
+  
+    ```XML
+    
+    ```
+    
+    `size`表示最大的缓存对象数量,当缓存达到上限时,会根据`eviction`配置的策略进行清理:
+    
+    - `LRU` – 最近最少使用:移除最长时间不被使用的对象。
+    - `FIFO` – 先进先出:按对象进入缓存的顺序来移除它们。
+    - `SOFT` – 软引用:基于垃圾回收器状态和软引用规则移除对象。
+    - `WEAK` – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
+    
+    `flushInterval`用于控制缓存刷新时间,当到达指定时间时会自动清理所有缓存,默认情况下如果不配置此项则==不会进行定时清理==。`readOnly`(只读)属性可以被设置为 true 或 false,只读的缓存会给所有调用者返回相同的缓存对象,且对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
+    
+    > 只读、读写效果和一级缓存一样(作用域更大),默认是读写,此时缓存对象的使用必须要属性序列化接口,这样设置是==为了方便将二级缓存存到外部(redis)==,不用则可设置为true
+    
+    > [!important] 二级缓存是事务性的,默认自动提交(连接关闭自动提交),缓存才会更新
+    
+- select ——> 二级缓存 ——> 一级缓存 ——> 数据库
+- 可以在某个方法设置不使用二级缓存`useCache`,不使用缓存设置`flushCache`
+- 解决多服务器缓存不一致的问题,需要通过将缓存存放在同一缓存数据库中(Redis、Ehcache、Memcahe)
+
+### 注解开发
+
+- 配置文件不导入mapper.xml,导入接口类
+  
+    ```XML
+        
+            
+        
+    ```
+    
+- 在方法上添加注解实现sql语句就行
+
+**增删改查**
+
+- 增加,设置返回值主键
+  
+    ```Java
+    @Options(useGeneratedKeys = true,keyColumn = "id",keyProperty = "id")
+    @Insert("insert into test(word) values (#{word})")
+    int insertTest(Test test);
+    ```
+    
+- 设置结果集,对于结果集配置,注解只支持查询多次拼接结果;还是使用xml方便
+  
+    ```Java
+    @Results({
+                @Result(id = true,property = "id",column = "id"),
+                @Result(property = "word",column = "word")
+        })
+    @Select("select * from test where id = #{id}")
+    Test selectById(int id);
+    ```
+    
+    ```Java
+     @Results({
+                @Result(id = true,property = "id",column = "id"),
+                @Result(property = "word",column = "word"),
+                @Result(property = "testDetail", column = "id", one=@One(select = "selectTestDetail") )
+        })
+       @Select("select * from test where id = #{id}")
+        Test selectById(int id);
+      
+       @Select("select * from test_detail where id = #{id}")
+       TestDetail selectTestDetail(int id);
+    ```
+    
+- 结果集创建对象,指定构造方法,xml实现→ 
+  
+    ```Java
+    @ConstructorArgs({
+                @Arg(id = true,column = "id",javaType = int.class),
+                @Arg(column = "name",javaType = String.class),
+                @Arg(column = "age",javaType = int.class)
+        })
+    @Select("select * from test where id = #{id}")
+    Test selectById(int id);
+    ```
+    
+- 动态SQL
+  
+    ```Java
+    @Select("""
+             
+             """)
+    User selectUserById(int id);
+    ```
+    
+- 开启二级缓存,在接口上就行注解`@CacheNamespace`
+  
+    ```Java
+    @CacheNamespace(readWrite = false)
+    public interface TestMapper {}
+    ```
+    
+
+### 使用类代替配置xml
+
+- 详情
+  
+    ```Java
+     public static Configuration init(){
+            Configuration configuration = new Configuration();
+            DataSource source=new PooledDataSource("com.mysql.cj.jdbc.Driver",
+                    "jdbc:mysql://localhost:3306/word","root","qgs");
+            Environment environment = new Environment("development",new JdbcTransactionFactory(),source);
+            configuration.setEnvironment(environment);
+            configuration.addMappers("com.qgs.mapper");
+            configuration.getTypeAliasRegistry().registerAliases("com.qgs.entity");
+            configuration.setLogImpl(StdOutImpl.class);
+            configuration.setMapUnderscoreToCamelCase(true);
+            return configuration;
+        }
+    //设置到sqlsessionFactory上 
+    sqlSessionFactory=new SqlSessionFactoryBuilder().build(Config.init());
+        
+        
+    ```
+    
+    ```XML
+    
+    
+    
+    
+        
+            
+            
+        
+    
+        
+            
+        
+    
+    
+        
+            
+    
+                
+    
+                
+                    
+                    
+                    
+                    
+                
+            
+        
+    
+        
+            
+    
+    
+        
+    
+    ```
+    
+
+## 高级用法
+
+### 类型处理器
+
+- 作用:自定义类型也可以解析成数据库类型
+  
+    ```XML
+    MyString(自定义) ---> 解析成数据库类型 
+    ```
+    
+- 自定义Type handler,进行类型配置,创建handler类继承`BaseTypeHandler`类,配置注解
+  
+    ```Java
+    @MappedJdbcTypes(JdbcType.VARCHAR)  //这里我们映射的是数据库里面的varchar
+    public class MyTypeHandler extends BaseTypeHandler {
+        //用于配置如何从ResultSet中读取并转换为我们需要的类型
+    @Override
+    public MyString getNullableResult(ResultSet rs, String columnName) throws SQLException {
+        String value = rs.getString(columnName);  //这里提供的是columnName进行查询
+        return new MyString(value);
+    }
+    
+    @Override
+    public MyString getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+        String value = rs.getString(columnIndex);  //同上,这里提供的是columnIndex进行查询
+        return new MyString(value);
+    }
+    
+    @Override
+    public MyString getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+        String value = cs.getString(columnIndex);  //一般用于存储过程之类的操作,同上
+        return new MyString(value);
+    }
+    }
+    ```
+    
+- 注册到配置文件
+  
+    ```XML
+    
+        
+    
+    ```
+    
+- 接着我们需要在Mapper中配置这里编写好的类型处理器:
+  
+    ```Java
+    @Results({
+            @Result(column = "name", property = "name", typeHandler = MyTypeHandler.class)
+    })
+    @Select("select * from user where id = #{id}")
+    User selectUserById(int id);
+    ```
+    
+    ```XML
+    
+    
+        
+    
+    ```
+    
+
+### 动态代理
+
+- 因为它我们不需要编写具体的实现类,有它代理生成代理类(结果类)
+
+**静态代理**
+
+- 定义接口规范,普通类实现接口
+- 代理类实现接口、属性接口,通过调用(属性形式)普通类方法、加上代理方法实现代理,然后接口实现交给代理类
+
+```Java
+interface AService{
+		void selectAll();
+}
+class AServiceImpl implements AService{
+		void selectAll(){
+				//...
+		}
+}
+class AServiceProxy implements AService{
+		private AService aService;
+		void selectAll(){
+			//代理方法
+			//...
+			aService.selectAll();
+		}
+}
+```
+
+> [!important] 通用性差,不可能每个类都定义一个接口;并且不能代理接口
+
+**动态代理**
+
+使用==jdk提供的动态代理==,运行时动态地代理类,**只能代理接口,不能直接对普通类进行代理**
+
+- 具体实现,创建代理类,实现`InvocationHandler` ,在`invoke` 方法中实现具体代理,参数解释
+    - `Object proxy`:代理类,相当于`newProxyInstance` 生成代理对象,常用于==链式调用、递归调用==
+    - `Method method`:代理类的方法类,包括了所有的方法
+    - `Object[] args`:方法类的参数
+- 生成代理类,`newProxyInstance` 方法解释
+  
+    - `**ClassLoader loader**`:类加载器,代理类会使用这个类加载器进行加载,通常类加载器都是一样的(使用接口类加载器)
+    - `**Class[] interfaces**`:接口的类对象,**可以代理多个接口**
+    - `**InvocationHandler h**`:传入我们创建的代理类实例
+    
+    ```Java
+    Proxy.newProxyInstance(
+       AMapper.class.getClassLoader(),  //接口的类加载器
+       new Class[]{AMapper.class},      //
+       new MyProxy()                    //
+    );
+    ```
+    
+- 对接口进行实现,假设对Mapper接口进行代理实现
+  
+    ```Java
+    public class MyProxy implements InvocationHandler {
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            //读取方法中的注解,进行具体实现
+            Select select = method.getAnnotation(Select.class);
+            System.out.println(Arrays.toString(select.value()));
+            return null;
+        }
+    }
+    ```
+    
+- 代理接口实现类,假设对一个Service接口的实现类进行代理(AOP),定义一个通用代理类 ,==将实现类设置为属性====,创建代理类时传入接口实现类==
+  
+    ```Java
+    public class ServiceProxy implements InvocationHandler {
+    
+        private Object object;
+    
+        public ServiceProxy(Object object) {
+            this.object = object;
+        }
+    
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            System.out.println("代理.....");
+            Object invoke = method.invoke(object, args);
+            System.out.println("代理。。。。");
+    //        System.out.println(proxy); 调用toString方法,由于toString方法也由invoke代理所以会继续递归
+            return invoke;
+        }
+    }
+    ```
+    
+    ```Java
+     AService aServiceProxy = (AService) newProxyInstance(
+          AService.class.getClassLoader(),
+          new Class[]{AService.class},
+          new ServiceProxy(new AServiceImpl())
+    );
+    ```
+    
+
+> [!important] 缺点:只能对接口进行代理,单个类,无法代理
+
+**CGLIB**(Code Generation Library)
+
+- CGLIB 通过**继承目标类**来创建代理,目标类的方法会被覆盖,可以对单个类进行代理
+
+- 添加依赖,具体使用
+  
+    ```Java
+    import net.sf.cglib.proxy.Enhancer;
+    import net.sf.cglib.proxy.MethodInterceptor;
+    import net.sf.cglib.proxy.MethodProxy;
+    
+    class A {
+        public void sayHello() {
+            System.out.println("Hello from A!");
+        }
+    }
+    
+    public class CGLibProxyExample {
+        public static void main(String[] args) {
+            // 创建 CGLIB 的代理对象
+            Enhancer enhancer = new Enhancer();
+            enhancer.setSuperclass(A.class); // 设置要代理的类
+            enhancer.setCallback(new MethodInterceptor() {
+                @Override
+                public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable {
+                    System.out.println("Before method call");
+                    Object result = proxy.invokeSuper(obj, args); // 调用目标方法
+                    System.out.println("After method call");
+                    return result;
+                }
+            });
+    
+            // 创建代理对象
+            A proxyA = (A) enhancer.create();
+            proxyA.sayHello(); // 通过代理对象调用方法
+        }
+    }
+    ```
+    
+
+### 原理
+
+mybatis做了什么?
+
+- 编写了一个代理类
+    - 代理对象来源:启动时获取,通过注解、路径配置进行读取
+    - 根据注解,进行具体实现
+
+📌 核心过程:
+
+1. 启动时,MyBatis 通过 `MapperProxyFactory` 为接口生成代理:
+
+```Java
+return (T) Proxy.newProxyInstance(classLoader, new Class[]{mapperInterface}, new MapperProxy());
+```
+
+1. `MapperProxy` 实现了 `InvocationHandler`,代理接口方法调用:
+
+```Java
+Object invoke(Object proxy, Method method, Object[] args)
+```
+
+所以我们在使用时,手动获取mybatis的代理对象,再进行调用
+
+> [!important] **mybatis结合spring后**,我们更加不用关心代理对象,而是直接调用接口,因为在结合Spring管理,mybatis会把代理对象和原接口进行Bean注册,使我们可以像操作Bean一样操作代理对象
+
+### 自定义拦截器
+
+- 面向插件开发者提供了丰富api,方便插件开发
+- 拦截器原理:动态代理
+
+在执行代理方法前执行拦截方法,使用起来有点有点向aop,mvc的拦截器
+
+```Shell
+@Intercepts({
+    @Signature(
+        type = Executor.class,               // 拦截 Executor 中的方法
+        method = "update",                   // 拦截 update 方法
+        args = {MappedStatement.class, Object.class}  // 参数签名匹配
+    ),
+    @Signature(
+        type = Executor.class,
+        method = "query",
+        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
+    )
+})
+```
+
+- 配置拦截类
+- 配置拦截那种类型的sql
+- 配置拦截参数
+
+做分页查询统一拦截(查询总数),和数据权限时进行拦截修改sql语句(统一的修改)
+
+  
+
+  
+
+- 学习:[https://www.itbaima.cn/document/ijay2hay19kn1k031?segment=3#doc3-%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8B%A6%E6%88%AA%E5%99%A8%EF%BC%88%E9%80%89%E5%AD%A6%EF%BC%89](https://www.itbaima.cn/document/ijay2hay19kn1k031?segment=3#doc3-%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8B%A6%E6%88%AA%E5%99%A8%EF%BC%88%E9%80%89%E5%AD%A6%EF%BC%89)
+
+  
+
+  
+
+### 自定义缓存
+
+- 学个设计模式、redis再来使用
+- 学习:[https://www.itbaima.cn/document/ijay2hay19kn1k031?segment=3#doc4-%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BC%93%E5%AD%98%EF%BC%88%E9%80%89%E5%AD%A6%EF%BC%89](https://www.itbaima.cn/document/ijay2hay19kn1k031?segment=3#doc4-%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BC%93%E5%AD%98%EF%BC%88%E9%80%89%E5%AD%A6%EF%BC%89)
+
+  
+
+  
+
+  
+
+## SpringBoot下的mybatis
+
+也就是说我们平时分层的controller、mapper、service,虽然service公用一个mapper,但是调用方法时还是通过不同的SqlSession进行执行的,只有事务下是同一个SqlSeesion对吗
+
+### ChatGPT 说:
+
+对,你理解得很准确 👌
+
+我帮你再梳理一下:
+
+---
+
+### 1. 结构层次
+
+- **Controller → Service → Mapper**
+- Mapper 本身是个 **代理对象**,Spring 注入的是 `MapperProxy`,背后实际靠 `SqlSessionTemplate` 去调 SQL。
+
+---
+
+### 2. SqlSession 的管理
+
+- `SqlSessionTemplate` 并不会固定持有一个 `SqlSession`。
+- 每次调用 Mapper 方法时,它会:
+    1. 判断当前线程是否有和事务绑定的 `SqlSession`。
+    2. 如果有,就复用(因此事务里是同一个 SqlSession)。
+    3. 如果没有,就临时创建一个新的,执行完方法后立刻关闭。
+
+---
+
+### 3. 结论
+
+- **无事务时**:
+    - Service 虽然“共享”一个 Mapper 实例,但每次调用 Mapper 方法时都会创建/关闭新的 SqlSession。
+    - 所以一级缓存不会生效。
+- **有事务时(例如加了** `**@Transactional**`**)**:
+    - 同一个事务范围内,多个 Mapper 方法共用同一个 SqlSession。
+    - 此时一级缓存可以生效(相同 SQL + 相同参数时)。
\ No newline at end of file
diff --git a/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image119.png b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image119.png
new file mode 100644
index 0000000..da83909
Binary files /dev/null and b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image119.png differ
diff --git a/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image211.png b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image211.png
new file mode 100644
index 0000000..ef09a76
Binary files /dev/null and b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image211.png differ
diff --git a/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image310.png b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image310.png
new file mode 100644
index 0000000..631f6dd
Binary files /dev/null and b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image310.png differ
diff --git a/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image34.png b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image34.png
new file mode 100644
index 0000000..5a8d1d2
Binary files /dev/null and b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image34.png differ
diff --git a/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image410.png b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image410.png
new file mode 100644
index 0000000..51816bf
Binary files /dev/null and b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image410.png differ
diff --git a/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image59.png b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image59.png
new file mode 100644
index 0000000..faf9e0d
Binary files /dev/null and b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image59.png differ
diff --git a/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image67.png b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image67.png
new file mode 100644
index 0000000..4d7c81c
Binary files /dev/null and b/posts/blog/编程技术/java/框架/MyBatis/MyBatis笔记/image67.png differ
diff --git a/posts/blog/编程技术/java/框架/Mybatis-plus/MyBatis 与 MyBatis-Plus 差异比较.md b/posts/blog/编程技术/java/框架/Mybatis-plus/MyBatis 与 MyBatis-Plus 差异比较.md
new file mode 100644
index 0000000..a1e6acd
--- /dev/null
+++ b/posts/blog/编程技术/java/框架/Mybatis-plus/MyBatis 与 MyBatis-Plus 差异比较.md	
@@ -0,0 +1,313 @@
+---
+title: MyBatis 与 MyBatis-Plus 差异比较
+date: 2025-04-18
+tags: [Mybatis-plus]
+---
+
+
+# 📖 MyBatis 与 MyBatis-Plus 差异比较
+
+## 📌 前言
+
+大家好,今天我来跟大家分享一下 **MyBatis 与 MyBatis-Plus 的差异对比**。
+
+希望通过这次分享,大家能对这两个框架的使用场景和优缺点有更清晰的认识。
+
+---
+
+## 📖 什么是 MyBatis?
+
+> MyBatis 是一款优秀的 半自动 ORM 框架,它通过映射文件(XML 或注解)将 SQL 语句与 Java 对象关联。
+
+### 📌 特点:
+
+- SQL 灵活,完全自定义
+- 映射清晰,便于调优
+- 入门简单,适合复杂业务
+
+### 📌 核心组件:
+
+- `SqlSessionFactory`
+- `SqlSession`
+- `Mapper XML` 或 `注解`
+
+---
+
+## 📖 什么是 MyBatis-Plus (MP)?
+
+> MyBatis-Plus 是在 MyBatis 基础上扩展的增强工具,简化开发,降低重复代码。
+
+### 📌 特点:
+
+- 内置 **CRUD 封装**
+- 内置分页、乐观锁、自动填充等功能
+- 零侵入,兼容 MyBatis
+
+### 📌 核心组件:
+
+- `BaseMapper`
+- `Wrapper 条件构造器`
+- 内置插件(分页、乐观锁等)
+
+---
+
+## 📊 功能对比
+
+|   |   |   |
+|---|---|---|
+|功能|MyBatis|MyBatis-Plus|
+|SQL 编写方式|XML 或注解,手动编写|内置方法 + XML/注解可扩展|
+|单表 CRUD|全部手写|内置 `BaseMapper` 实现|
+|多表关联查询|手写 SQL|手写 SQL,支持 Wrapper 辅助|
+|条件构造器|无|内置 `QueryWrapper`、`LambdaQueryWrapper`|
+|分页|需手写 SQL + 参数|内置分页插件|
+|乐观锁|手动实现|内置乐观锁插件|
+|自动填充(创建时间等)|手动编写|注解 + Handler 实现|
+|自定义 SQL|XML 或注解|同 MyBatis,完全兼容|
+|代码侵入性|低|极低|
+
+---
+
+## 📌 开发体验对比
+
+### 🔹 MyBatis
+
+- SQL 主导,灵活性极高
+- 适合复杂业务、多表联查、动态 SQL
+- 需要手动管理大量 CRUD SQL
+
+### 🔹 MyBatis-Plus
+
+- 内置单表操作,效率高
+- 条件查询封装,极简编码
+- 保留 MyBatis 灵活性,复杂业务继续 XML
+
+---
+
+## 📖 使用示例对比
+
+### 📝 插入操作
+
+### MyBatis:
+
+```XML
+
+  INSERT INTO user(name, age) VALUES(#{name}, #{age})
+
+```
+
+### MyBatis-Plus:
+
+```Java
+userMapper.insert(user);
+```
+
+---
+
+### 📝 条件查询
+
+### MyBatis:
+
+```XML
+
+```
+
+### MyBatis-Plus:
+
+```Java
+QueryWrapper wrapper = new QueryWrapper<>();
+wrapper.eq("name", "张三");
+userMapper.selectList(wrapper);
+```
+
+---
+
+### 📝 分页查询
+
+### MyBatis:
+
+```XML
+
+```
+
+### MyBatis-Plus:
+
+```Java
+Page page = new Page<>(1, 10);
+userMapper.selectPage(page, null);
+```
+
+---
+
+## 📌 适用场景
+
+### 🔹 MyBatis:
+
+- 复杂 SQL
+- 多表关联
+- 动态 SQL
+- 精细化 SQL 调优场景
+
+### 🔹 MyBatis-Plus:
+
+- 单表 CRUD 占主导
+- 快速开发、低代码项目
+- 内置功能(分页、乐观锁)需求
+
+---
+
+## 📌 优缺点总结
+
+### ✅ MyBatis 优点:
+
+- 灵活性强
+- 精细控制 SQL
+- 动态 SQL 功能完善
+
+### ❌ MyBatis 缺点:
+
+- 重复代码多
+- 配置繁琐,维护成本高
+
+---
+
+### ✅ MyBatis-Plus 优点:
+
+- 开发效率高
+- 内置常用功能
+- 零侵入,兼容 MyBatis
+
+### ❌ MyBatis-Plus 缺点:
+
+- 多表、复杂查询仍需手写
+- 极个别场景下扩展性受限
+
+---
+
+## 📖 总结
+
+- **MyBatis** → 灵活强大,适合复杂业务
+- **MyBatis-Plus** → 快速开发,适合单表+简单查询为主的项目
+
+二者并非对立,**可以混用**:
+
+**简单用 MP,复杂用 MyBatis,自定义扩展互不影响。**
+
+---
+
+## 📚 附录
+
+- 官方文档:
+    - MyBatis:[https://mybatis.org/mybatis-3/](https://mybatis.org/mybatis-3/)
+    - MyBatis-Plus:[https://baomidou.com/](https://baomidou.com/)
+
+---
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+  
+
+## 📖 15 分钟演讲节奏安排表
+
+|   |   |   |
+|---|---|---|
+|时间|内容|备注|
+|**0:00 - 1:00**|**开场+前言**|自我介绍+介绍主题,说明本次目标|
+|**1:00 - 3:00**|**什么是 MyBatis**|简述框架特点、核心组件|
+|**3:00 - 5:00**|**什么是 MyBatis-Plus (MP)**|简述框架特点、核心组件,强调“在 MyBatis 基础上增强”|
+|**5:00 - 7:30**|**功能对比表格讲解**|按表格逐行讲,结合实际项目经验点出亮点和常用项|
+|**7:30 - 10:00**|**开发体验对比 + 使用示例对比**|用示例代码对比,重点展示 MP 条件查询、分页优势|
+|**10:00 - 11:00**|**适用场景讲解**|说明哪个场景用哪个,避免滥用或误用|
+|**11:00 - 12:00**|**优缺点总结**|用简短对比方式归纳两者优劣|
+|**12:00 - 13:30**|**总结回顾**|强调“可以混用”,简单用 MP,复杂用 MyBatis|
+|**13:30 - 15:00**|**Q&A 环节**|邀请大家提问,或分享自己用过的经验|
\ No newline at end of file
diff --git a/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记.md b/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记.md
new file mode 100644
index 0000000..0f399f2
--- /dev/null
+++ b/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记.md
@@ -0,0 +1,322 @@
+---
+title: Mybatis-plus笔记
+date: 2025-04-27
+tags: [Mybatis-plus]
+---
+
+
+## 依赖
+
+只需要使用Mybatis-plus即可,mybatis不需要了
+
+## 常见注解
+
+指定字段、表名名称(字段名不一致时需指定,直接指定最安全)
+
+- 原理
+  
+    反射获得字段名时,方便生成动态sql
+    
+- @TableName:表名
+- @Tableld:指定主键,指定类型(自增、手动)
+    - 没有指定类型,默认是雪花生成算法生成ID
+- @TableField:普通字段
+
+> [!important] 特殊字段:order,指定名称可加``,比如”`order`”
+> 
+> is开头的布尔类型,需指定
+> 
+> 非数据库字段:@TableField(exist=false),手动排查
+
+## 配置文件
+
+在mybatis基础上进行更改,把mybatis改为mybatis-plus即可
+
+很多默认配置
+
+- 默认加载所有mapper.xml文件
+
+![image_(1).png](E:\\桌面\\hexo\\Notion\\images\\image_(1).png)
+
+- X的回答
+  
+    MyBatis-Plus 是基于 MyBatis 的增强框架,提供了许多默认行为和配置项,方便开发者快速集成和使用。它的配置文件主要通过 Spring Boot 的 application.yml 或 application.properties 进行设置,位于 mybatis-plus 命名空间下。以下是对 MyBatis-Plus 配置文件默认行为的全面说明,涵盖核心配置项、默认值及其作用。
+    
+    ---
+    
+    **MyBatis-Plus 配置文件的默认行为**
+    
+    MyBatis-Plus 的配置主要分为以下几个部分:
+    
+    1. **核心配置**(mybatis-plus 根节点)
+    2. **全局配置**(mybatis-plus.global-config)
+    3. **数据库相关配置**(mybatis-plus.global-config.db-config)
+    4. **MyBatis 原生配置**(mybatis-plus.configuration)
+    5. **其他高级配置**(如执行器、SQL 注入器等)
+    
+    以下按配置项逐一说明默认行为、作用和常见设置。
+    
+    ---
+    
+    **1. 核心配置(mybatis-plus 根节点)**
+    
+    这些配置直接位于 mybatis-plus 命名空间下,控制 MyBatis-Plus 的基本行为。
+    
+    |   |   |   |   |
+    |---|---|---|---|
+    |**配置项**|**默认值**|**作用**|**说明**|
+    |mapper-locations|classpath*:/mapper/**/*.xml|指定 MyBatis Mapper XML 文件的扫描路径|- 默认扫描类路径下 mapper 目录及其子目录中的所有 .xml 文件(如 src/main/resources/mapper/**/*.xml)。  
- 如果项目包含 XML 文件(如 SmtOrderDeliveryMapper.xml),MyBatis-Plus 会自动加载并解析。
- 可设置为具体路径(如 classpath:/mapper/account/*.xml)或空路径(如 classpath:/nonexistent/*.xml)以禁用 XML 扫描。| + |type-aliases-package|无(未设置)|指定实体类的包路径,用于类型别名|- 未设置时,XML 中的 resultType 或 parameterType 需使用全限定类名(如 com.qgs.official.entity.SmtAccount)。
- 设置后(如 com.qgs.official.entity),类名可简化为别名(首字母小写,如 SmtAccount 变为 smtAccount)。
- 支持通配符(如 com.qgs.official.entity.*)。| + |type-enums-package|无(未设置)|指定枚举类的包路径,用于枚举类型处理|- 未设置时,枚举类型需手动配置或使用全限定名。
- 设置后,MyBatis-Plus 自动识别指定包下的枚举类,支持枚举字段映射。| + |type-handlers-package|无(未设置)|指定自定义类型处理器(TypeHandler)的包路径|- 用于处理 Java 类型与数据库类型之间的自定义转换(如 JSON 字段)。
- 默认不启用,需手动注册 TypeHandler。| + |check-config-location|false|是否检查 MyBatis XML 配置文件(如 mybatis-config.xml)|- 默认不检查 MyBatis 的全局配置文件。
- 如果设置为 true,需确保 mybatis-config.xml 存在并正确配置。| + |executor-type|simple|指定 MyBatis 的执行器类型|- 默认使用 SimpleExecutor,每次执行 SQL 都会创建新的 Statement。
- 可选值:
- simple: 简单执行器(默认)。
- reuse: 重用 PreparedStatement。
- batch: 批量执行器,适合批量操作。| + |configuration-properties|无|自定义 MyBatis 配置属性|- 用于传递额外的 MyBatis 配置参数,格式为键值对。
- 默认无,需手动配置。| + |default-enum-type-handler|org.apache.ibatis.type.EnumTypeHandler|默认的枚举类型处理器|- 默认将枚举映射为数据库中的字符串(枚举的 name())。
- 可改为 EnumOrdinalTypeHandler(映射为枚举的序数值)或自定义处理器。| + + **默认行为总结**: + + - MyBatis-Plus 默认扫描 classpath*:/mapper/**/*.xml,这解释了为什么你的项目中未显式配置却加载了 SmtOrderDeliveryMapper.xml。 + - 没有设置 type-aliases-package,因此 XML 中的类型别名(如 smtOrderDelivery)需要全限定类名或正确注册,否则会导致 ClassNotFoundException。 + - 执行器和枚举处理使用 MyBatis 的默认设置,适合大多数场景。 + + --- + + **2. MyBatis 原生配置(mybatis-plus.configuration)** + + mybatis-plus.configuration 对应 MyBatis 的原生配置,映射到 org.apache.ibatis.session.Configuration 类。MyBatis-Plus 默认继承 MyBatis 的配置行为。 + + | | | | | + |---|---|---|---| + |**配置项**|**默认值**|**作用**|**说明**| + |map-underscore-to-camel-case|true|是否启用下划线转驼峰映射|- 默认启用,将数据库列(如 user_id)映射为 Java 字段(如 userId)。
- 你的配置中显式设置为 true,符合默认行为。| + |log-impl|无(未设置)|指定 MyBatis 的日志实现|- 默认不启用日志输出,需手动配置。
- 你的配置中设置为 org.apache.ibatis.logging.stdout.StdOutImpl,表示将 SQL 日志输出到控制台。
- 其他可选值:
- org.apache.ibatis.logging.slf4j.Slf4jImpl(SLF4J 日志)。
- org.apache.ibatis.logging.log4j2.Log4j2Impl(Log4j2)。| + |cache-enabled|true|是否启用二级缓存|- 默认启用 MyBatis 的二级缓存(基于 Mapper 命名空间)。
- 注意:MyBatis-Plus 的缓存需手动配置(如通过 标签)。| + |lazy-loading-enabled|false|是否启用延迟加载|- 默认禁用,关联对象不会延迟加载。
- 需配合 aggressive-lazy-loading 使用。| + |aggressive-lazy-loading|false|是否激进地触发延迟加载|- 默认禁用,延迟加载需显式触发。
- 如果启用,访问任何属性都会加载所有延迟对象。| + |multiple-result-sets-enabled|true|是否支持多结果集|- 默认启用,允许 SQL 返回多个结果集。| + |use-generated-keys|true|是否启用主键回填|- 默认启用,插入数据后自动回填数据库生成的主键到实体对象。
- 配合 id-type: auto 使用(如你的配置)。| + |default-statement-timeout|无(未设置)|SQL 执行的默认超时时间(秒)|- 默认由数据库驱动控制,建议显式设置(如 30)。| + |default-fetch-size|无(未设置)|默认结果集的获取行数|- 默认由数据库驱动控制,通常无需设置。| + |local-cache-scope|session|本地缓存的作用范围|- 默认使用会话级缓存(SESSION),同一会话内的查询共享缓存。
- 可设置为 statement(每次查询清空缓存)。| + |jdbc-type-for-null|OTHER|空值的默认 JDBC 类型|- 默认将 null 值映射为 JdbcType.OTHER。
- 可设置为 NULL 或其他类型。| + |call-setters-on-nulls|false|是否在结果集为 null 时调用 setter 方法|- 默认不调用,null 值直接忽略。
- 如果启用,null 值会触发 setter 方法。| + |use-actual-param-name|true|是否使用实际参数名(Java 8+)|- 默认启用,允许 MyBatis 使用编译时的参数名(如 #{paramName})。| + |return-instance-for-empty-row|false|空结果集是否返回空对象|- 默认不返回空对象,直接返回 null。
- 如果启用,空结果集返回默认构造的对象。| + + **默认行为总结**: + + - 你的配置启用了 map-underscore-to-camel-case: true 和 log-impl: StdOutImpl,其他配置继承 MyBatis 默认值。 + - 默认行为(如 use-generated-keys: true)与你的 id-type: auto 配合,支持主键自增。 + - 未设置 default-statement-timeout 等,可能导致 SQL 超时问题,建议显式配置。 + + --- + + **3. 全局配置(mybatis-plus.global-config)** + + mybatis-plus.global-config 用于设置 MyBatis-Plus 的全局行为,影响所有 Mapper。 + + | | | | | + |---|---|---|---| + |**配置项**|**默认值**|**作用**|**说明**| + |banner|true|是否打印 MyBatis-Plus 的启动 Banner|- 默认打印 MyBatis-Plus 的 Logo 和版本信息。
- 可设置为 false 禁用。| + |enable-sql-runner|false|是否启用 SQL Runner|- 默认禁用,SQL Runner 是一个用于动态 SQL 执行的工具。
- 启用后需手动配置依赖。| + |db-config|无|数据库相关配置|- 见下节(db-config)。| + + **默认行为总结**: + + - 默认打印 Banner,适合调试。 + - 你的配置只设置了 db-config,其他全局配置使用默认值。 + + --- + + **4. 数据库相关配置(mybatis-plus.global-config.db-config)** + + mybatis-plus.global-config.db-config 用于配置数据库相关的行为,如主键策略、表名映射等。 + + | | | | | + |---|---|---|---| + |**配置项**|**默认值**|**作用**|**说明**| + |id-type|NONE|主键生成策略|- 默认无策略,需手动设置主键值。
- 你的配置为 auto,表示数据库自增(如 MySQL 的 AUTO_INCREMENT)。
- 可选值:
- AUTO: 数据库自增。
- ASSIGN_ID: 雪花算法生成 ID(默认 64 位 Long)。
- ASSIGN_UUID: 生成 UUID(无 -)。
- INPUT: 手动输入 ID。| + |table-prefix|无|表名前缀|- 默认无前缀,表名与实体类名一致(驼峰转下划线)。
- 可设置为 t_,则实体 UserInfo 映射到表 t_user_info。| + |table-suffix|无|表名后缀|- 类似 table-prefix,用于追加后缀。| + |schema|无|数据库 Schema|- 默认不指定 Schema,使用数据源的默认 Schema。
- 可设置为特定 Schema(如 public)。| + |column-underline|true|是否将实体字段驼峰转为下划线|- 默认启用,与 map-underscore-to-camel-case 配合,字段 userId 映射到列 user_id。| + |capital-mode|false|是否启用大写模式|- 默认禁用,SQL 使用小写表名和列名。
- 如果启用,表名和列名转为大写(如 USER_ID)。| + |logic-delete-field|无|逻辑删除字段|- 默认无逻辑删除,需手动配置(如 deleted)。
- 配置后,delete 操作转为 UPDATE 设置该字段。| + |logic-delete-value|无|逻辑删除的“已删除”值|- 默认无,需配合 logic-delete-field 设置(如 1)。| + |logic-not-delete-value|无|逻辑删除的“未删除”值|- 默认无,需配合 logic-delete-field 设置(如 0)。| + |field-strategy|NOT_NULL|字段更新策略(已废弃,3.3.0+ 使用 insert-strategy 等)|- 默认只更新非 null 字段。
- 3.3.0+ 推荐使用 insert-strategy、update-strategy。| + |insert-strategy|NOT_NULL|插入时的字段策略|- 默认只插入非 null 字段。
- 可设置为 NOT_EMPTY(非空字符串)或 IGNORED(全部插入)。| + |update-strategy|NOT_NULL|更新时的字段策略|- 默认只更新非 null 字段。
- 可设置为 NOT_EMPTY 或 IGNORED。| + |select-strategy|NOT_NULL|查询时的字段策略|- 默认只查询非 null 条件的字段。
- 可设置为 NOT_EMPTY 或 IGNORED。| + + **默认行为总结**: + + - 你的配置设置了 id-type: auto,适合 MySQL 等自增主键场景。 + - 未设置逻辑删除或表前缀,表名和字段名通过驼峰转下划线映射。 + - 默认的 insert-strategy 和 update-strategy(NOT_NULL)确保只处理非 null 值,减少意外更新。 + + --- + + **5. 其他高级配置** + + 这些配置较少使用,但对特定场景有帮助。 + + | | | | | + |---|---|---|---| + |**配置项**|**默认值**|**作用**|**说明**| + |mybatis-plus.global-config.sql-injector|com.baomidou.mybatisplus.core.injector.DefaultSqlInjector|SQL 注入器|- 默认使用 DefaultSqlInjector,支持 MyBatis-Plus 的动态 SQL 生成功能。
- 可自定义注入器扩展 SQL。| + |mybatis-plus.global-config.identifier-quote|无|数据库标识符引用符|- 默认不使用引用符(如 MySQL 的 ``` 或 SQL Server 的 [])。
- 可设置为特定符号,防止关键字冲突。| + |mybatis-plus.auto-mapping-behavior|PARTIAL|自动映射行为|- 默认只映射未显式定义的字段。
- 可设置为 NONE(禁用自动映射)或 FULL(全部映射)。| + |mybatis-plus.auto-mapping-unknown-column-behavior|NONE|未识别列的处理行为|- 默认忽略未知列。
- 可设置为 WARNING(警告)或 FAIL(抛异常)。| + + **默认行为总结**: + + - 默认 SQL 注入器支持 MyBatis-Plus 的核心功能(如 BaseMapper 的方法)。 + - 自动映射行为(PARTIAL)平衡了灵活性和安全性。 + + --- + + **你的配置与默认行为的对比** + + 你的 application.yml 配置: + + yaml + + ```YAML + mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + map-underscore-to-camel-case: true + global-config: + db-config: + id-type: auto + ``` + + **分析**: + + 1. **已配置项**: + - log-impl: StdOutImpl:启用了控制台 SQL 日志,覆盖了默认的无日志行为。 + - map-underscore-to-camel-case: true:与默认一致,保持驼峰映射。 + - id-type: auto:覆盖默认的 NONE,使用数据库自增主键。 + 2. **未配置项(使用默认值)**: + - mapper-locations: classpath*:/mapper/**/*.xml:导致 MyBatis-Plus 扫描并加载了 SmtOrderDeliveryMapper.xml,引发异常。 + - type-aliases-package:未设置,因此 XML 中的 smtOrderDelivery 需全限定类名或手动注册别名。 + - cache-enabled: true、 use-generated-keys: true 等 MyBatis 默认配置生效。 + - insert-strategy 和 update-strategy 为 NOT_NULL,确保插入和更新只处理非 null 字段。 + 3. **异常原因**: + - 默认的 mapper-locations 扫描加载了 SmtOrderDeliveryMapper.xml。 + - XML 中使用了未注册的类型别名 smtOrderDelivery,且未配置 type-aliases-package,导致 ClassNotFoundException。 + + --- + + **如何避免类似问题** + + 针对你的场景(异常由 SmtOrderDeliveryMapper.xml 引发),以下是避免默认行为导致问题的建议: + + 1. **显式控制 XML 扫描**: + - 如果项目不依赖 XML 文件,禁用扫描:yaml + + ```YAML + mybatis-plus: + mapper-locations: classpath:/mapper/nonexistent/*.xml + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + map-underscore-to-camel-case: true + global-config: + db-config: + id-type: auto + ``` + + - 如果需要特定 XML,指定路径:yaml + + ```YAML + mybatis-plus: + mapper-locations: classpath:/mapper/account/*.xml + ``` + + 2. **配置类型别名**: + - 为实体类设置别名包:yaml + + ```YAML + mybatis-plus: + type-aliases-package: com.qgs.official.entity + ``` + + - 这允许 XML 使用 smtOrderDelivery 代替 com.qgs.official.entity.SmtOrderDelivery。 + 3. **清理无用 XML 文件**: + - 检查 src/main/resources/mapper/,删除不必要的 XML 文件(如 SmtOrderDeliveryMapper.xml)。 + - 运行:bash + + ```Shell + mvn clean install + ``` + + 4. **使用注解代替 XML**: + - 如果 SmtOrderDeliveryMapper 只使用 BaseMapper 方法,无需 XML。 + - 自定义 SQL 可使用注解:java + + ```Java + @Mapper + public interface SmtOrderDeliveryMapper extends BaseMapper { + @Select("SELECT * FROM smt_order_delivery WHERE id = #{id}") + SmtOrderDelivery selectById(Long id); + } + ``` + + 5. **显式配置其他关键项**: + - 设置 SQL 超时:yaml + + ```YAML + mybatis-plus: + configuration: + default-statement-timeout: 30 + ``` + + - 启用逻辑删除(如果需要):yaml + + ```YAML + mybatis-plus: + global-config: + db-config: + logic-delete-field: deleted + logic-delete-value: 1 + logic-not-delete-value: 0 + ``` + + + --- + + **总结** + + MyBatis-Plus 的默认行为包括: + + - **XML 扫描**:默认扫描 classpath*:/mapper/**/*.xml,加载所有 Mapper XML 文件。 + - **类型别名**:未设置 type-aliases-package,XML 需使用全限定类名。 + - **主键策略**:默认 NONE,需手动配置(如你的 id-type: auto)。 + - **字段映射**:默认启用驼峰转下划线(map-underscore-to-camel-case: true)。 + - **缓存和执行器**:继承 MyBatis 默认配置(如 cache-enabled: true、 executor-type: simple)。 + - **日志**:默认无日志输出,需手动启用(如你的 StdOutImpl)。 + + 你的配置未设置 mapper-locations,导致 MyBatis-Plus 扫描并加载了 SmtOrderDeliveryMapper.xml,而 XML 中的类型别名 smtOrderDelivery 引发了异常。可以通过删除无用 XML、限制扫描路径或配置类型别名解决问题。 + + 如果需要进一步帮助(如具体配置调整、XML 修复代码),请提供: + + 1. SmtOrderDeliveryMapper.xml 的内容(如果保留)。 + 2. SmtOrderDeliveryMapper 和 SmtOrderDelivery 的代码。 + 3. src/main/resources/mapper/ 下的文件列表。 + +## 条件构造器 + +![fc4d45732c71d06afabb4d9c2c5bb723_750.png](Mybatis-plus笔记/fc4d45732c71d06afabb4d9c2c5bb723_750.png) + + + +![a8380ec87a1efb24dbce86fac4712dbe_750.png](Mybatis-plus笔记/a8380ec87a1efb24dbce86fac4712dbe_750.png) + + + +![89da09f98a7584979883a8e38cfd4e2b_750.png](Mybatis-plus笔记/89da09f98a7584979883a8e38cfd4e2b_750.png) + +![3dced52234dc9cc8d60f6a227084423e.png](Mybatis-plus笔记/3dced52234dc9cc8d60f6a227084423e.png) \ No newline at end of file diff --git a/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记/3dced52234dc9cc8d60f6a227084423e.png b/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记/3dced52234dc9cc8d60f6a227084423e.png new file mode 100644 index 0000000..c203e3c Binary files /dev/null and b/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记/3dced52234dc9cc8d60f6a227084423e.png differ diff --git a/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记/89da09f98a7584979883a8e38cfd4e2b_750.png b/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记/89da09f98a7584979883a8e38cfd4e2b_750.png new file mode 100644 index 0000000..b7c6d3e Binary files /dev/null and b/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记/89da09f98a7584979883a8e38cfd4e2b_750.png differ diff --git a/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记/a8380ec87a1efb24dbce86fac4712dbe_750.png b/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记/a8380ec87a1efb24dbce86fac4712dbe_750.png new file mode 100644 index 0000000..e16b4d9 Binary files /dev/null and b/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记/a8380ec87a1efb24dbce86fac4712dbe_750.png differ diff --git a/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记/fc4d45732c71d06afabb4d9c2c5bb723_750.png b/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记/fc4d45732c71d06afabb4d9c2c5bb723_750.png new file mode 100644 index 0000000..adbb765 Binary files /dev/null and b/posts/blog/编程技术/java/框架/Mybatis-plus/Mybatis-plus笔记/fc4d45732c71d06afabb4d9c2c5bb723_750.png differ diff --git a/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper.md b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper.md new file mode 100644 index 0000000..42620d5 --- /dev/null +++ b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper.md @@ -0,0 +1,163 @@ +--- +title: 使用Wrapper +date: 2025-04-19 +tags: [Mybatis-plus] +--- + + +## 简介 + +用于构建复杂的数据库查询条件 + +![image.png](使用Wrapper/image.png) + +使用流程,对比Mybatis + +![image 1.png](使用Wrapper/image1.png) + +了解继承结构 + +![52971bc8-af9e-4781-b85d-42e8c54ddaa9.png](使用Wrapper/52971bc8-af9e-4781-b85d-42e8c54ddaa9.png) + + + +![image 2.png](使用Wrapper/image2.png) + +## 基本使用 + +依赖、配合BaseMapper使用 + +```Shell + + com.baomidou + mybatis-plus-spring-boot3-starter + 3.5.11 + +``` + +### 方法 + +1、eq - 等于条件 + +- 功能:指定字段等于某个值,等价于 SQL 的 = + +2、like - 模糊查询 + +- 功能:指定字段包含某个值,等价于 SQL 的 LIKE + +3、gt - 大于条件 + +- 功能:指定字段大于某个值,等价于 SQL 的 >. + +4. orderByAsc / orderByDesc - 排序 + +- 功能:指定字段按升序(ASC)或降序(DESC)排序。 + +5. in - 包含条件 + +- 功能:指定字段在某个集合中,等价于 SQL 的 IN. + +![image 3.png](使用Wrapper/image3.png) + +## Wrapper动态sql原理探究 + +为什么sql会随着我们的条件变化而变化呢?动态sql?我们可以从baseMapper原理出发 + +- 重写代理,拓展功能 +- 每个抽象方法对应一个类来处理 + +![image 4.png](使用Wrapper/image4.png) + +debug + +```Shell + +``` + + + +绑定变量 +`` + + + +属性含义: + +- sqlFirst:前置 SQL(如 DISTINCT),默认 null。 +- sqlSelect:自定义 SELECT 列,默认 null。 +- sqlSegment:WHERE 条件(如 name = ?),默认空。 +- entity:关联实体对象,默认 null。 +- nonEmptyOfNormal:sqlSegment 是否含普通条件(true/false)。 +- emptyOfNormal:sqlSegment 是否无普通条件(true/false)。 +- sqlComment:SQL 注释,默认 null。 + + + +## 在Mybatis中使用Wrapper + +将自定义方法的行参数换为Wrapper + +```Shell + +``` + +## 总结 + +使用Wrapper配合BaseMapper,以编码的形式编写sql,即可大大减少手动编写动态SQL + +其次Wrapper也可以运用在自定义sql当中 \ No newline at end of file diff --git a/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/52971bc8-af9e-4781-b85d-42e8c54ddaa9.png b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/52971bc8-af9e-4781-b85d-42e8c54ddaa9.png new file mode 100644 index 0000000..1b9bc39 Binary files /dev/null and b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/52971bc8-af9e-4781-b85d-42e8c54ddaa9.png differ diff --git a/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image.png b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image.png new file mode 100644 index 0000000..bbc70ed Binary files /dev/null and b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image.png differ diff --git a/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image1.png b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image1.png new file mode 100644 index 0000000..15e99fd Binary files /dev/null and b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image1.png differ diff --git a/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image2.png b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image2.png new file mode 100644 index 0000000..6acac3d Binary files /dev/null and b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image2.png differ diff --git a/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image3.png b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image3.png new file mode 100644 index 0000000..4d64af7 Binary files /dev/null and b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image3.png differ diff --git a/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image4.png b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image4.png new file mode 100644 index 0000000..77d05bc Binary files /dev/null and b/posts/blog/编程技术/java/框架/Mybatis-plus/使用Wrapper/image4.png differ diff --git a/posts/blog/编程技术/java/框架/Spring/Spring笔记.md b/posts/blog/编程技术/java/框架/Spring/Spring笔记.md new file mode 100644 index 0000000..47da6a3 --- /dev/null +++ b/posts/blog/编程技术/java/框架/Spring/Spring笔记.md @@ -0,0 +1,1305 @@ +--- +title: Spring笔记 +date: 2025-02-10 +tags: [Spring] +--- + + +## 目的 + +- 简化开发 + +> [!important] +> +> - xml:xml中注册Bean即可 +> - 注解:配置类中设置范围,具体类中添加注解即可 + +- 核心依赖 + + ```JavaScript + + org.springframework + spring-context + 6.0.4 + + ``` + + +## IoC容器 + +- Inversion of Control,控制反转 +- 目的:解耦合,==只需要将====**实现类进行注册**====,然后直接使用接口即可== + - 比如A类中使用了B类,现在升级了,将B类换成C类,将引发大面积的改动 + - 解决方案: + - 首先使用接口而不是具体类 + - 使用者:从IoC中获取实现类(不直接使用new对象) + - 开发者:将实现接口的类交给IoC管理(修改xml文件) + - xml相当于就是IoC容器,这样修改实现类则不会影响使用者 + - IoC会自动将继承关系的类进行配对,控制权力交给IoC管理 + - ==只需要将====**实现类进行注册**====,然后直接使用接口即可== +- 由IoC容器管理的对象称为:**Bean** + + ![image 33.png](Spring笔记/image33.png) + +- xml配置文件语法 + + ```XML + + + + + + + + + + + + + + + + + + + + + + + + + + ``` + + ![image 1 18.png](Spring笔记/image118.png) + + 配置全局 + + + +- 启动类 + + ```Java + public static void main(String[] args) { + ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml"); + BookService stu=context.getBean(BookService.class); + } + ``` + +- 当一个接口,注册了2个Bean子类,如果直接获取接口实例(`getBean(Service.class)`),则会报错,因为有多个子类,此时只能将接口实现具体化(`getBean(XXXService.class)`) +- IoC容器管理bean的方式,默认==单例模式、懒加载、加载顺序问题== + + IoC容器管理,默认为单例模式,每次获得的对象是同一个对象 + + ![image 2 10.png](Spring笔记/image210.png) + + ```XML + + ``` + + ```XML + + + ``` + + +## 依赖注入 + +- DI(Dependency Injection),目的依然是解耦合 +- **对属性进行解耦**,思想与IoC中的一致 + - setter方法进行注入 + - 构造方法进行注入 +- xml中使用`property`进行设置;也可以在构造方法中进行依赖注入([[Spring]]) + + ```XML + + + + + + + AA + BB + + + + + + + + + + ``` + +- **自动装配** + + ```XML + + + + + + + + + ``` + + +### bean的生命周期和继承 + +- 配置init方法和destroy方法 + + - init方法的调用时机 + - 单例是启动时调用,原型模式是创建对象时调用 + - ==@PostConstruct== + - destroy方法的调用时机 + - 当容器销毁时调用,将`context.close();` 时调用 + - ==@PreDestroy== + + ```XML + + ``` + +- 两个属性一样的Bean,可以通过==**继承**==减少冗余代码,可以`abstract`修饰一个bean作为模板提供继承 + + ```XML + + ``` + + +### 工厂Bean + +- 设置spring不通过反射,而是使用工厂模式进行创建`factory-method` + + ```XML + + ``` + +- 当获取方法不是静态方法时,将工厂类进行注册,再设置 + + ```XML + + + ``` + +- 标准做法,工厂类继承`FactoryBean`类,则只需要注册工厂类即可 + - 如果通过name获取产品Bean,则会返回工厂Bean,如果要获取工厂Bean则name值为`&factory` + +## 注解开发 + +- 使用配置类代替xml文件 + + ```Java + @Configuration + public class Config { + + //@Bean("book") 起别名 + @Bean + public BookDao bookDao(){ + return new BookDaoImpl(); + } + + //手动创建对象、依赖注入 + @Bean + public BookService bookService(BookDao bookDao){ + BookServiceImpl bookService = new BookServiceImpl(); + bookService.setBookDao1(bookDao); + return bookService; + } + //自动装配,只需要在属性上填入@Autowired即可,默认byType + //类型 + + } + ``` + +- 启动方式 + + ```Java + ApplicationContext context=new AnnotationConfigApplicationContext(Config.class); + ``` + +- 再次简化配置类,不使用bean注解,使用`@Component` 注解标注实现类,并且有参构造函数的参数会自动注入 + + ```Java + @Configuration + @ComponentScan("com.Q.*") //添加扫描位置的注解 + public class Config { + } + ``` + + +## Bean Aware + + + +## 任务调度 + +### **异步任务** + +- 开启异步任务,在**配置类**中加`**@EnableAsync**` ,在**异步方法**中添加`**@Async**` ,返回值只能是`void`或`Future` +- 如果你没有显式配置异步线程池,Spring 会使用默认的线程池(`SimpleAsyncTaskExecutor`)为每个异步任务开启一个线程,并且是无限制的线程创建,不建议直接使用,**最好手动配置线程池** + + ```Java + @Bean + public Executor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); // 核心线程数 + executor.setMaxPoolSize(10); // 最大线程数 + executor.setQueueCapacity(25); // 队列容量 + executor.setThreadNamePrefix("Async-"); // 线程名前缀 + executor.initialize(); + return executor; + } + ``` + + - 参数解释 + - `CorePoolSize`:线程池中保持的最小线程数。即使没有任务执行,线程池也会保持这些线程。 + - `MaxPoolSize`:线程池能容纳的最大线程数。当任务量激增时,线程池会创建最多 `MaxPoolSize` 个线程。 + - `QueueCapacity`:任务队列的容量。任务会先放到队列中,直到有线程可用为止。如果队列满了,且线程池已达到最大线程数,新的任务将被拒绝执行。 + +### **定时任务** + +- **配置类**注解`**@EnableScheduling**` ,定时任务方法注解`@``**Scheduled**``(fixedRate = 2000)`,Bean注册后将自己执行,参数解释 + - fixedDelay:在上一次定时任务执行完之后,间隔多久继续执行。 + - fixedRate:无论上一次定时任务有没有执行完成,两次任务之间的时间间隔。 + - cron:如果嫌上面两个不够灵活,你还可以使用cron表达式来指定任务计划 +- Spring 默认使用 **单线程** 执行所有 `@Scheduled` 注解标记的任务,意味着如果多个定时任务有重叠,前一个任务没有完成时,后一个任务将被阻塞,直到前一个任务执行完毕。手动配置 + + ```Java + @Bean + public ThreadPoolTaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(5); // 设置线程池大小 + scheduler.setThreadNamePrefix("Scheduled-"); // 设置线程前缀 + scheduler.setAwaitTerminationSeconds(60); // 线程池等待终止的最大时间 + scheduler.setRemoveOnCancelPolicy(true); // 取消任务时,自动删除任务 + return scheduler; + } + ``` + + - 参数详解 + - `setPoolSize(int poolSize)`:设置线程池的大小(即定时任务能够并发执行的最大线程数)。 + - `setThreadNamePrefix(String threadNamePrefix)`:为每个线程设置一个前缀,方便调试和日志管理。 + - `setAwaitTerminationSeconds(int seconds)`:设置线程池关闭时的等待时间,确保所有任务都能够正确完成。 + - `setRemoveOnCancelPolicy(true)`:当任务取消时,自动从线程池中移除。 +- 原理是对Bean进行了动态代理 + +## 监听器 + +- 监听事件:编写监听器类,记得注册Bean,TaskEvent指的是事件 + + ```Java + @Component + public class TaskListener implements ApplicationListener { + @Override + public void onApplicationEvent(TaskEvent event) { + System.out.println("收到了"); + System.out.println(event); + System.out.println(event.getSource()); + } + } + ``` + +- 自定义事件,写一个构造方法 + + ```Java + public class TaskEvent extends ApplicationEvent { + public TaskEvent(Object source) { + super(source); + } + } + ``` + +- 触发事件,注入一个事件发布器,发布事件然后触发 + + ```Java + @Autowired + ApplicationEventPublisher publisher; + public void test() { + publisher.publishEvent(new TaskEvent(this)); + } + ``` + + +## SpringEL表达式 + +- 简洁的装配Bean +- 配置类中配置文件`@PropertySource` ,可以直接给属性赋值`@value` + + ```Java + @PropertySource("classpath:app.properties") //配置文件注解,在文件中不需要classpath + + @Value("${Stu.name}") + private String name; + ``` + +- Spring Expression Language(SpEL),SpEL表达式 + + ```Java + Expression exp = parser.parseExpression("name.toUpperCase()"); //如果Student对象中的name属性为null + System.out.println(exp.getValue(student)); + ``` + + 一般不直接使用,常用注解的方式使用 + + +## AOP + +- Aspect Oriented Programming +- 在保证源代码的基础上,**对代码进行增强** + + ![image 3 9.png](Spring笔记/image39.png) + + + + +- 原理:**动态代理** +- 关于动态代理,Spring AOP 实际上可以使用两种类型的动态代理机制:JDK 动态代理和 CGLIB 代理。选择哪种方式取决于被代理的目标对象是否实现了接口: + +1. **JDK 动态代理**:如果目标类实现了至少一个接口,Spring AOP 默认会使用 JDK 提供的动态代理机制。JDK 动态代理只能代理接口中的方法,这意味着它为接口生成代理对象,并且只拦截接口定义的方法调用。 +2. **CGLIB 代理**:如果目标类没有实现任何接口,Spring AOP 会使用 CGLIB 库来实现代理。CGLIB 通过继承的方式实现代理对象,它可以代理类中的所有方法,包括那些未在任何接口中声明的方法。值得注意的是,由于它是基于继承的,因此不能代理被标记为 `final` 的类或方法。 + +**使用xml配置实现AOP** + +- 导入依赖 + + ```JavaScript + + org.springframework + spring-aspects + 6.0.10 + + ``` + +- 配置aop.xml,**切点**:需要代理的方法,**切面**:绑定切面类,配置代理内容和对应切点 + + ```JavaScript + + + + + + + + + + + + + + + + + + ``` + +- 如果要对代理方法的内容进行增强,`JoinPoint` 表示方法对象(method),可以获得形参、对象及方法调用 + + ```JavaScript + public void method(JoinPoint joinPoint){ + System.out.println(joinPoint); + System.out.println("aop"); + } + ``` + +- 使用环绕`around` ,对方法进行完全代理,需要手动执行方法 + + ```JavaScript + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + System.out.println(joinPoint); + Object proceed = joinPoint.proceed(); + System.out.println("aop"); + return proceed; + } + ``` + + +**接口实现AOP** + +- 和上面差不多,唯一的区别是**将绑定类、代理方式、切点结合** +- 将代理类实现aop的接口,按需实现接口,配置xml文件即可 + + ```JavaScript + + ``` + + ```JavaScript + public class StuImplAOP implements MethodBeforeAdvice { + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + System.out.println("before"); + } + } + ``` + + +**注解实现AOP** + +- 配置类增加开启aop的注解`@EnableAspectJAutoProxy` +- 代理类中添加`@Aspect`注解,在代理方法中添加相应注解,在注解中设置切点`execution(public * study())` +- 因为 Spring AOP 的 `@annotation` 表达式只会匹配**方法上直接声明的注解,**如果要对方法上的参数进行注解拦截,只能先拦截方法级别,再反射形参上的注解进行验证 + + ```Java + @Component + @Aspect + public class StuAOP { + @After("execution(public * study())") + public void method(JoinPoint joinPoint){ + System.out.println(joinPoint); + System.out.println("aop"); + } + @Around("execution(public * task())") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + System.out.println(joinPoint); + Object proceed = joinPoint.proceed(); + System.out.println("aop"); + return proceed; + } + } + ``` + +- **AOP的实际用处** + + 在实际开发中,Spring AOP(面向切面编程)是一个非常强大的工具,它通过将横切关注点(Cross-Cutting Concerns)从业务逻辑中分离出来,提高了代码的模块化、可维护性和可重用性。以下是 AOP 在实际开发中的常见使用场景: + + --- + + **1. 事务管理** + + - **描述**:这是 Spring AOP 最经典的应用场景,通过 @Transactional 注解实现声明式事务管理。 + - **使用方式**:在服务层方法上添加 @Transactional,AOP 会自动在方法执行前后管理事务(开启、提交或回滚)。 + - **示例**: + + ```Java + @Service + public class UserService { + @Transactional(rollbackOn = Exception.class) + public void saveUser(User user) { + // 数据库操作 + } + } + ``` + + - **作用**:无需手动编写事务控制代码,统一处理事务逻辑。 + + --- + + **2. 日志记录** + + - **描述**:记录方法的执行信息,如入参、返回值、执行时间、异常等,用于调试、监控或审计。 + - **使用方式**:通过自定义切面拦截指定方法,记录日志。 + - **示例**: + + ```Java + @Aspect + @Component + public class LoggingAspect { + @Around("execution(* com.example.service.*.*(..))") + public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { + long start = System.currentTimeMillis(); + Object result = joinPoint.proceed(); + long timeTaken = System.currentTimeMillis() - start; + System.out.println("Method " + joinPoint.getSignature() + " took " + timeTaken + "ms"); + return result; + } + } + ``` + + - **作用**:集中管理日志逻辑,避免在每个业务方法中重复编写。 + + --- + + **3. 权限控制** + + - **描述**:在方法执行前检查用户权限,确保只有授权用户才能访问特定功能。 + - **使用方式**:通过切面拦截控制器或服务层方法,结合自定义注解或权限校验逻辑。 + - **示例**: + + ```Java + @Aspect + @Component + public class PermissionAspect { + @Before("@annotation(RequiresPermission)") + public void checkPermission(JoinPoint joinPoint) { + // 校验当前用户权限 + if (!hasPermission()) { + throw new SecurityException("无权限访问"); + } + } + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface RequiresPermission { + String value(); + } + + @Service + public class AdminService { + @RequiresPermission("admin:edit") + public void updateSettings() { + // 管理员操作 + } + } + ``` + + - **作用**:统一处理权限校验,提升安全性。 + + --- + + **4. 性能监控** + + - **描述**:监控方法的执行时间,识别性能瓶颈。 + - **使用方式**:通过 @Around 切面统计方法耗时并记录。 + - **示例**: + + ```Java + @Aspect + @Component + public class PerformanceMonitor { + @Around("execution(* com.example.controller.*.*(..))") + public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable { + long start = System.currentTimeMillis(); + Object result = joinPoint.proceed(); + long end = System.currentTimeMillis(); + System.out.println("Execution time: " + (end - start) + "ms"); + return result; + } + } + ``` + + - **作用**:帮助开发者优化系统性能。 + + --- + + **5. 异常处理** + + - **描述**:捕获业务逻辑中的异常,进行统一处理(如记录日志、返回友好提示)。 + - **使用方式**:通过 @AfterThrowing 切面捕获异常。 + - **示例**:java + + ```Java + @Aspect + @Component + public class ExceptionAspect { + @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex") + public void handleException(Throwable ex) { + System.err.println("Exception occurred: " + ex.getMessage()); + // 记录日志或通知管理员 + } + } + ``` + + - **作用**:避免在业务代码中重复编写 try-catch,提升代码整洁度。 + + --- + + **6. 缓存管理** + + - **描述**:在方法调用时检查缓存,若缓存命中则直接返回结果,否则执行方法并缓存结果。 + - **使用方式**:通过自定义切面或 Spring Cache(如 @Cacheable)实现。 + - **示例**:java + + ```Java + @Aspect + @Component + public class CacheAspect { + private Map cache = new HashMap<>(); + + @Around("@annotation(Cacheable)") + public Object cache(ProceedingJoinPoint joinPoint) throws Throwable { + String key = joinPoint.getSignature().toString(); + if (cache.containsKey(key)) { + return cache.get(key); + } + Object result = joinPoint.proceed(); + cache.put(key, result); + return result; + } + } + ``` + + - **作用**:减少数据库或外部服务的访问,提高性能。 + + --- + + **7. 方法参数校验** + + - **描述**:在方法执行前对参数进行校验,确保输入符合要求。 + - **使用方式**:通过切面拦截方法,检查参数合法性。 + - **示例**:java + + ```Java + @Aspect + @Component + public class ValidationAspect { + @Before("execution(* com.example.controller.*.*(..))") + public void validate(JoinPoint joinPoint) { + Object[] args = joinPoint.getArgs(); + for (Object arg : args) { + if (arg == null) { + throw new IllegalArgumentException("参数不能为空"); + } + } + } + } + ``` + + - **作用**:统一校验逻辑,避免业务代码中重复检查。 + + --- + + **8. 请求限流** + + - **描述**:限制某个接口的访问频率,防止系统过载。 + - **使用方式**:通过切面结合限流算法(如令牌桶或计数器)。 + - **示例**:java + + ```Java + @Aspect + @Component + public class RateLimitAspect { + private RateLimiter limiter = RateLimiter.create(10.0);// 每秒10次 + + @Around("@annotation(RateLimit)") + public Object limit(ProceedingJoinPoint joinPoint) throws Throwable { + if (!limiter.tryAcquire()) { + throw new RuntimeException("请求过于频繁"); + } + return joinPoint.proceed(); + } + } + ``` + + - **作用**:保护系统免受高并发冲击。 + + --- + + **总结** + + AOP 的核心价值在于将横切关注点(如事务、日志、权限、缓存等)从业务逻辑中抽离出来,常见的场景包括: + + - **事务管理**:声明式事务。 + - **日志记录**:统一记录系统行为。 + - **权限控制**:确保安全性。 + - **性能监控**:优化系统。 + - **异常处理**:统一异常管理。 + - **缓存管理**:提升效率。 + - **参数校验**:保证输入合法性。 + - **请求限流**:保护系统稳定。 + + 在实际开发中,AOP 通常与自定义注解结合使用,进一步提高灵活性和可读性。你可以根据项目需求选择适合的场景应用 AOP!有什么具体的开发场景想深入讨论吗? + + +## 原理 + +SpringAOP做了什么? + +- 编写了通用的代理类,启动时,(通过配置)将我们需要代理的对象进行代理,最后**替换Spring容器中的Bean** +- 代理类的生成时机,取决于Bean的作用域和初始化策略 + +**AOP 实际执行流程总结:** + +1. Spring 容器启动,扫描 Bean +2. 检查 Bean 上是否有 AOP 切面匹配 +3. 如果有,创建代理对象(JDK 动态代理或 CGLIB) +4. 调用方法 → 实际是代理对象的方法 +5. 代理对象的方法中: + - 执行前置通知 + - 调用目标方法 + - 执行后置通知 + +所以,我们的每次调用方法,以及controller执行service里的方法,都是代理后的 + +## 数据库整合 + +### 数据源 + +- 通过源码可知,mybatis不是通过JDBC那套`DriverManager`来获取连接,而是通过`DataSource`来获取,减少连接开销 + + ![image 4 9.png](Spring笔记/image49.png) + +- 常见数据源 + - POOLED:使用连接词的数据源 + - JNDI:使用JNDI实现的数据源 + +**Mybatis的数据源** + +- 不管是不是池化数据源,基础连接依然是`connection` +- [https://www.itbaima.cn/document/h7sjo5oy0l03607e?segment=4#doc1-%E8%A7%A3%E8%AF%BBMybatis%E6%95%B0%E6%8D%AE%E6%BA%90%E5%AE%9E%E7%8E%B0%EF%BC%88%E9%80%89%E5%AD%A6%EF%BC%89](https://www.itbaima.cn/document/h7sjo5oy0l03607e?segment=4#doc1-%E8%A7%A3%E8%AF%BBMybatis%E6%95%B0%E6%8D%AE%E6%BA%90%E5%AE%9E%E7%8E%B0%EF%BC%88%E9%80%89%E5%AD%A6%EF%BC%89) + +### Mybatis整合 + +- 依赖:==一个jdbc、一个mybatis、一个jdbc-spring、一个mybatis-spring== + + ```XML + + + mysql + mysql-connector-java + 8.0.33 + + + org.mybatis + mybatis + 3.5.10 + + + + org.springframework + spring-jdbc + 6.0.17 + + + org.mybatis + mybatis-spring + 3.0.3 + + ``` + +- 有配置文件,半自动获取,将SqlSessionFactory进行注册,再通过它获得Mapper实现类 + + - 设置xml配置文件 + + ```XML + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ``` + + - 在Spring配置类中,进行注册`SqlSessionTemplate` + + ```Java + @Bean + public SqlSessionTemplate sqlSessionTemplate() throws IOException { + SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build( + Resources.getResourceAsReader("mybatis.xml") + ); + return new SqlSessionTemplate(sqlSessionFactory); + } + ``` + + - 使用 + + ```Java + ApplicationContext context=new AnnotationConfigApplicationContext(Config.class); + SqlSessionTemplate sqlSessionTemplate = context.getBean(SqlSessionTemplate.class); + TestMapper testMapper = sqlSessionTemplate.getMapper(TestMapper.class); + System.out.println(testMapper.selectAll()); + ``` + +- 有配置文件,全自动获取,==直接获取Mapper实现类==,在上面的基础上,在Spring的配置类中增加`@MapperScan("com.qgs.mapper")`进行扫描 + +> [!important] 当前配置,不需要在接口中添加注解 + +**简化mybatis的配置文件** + +- 在Spring的配置类中,注册数据源(这个是mybatis提供的数据源),这样就不需要xml配置文件了,注意,注册的是`DataSource` 和 `**SqlSessionFactoryBean**` + + ```Java + @Bean + public DataSource dataSource(){ + return new PooledDataSource( + "com.mysql.cj.jdbc.Driver", + "jdbc:mysql://localhost:3306/word", + "root", + "qgs" + ); + } + + @Bean + public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException { + SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); + sqlSessionFactoryBean.setDataSource(dataSource); + return sqlSessionFactoryBean; + } + ``` + + +> [!important] 不过数据库的配置容易发送更改,最后还是以配置文件的形式 + +### HiKariCP连接池 + +- springBoot3官方推荐的数据源实现,性能好 +- 更换数据源 + + - 导入依赖 + + ```XML + + com.zaxxer + HikariCP + 5.0.1 + + ``` + + - 在spring配置类中修改数据源 + + ```Java + @Bean + public DataSource dataSource(){ + HikariDataSource hikariDataSource=new HikariDataSource(); + hikariDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/word"); + hikariDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); + hikariDataSource.setUsername("root"); + hikariDataSource.setPassword("qgs"); + return hikariDataSource; + } + ``` + + +![image 5 8.png](Spring笔记/image58.png) + +报错是因为没有日志实现(SLF4J是一个日志接口),但不影响结果 + +> [!important] 总结:在配置类中添加数据源、SqlSessionFactoryBean、扫描Mapper的注解即可 + +### 事务 + +- 基础知识 [[事务]] + +**Mybatis的事务** + +- mybatis事务的抽象接口 + + ```Java + public interface Transaction { + Connection getConnection() throws SQLException; + + void commit() throws SQLException; + + void rollback() throws SQLException; + + void close() throws SQLException; + + Integer getTimeout() throws SQLException; + } + ``` + +- MyBatis的事务管理分为两种形式: + + - 使用**JDBC**的事务管理机制:即利用对应数据库的驱动生成的`Connection`对象完成对事务的提交(commit())、回滚(rollback())、关闭(close())等,对应的实现类为`JdbcTransaction` + - 使用**MANAGED**的事务管理机制:这种机制MyBatis自身不会去实现事务管理,而是让程序的容器(比如Spring)来实现对事务的管理,对应的实现类为`ManagedTransaction` + - 如果需要自定义,那么得实现`org.apache.ibatis.transaction.Transaction`接口,然后在`type`属性中指定其类名。使用自定义的事务管理器可以根据具体需求来实现一些特定的事务管理行为。 + + ```XML + + ``` + + +**使用Spring事务管理** + +- 编程式事务和**声明式事务** +- Spring的事务实现原理是**aop,**使用**适配器模式**,匹配各个数据源的**事务管理器** +- spring使用事务 + - 在配置类中使用`@EnableTransactionManagement` 开启事务,注册事务管理器,需要先配置数据源 + + ```Java + @Bean + public TransactionManager transactionManager(DataSource dataSource){ + return new DataSourceTransactionManager(dataSource); + } + ``` + + - 在服务方法上添加`@Transactional` 注解,一旦发生异常自动回滚 + + ```Java + @Transactional + @Override + public boolean insertTest(Test test) { + testMapper.insertTest(test); + int i=1/0; + return testMapper.insertTest(test.setId(test.getId()+1))>0; + } + ``` + + +- 事务注解的一些属性 + + - transactionManager:指定事务管理器 + + - propagation:**事务传播规则**,一个事务可以包括N个子事务 + + - 事务嵌套 + - 事务传播7种级别 + + ![image 6 6.png](Spring笔记/image66.png) + + - isolation:事务隔离级别,不多说了 + - timeout:事务超时时间 + - readOnly:是否为只读事务,不同的数据库会根据只读属性进行优化,比如MySQL一旦声明事务为只读,那么久不允许增删改操作了。 + - rollbackFor和noRollbackFor:发生指定异常时回滚或是不回滚,默认发生任何异常都回滚。 +- **事务的传播行为** + + **1、REQUIRED** (默认行为) + + - 行为: 如果当前没有事务则创建新事务,如果已存在事务则加入该事务 + - 最常用的,所有行为共有一个事务 + + **2、REQUIRES_NEW(新事务)** + + - 行为: 总是创建新事务,如果当前存在事务则将其挂起 + - 可以独立于当前事务,当前事务即使回滚了(失败),也不影响当前事务(适合写入日志) + + **3、NESTED(嵌套事务)** + + - 行为: 如果当前存在事务则创建嵌套事务,否则等同于REQUIRED + - 作为子事务,父事务失败子也失败,但子不影响父事务;适用于附加服务:通知、发放优惠价等非必要服务 + + 其他 + + - 只读操作可以使用**SUPPORTS**提升性能 + - 特别重要且必须在事务中的操作使用**MANDATORY** +- **事务失效** + - 场景一:方法不是public;原因:Spring AOP代理只能拦截public方法 + + ```Java + @Service + public class UserService { + @Transactional + private void updateUser() { // 事务无效 + // ... + } + } + ``` + + - 场景二:类内部调用 + + ```Java + @Service + public class UserService { + public void A() { // 外部调用A方法 + // do something + B(); // 直接调用B,事务失效 + } + + @Transactional + public void B() { + // 事务操作 + } + } + + 解决方案: + + @Service + public class UserService { + @Autowired + private UserService self; // 注入自己 + + public void A() { + // do something + self.B(); // 通过代理对象调用 + } + + @Transactional + public void B() { + // 事务操作 + } + } + ``` + + - 场景三:异常被吃掉 ; 解决方案`重新抛出异常` + + ```Java + @Service + public class UserService { + @Transactional + public void updateUser() { + try { + // 数据库操作 + } catch(Exception e) { + log.error("更新失败", e); + // 异常被吃掉,事务不会回滚 + } + } + } + ``` + + - 场景四:错误的异常类型; `@Transactional(rollbackFor = Exception.class) // 指定回滚异常类型` + + ```Java + @Service + public class UserService { + @Transactional // 默认只回滚RuntimeException + public void updateUser() throws Exception { + throw new Exception("检查异常"); // 事务不会回滚 + } + } + ``` + + +### 集成JUnit测试 + +- 原本的@Test已经足够方便,但每次都要创建ApplicationContext有点麻烦,集成spring可以交给管理 + +- 导入依赖 + + ```XML + + org.junit.jupiter + junit-jupiter + 5.9.0 + test + + + org.springframework + spring-test + 6.0.10 + + ``` + +- 测试类,2个配置注解,就可以直接依赖注入 + + ```Java + @ExtendWith(SpringExtension.class) + @ContextConfiguration(classes = TestConfiguration.class) + public class TestMain { + + @Autowired + TestService service; + + @Test + public void test(){ + service.test(); + } + } + ``` + + +## 原理探究 + +### 创建Bean + +创建Bean接口`ObjectFactory` + +- `SimpleInstantiationStrategy` (反射) +- `CglibSubclassingInstantiationStrategy` (CGLB动态代理) + +**Spring 完成所有 Bean 创建的完整流程** + +📊 全流程总览: + +```Plain +refresh() + └──> prepareBeanFactory() + └──> invokeBeanFactoryPostProcessors() + └──> registerBeanPostProcessors() + └──> finishBeanFactoryInitialization() + └── getBeanDefinitionNames() + └── for each Bean: + getBean() + └──> createBean() + └──(走单个Bean创建流程) + └──> finishRefresh() +``` + +--- + +- **📖 详细步骤:** + + 1️⃣ `refresh()` + + - Spring 容器启动入口方法,执行容器初始化 + + --- + + 2️⃣ `prepareBeanFactory()` + + - 初始化 BeanFactory 配置 + - 注册内置 BeanPostProcessor、Aware 处理器等 + + --- + + 3️⃣ `invokeBeanFactoryPostProcessors()` + + - 执行所有实现 `BeanFactoryPostProcessor` 的 Bean + - 允许动态修改 BeanDefinition 或注册新 BeanDefinition + + --- + + 4️⃣ `registerBeanPostProcessors()` + + - 注册所有实现 `BeanPostProcessor` 的 Bean + - 注意:AOP 相关代理包装器、事务代理器都在这里注册 + + --- + + 5️⃣ `finishBeanFactoryInitialization()` + + - 📌 核心:**实例化所有单例非懒加载 Bean** + + ```Java + String[] beanNames = getBeanDefinitionNames(); + for (String beanName : beanNames) { + getBean(beanName); + } + ``` + + - 每个 Bean 执行 `getBean()` + - 执行上面那套**单个 Bean 创建流程** + + --- + + 6️⃣ `finishRefresh()` + + - 发布容器刷新完成事件 + - 调用监听器 + - 容器启动完成 + + --- + + 📌 📊 总结表: + + | | | | + |---|---|---| + |阶段|功能|是否创建 Bean| + |`refresh()`|容器启动入口,执行准备工作|否| + |`prepareBeanFactory()`|配置 BeanFactory,注册核心组件|否| + |`invokeBeanFactoryPostProcessors()`|执行 BeanFactoryPostProcessor|部分创建(后置处理器相关)| + |`registerBeanPostProcessors()`|注册 BeanPostProcessor(AOP、事务代理器等)|部分创建| + |`finishBeanFactoryInitialization()`|实例化所有非懒加载单例 Bean|✅ 全部单例创建| + |`finishRefresh()`|发布容器刷新完成事件|否| + + +**单个单例 Bean 从创建到注入完成的全过程** + +📊 流程图: + +```Plain +createBean() + └──> 实例化(createBeanInstance) + └── 反射 or CGLIB 创建原始对象 + └── 暴露 ObjectFactory 到 三级缓存(singletonFactories) + └──> 填充属性(populateBean) + └── getBean() 获取依赖 + └── 若依赖循环,走三级缓存解决 + └──> 调用 BeanPostProcessor.postProcessBeforeInitialization + └──> 调用 InitializingBean.afterPropertiesSet() + └──> 调用自定义 init-method + └──> 调用 BeanPostProcessor.postProcessAfterInitialization + └── AOP 代理点:这里决定是否包装代理 + └──> 放入 一级缓存(singletonObjects) +``` + +--- + +- 📖 详细步骤: + + 1️⃣ 实例化对象 `createBeanInstance()` + + - 找到构造方法 + - 反射调用 `newInstance()` 或 CGLIB 动态代理生成对象 + - 此时对象是“半成品”,属性未注入 + + **👉 同时在三级缓存放入 ObjectFactory** + + ```Java + singletonFactories.put(beanName, () -> getEarlyBeanReference(beanName, bean)); + ``` + + --- + + 2️⃣ 填充属性 `populateBean()` + + - 解析 @Autowired/@Value/@Resource 等依赖 + - 调用 `getBean()` 递归创建依赖对象 + - 如果依赖 Bean 正在创建 → 查三级缓存解决循环依赖 + - 全部注入完成 + + --- + + 3️⃣ 执行 BeanPostProcessor.beforeInitialization + + - 所有实现了 `BeanPostProcessor` 的 `postProcessBeforeInitialization()` 方法依次执行 + + --- + + 4️⃣ 调用 InitializingBean.afterPropertiesSet() + + - 如果 Bean 实现了 `InitializingBean` 接口,调用它的 `afterPropertiesSet()` + + --- + + 5️⃣ 调用自定义 init-method + + - 如果配置了 `init-method="xxx"`,执行这个初始化方法 + + --- + + 6️⃣ 执行 BeanPostProcessor.afterInitialization + + - 所有实现了 `BeanPostProcessor` 的 `postProcessAfterInitialization()` 方法依次执行 + - 📌 **AOP 动态代理点就在这里**,如果需要代理,包装代理对象返回 + + --- + + 7️⃣ 放入一级缓存 singletonObjects + + - 初始化完成的 Bean 放入一级缓存 + + ```Java + singletonObjects.put(beanName, fullyInitializedBean); + ``` + + - 同时从二级、三级缓存移除 + +**说明:** + +- 如果 Bean 没有默认构造方法,或者特殊情况下(比如 FactoryBean 或一些 BeanDefinition 中设置了 methodOverride)需要生成**子类** +- 此时通过 `CGLIB Enhancer.create()` 创建子类 + +### 三级缓存 + +是3个Map,作用是解决**单例属性/setter注入的循环依赖问题**,构造器注入的循环依赖是无解的 + +一级缓存 + +- 存初始化、属性注入完整的Bean + +二级缓存 + +- 中间层,存第半成品,保证多次依赖请求拿到同一个早期对象,避免重复调用 ObjectFactory + +三级缓存 + +- 存反射/代理生成的对象(Bean) + +> [!important] 但是SpringBoot在2.6版本后,只要有循环依赖,则运行就会报错,除非配置文件说明(不推荐),建议重构解决该问题 \ No newline at end of file diff --git a/posts/blog/编程技术/java/框架/Spring/Spring笔记/image118.png b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image118.png new file mode 100644 index 0000000..b3e7cad Binary files /dev/null and b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image118.png differ diff --git a/posts/blog/编程技术/java/框架/Spring/Spring笔记/image210.png b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image210.png new file mode 100644 index 0000000..4b68d27 Binary files /dev/null and b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image210.png differ diff --git a/posts/blog/编程技术/java/框架/Spring/Spring笔记/image33.png b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image33.png new file mode 100644 index 0000000..917a2e6 Binary files /dev/null and b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image33.png differ diff --git a/posts/blog/编程技术/java/框架/Spring/Spring笔记/image39.png b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image39.png new file mode 100644 index 0000000..7ab1790 Binary files /dev/null and b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image39.png differ diff --git a/posts/blog/编程技术/java/框架/Spring/Spring笔记/image49.png b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image49.png new file mode 100644 index 0000000..6744888 Binary files /dev/null and b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image49.png differ diff --git a/posts/blog/编程技术/java/框架/Spring/Spring笔记/image58.png b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image58.png new file mode 100644 index 0000000..5a00f86 Binary files /dev/null and b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image58.png differ diff --git a/posts/blog/编程技术/java/框架/Spring/Spring笔记/image66.png b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image66.png new file mode 100644 index 0000000..6a432fd Binary files /dev/null and b/posts/blog/编程技术/java/框架/Spring/Spring笔记/image66.png differ diff --git a/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记.md b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记.md new file mode 100644 index 0000000..e016dfb --- /dev/null +++ b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记.md @@ -0,0 +1,523 @@ +--- +title: SpringBoot笔记 +date: 2025-02-21 +tags: [SpringBoot] +--- + + +## 特性 + +- 快速创建 +- 直接嵌入Servlet容器(tomcat) +- 可选**starter**,简化整合 +- 按需**自动配置** + - 约定大于配置,导包后,很多默认配置 +- 提供**生产级特性** + - 监控指标、健康检查、外部化配置(简化运维) + - 在jar包同一文件夹下,编写配置,修改配置,重新启动jar包即可生效 + +## 核心 + +### 依赖管理 + +- 场景启动器 + +版本控制(常用框架,其他依赖依然需要声明) + +- 统一父项目(spring-boot-start-parent、spring-boot-dependencies) +- 依赖传递原则 +- 就近原则,修改版本号 + +### 自动装配机制 + +- 默认包扫描规则:扫描与application同一个文件夹下的包 + - 可以自定义扫描路径 +- 默认配置 + - 配置文件与类对象进行绑定 +- 按需加载自动配置流程 + - 导入依赖 + - 依赖中有`spring-boot-starter` ,是所有start中都有的 + - `spring-boot-starter` 中有`spring-boot-autoconfigure` ,覆盖了所有的**自动配置类**,按需自动配置 + +- 原理 + + 1、导入`start-web`依赖 + + - 场景启动器导入相关依赖:starter-json、mvc、starter-tomcat… + - 场景启动器中都有一个`spring-boot-starter` ,核心场景启动器 + - 核心场景启动器中有`spring-boot-autoconfigure` 里面有场景的配置,但是springboot扫描不到 + + 2、主程序 + + - `@SpringBootApplication` 的组成 + - `@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan` + - 自动装配的核心依赖: `@EnableAutoConfiguration` ,中的`@Import`注解会将autoconfigure包下的所有**自动配置类**导入 + - 虽然配置类都导入了,但结合**条件注解**、按需生效; + - 配置类结合`EnableConfigurationPropertieas` ,将配置文件中的配置,配到类对象中去 + + 3、写业务 + + +### 常用注解 + +**bean注册** + +- @Configuration、@SpringBootConfiguration(效果类似)、@Bean、@Scope +- @Controller、@Service、@Repository、@Component +- @Import + - 合并@Configuration、代替@Bean注册普通对象、 + +**条件注解** + +> 如果注解指定条件成立,则触发指定行为 + +`@ConditionalOnBean` :存在Bean + +`@ConditionalOnMissingBean` :不存在Bean + +`@ConditionalOnClass`:存在类 + +`@ConditionalOnMissingClass`:不存在类 + +场景:以下是方法级别,如果是类级别,那么只有满足条件,类才会生效 + +```Java +@ConditionalOnClass(name="com.xxx.类") +@Bean +public xx xxBean(){} +``` + +**属性绑定** + +- 将Bean注册到容器,编写配置文件,使用注解进行匹配 +- `@``**ConfigurationProperties**``(prefix = "前缀")` ,类、方法级别都可以 +- `@EnableConfigurationPropertieas(xxx.class)` ,导入第三方属性绑定的类 + + + +## 配置文件 + +### yaml + +类对应对象 + +![image 36.png](SpringBoot笔记/image36.png) + +### propertity + +![image 1 21.png](SpringBoot笔记/image121.png) + +## 日志 + +默认就有,spring-boot-starter中就有,slf4j+logback + +- 日志是利用监听器机制配置好的 + +![image 2 13.png](SpringBoot笔记/image213.png) + +日志级别 + +![image 3 12.png](SpringBoot笔记/image312.png) + +包级别 + +![image 4 11.png](SpringBoot笔记/image411.png) + +日志分组 + +![image 5 10.png](SpringBoot笔记/image510.png) + +日志写入文件 + +![image 6 8.png](SpringBoot笔记/image68.png) + +![image 7 5.png](SpringBoot笔记/image75.png) + +归档+切割 + +每天一个日志文件 + +日志文件过大切割 + +![image 8 5.png](SpringBoot笔记/image85.png) + +切换日志 + +直接引进logback-spring.xml + + + + + +自定义返回内容类型 + +- 默认协商(可修改) +- 请求中说明 +- Response中设置 +- ResponseEntity,自定义内容 + + + +## json格式处理 + +- 接收json格式数据,**@RequestBody**,**将json转为Java类型** + + ```Java + 123、'123'、[1,2,3]、{ "username":"root","password":"123"} + ``` + +- 当http响应内容为json格式时,(**@RestController**或@**ResponseBody**),==**返回对象自动转成json**====(除了数字和字符串类),字符串接收json字符串会保留“”== + +## Mybatis + +- 依赖,前提是jdbc的依赖已经导入了 + + ```XML + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 3.0.3 + + ``` + +- 在yml中配置加载xml文件、驼峰、日志打印 + + ```Plain + # mybatis + mybatis: + # 扫描mapper.xml + mapper-locations: classpath:mapper/*.xml + # 结果集类名简写 + type-aliases-package: com.qgs.testspringboot.entiry + configuration: + # 配置日志 + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + # 驼峰配置 + map-underscore-to-camel-case: true + ``` + +- 分页查询插件:pagehelper,页码、个数自定义,`PageInfo` 为结果 + + ```XML + + com.github.pagehelper + pagehelper-spring-boot-starter + 1.4.6 + + + org.mybatis + mybatis + + + + ``` + + +```Java +PageHelper.startPage(pageNum,pageSize); +List students = stuMapper.selectAll(); +PageInfo studentPageInfo = PageInfo.of(students); +``` + +- json数据如下 + + ```JavaScript + { + "code": "200", + "msg": "请求成功", + "data": { + "total": 7, + "list": [ + { + "sno": 1, + "name": "张三" + }, + { + "sno": 2, + "name": "李四" + } + ], + "pageNum": 1, + "pageSize": 2, + "size": 2, + "startRow": 1, + "endRow": 2, + "pages": 4, + "prePage": 0, + "nextPage": 2, + "isFirstPage": true, + "isLastPage": false, + "hasPreviousPage": false, + "hasNextPage": true, + "navigatePages": 8, + "navigatepageNums": [1, 2, 3, 4], + "navigateFirstPage": 1, + "navigateLastPage": 4 + } + } + ``` + + +## 跨域问题 + +理论上SpringBoot绑定的web容器不会有跨域问题,不管你的请求来自那里都会进行处理;问题产生原因是因为**浏览器,浏览器发现js发起的请求与当前请求不一致(不在一个域),则会发出,SpringBoot允许一下即可** + +- 原理 + + - js中的代码才会促发跨域,html中的资源请求不会触发 + - get请求是通过**响应结果**处理跨域 + - 其他请求是通过**预请求**处理跨域 + + + + 不完全正确,主要区别在于**简单请求(Simple Request)和预检请求(Preflight Request)**的处理方式。 + + 1. GET 请求(简单请求) + + - 服务器会接收到请求并处理。 + - 浏览器也会收到响应,但如果跨域且服务器未设置 Access-Control-Allow-Origin,浏览器会拦截数据,前端无法读取。 + - 也就是说,请求能发出,服务器能响应,但浏览器不给前端访问权限。 + + 示例: + + fetch('https://api.example.com/data') + + .then(response => response.json()) + + .then(data => console.log(data)) + + .catch(error => console.error(error)); // 如果跨域未被允许,这里会报错 + + 如果 https://api.example.com 没有返回 Access-Control-Allow-Origin,浏览器会拦截响应数据,前端无法访问 data,但服务器仍然处理了请求。 + + 2. PUT/POST 请求(预检请求) + + 如果请求不是简单请求(如 PUT、带自定义头部、或 Content-Type 不是 application/x-www-form-urlencoded),浏览器会先发送一个 OPTIONS 预检请求来询问服务器是否允许跨域。 + + - 如果服务器返回合适的 CORS 头(如 Access-Control-Allow-Origin、Access-Control-Allow-Methods),浏览器才会真正发送 PUT/POST 请求。 + - 如果服务器不返回 CORS 头,浏览器直接拦截,不会发出真正的 PUT/POST 请求。 + + 示例(跨域 PUT 请求): + + fetch('https://api.example.com/update', { + + method: 'PUT', + + headers: { 'Content-Type': 'application/json' }, + + body: JSON.stringify({ key: 'value' }) + + }); + + 实际过程: + + 1. 浏览器先发 OPTIONS 预检请求: + + OPTIONS /update HTTP/1.1 + + Origin: https://your-website.com + + Access-Control-Request-Method: PUT + + Access-Control-Request-Headers: Content-Type + + 2. 服务器如果允许跨域,会返回: + + HTTP/1.1 200 OK + + Access-Control-Allow-Origin: https://your-website.com + + Access-Control-Allow-Methods: PUT + + Access-Control-Allow-Headers: Content-Type + + 3. 只有服务器返回这些头,浏览器才会发真正的 PUT 请求。否则请求会被浏览器拦截,后端不会收到 PUT 请求。 + + 总结 + + | | | | | + |---|---|---|---| + |**请求类型**|**服务器会收到请求吗?**|**浏览器会拦截响应吗?**|**预检请求?**| + |GET(简单请求)|✅ 会收到|❌ 可能拦截(如果无 Access-Control-Allow-Origin)|❌ 无预检| + |POST(简单请求)|✅ 会收到|❌ 可能拦截(如果无 Access-Control-Allow-Origin)|❌ 无预检| + |POST(非简单请求,如带自定义头)|❌ 不会收到(如果预检失败)|-|✅ 有预检| + |PUT/DELETE|❌ 不会收到(如果预检失败)|-|✅ 有预检| + + 所以, + + - GET 请求即使跨域,服务器也会收到,浏览器只是拦截响应数据。 + - PUT/POST(非简单请求)如果跨域,浏览器会先发 OPTIONS 预检请求,服务器必须允许,才会继续发正式请求,否则服务器根本收不到请求。 + +- 注册CorsFilter + + ```Java + @Configuration + public class CorsConfig { + @Bean + public CorsFilter corsFilter(){ + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration corsConfiguration = new CorsConfiguration(); + corsConfiguration.addAllowedOrigin("*"); + corsConfiguration.addAllowedHeader("*"); + corsConfiguration.addAllowedMethod("*"); + source.registerCorsConfiguration("/**",corsConfiguration); + return new CorsFilter(source); + } + ``` + + + + +使用springTask + +- 查看spring + + + +整合spring security + +- 导入依赖 +- 编写配置类:跨域、csrf、密码加密等 +- 自定义用户处理服务 +- 自定义异常处理 +- 自定义用户验证方式(session、token) + + + + + +整合websocket + +- 导入依赖 +- 编写配置类 +- 编写服务端点,以及连接、断开、消息处理逻辑 + + + +## **过滤器、拦截器、AOP 的应用场景** + +--- + +**📌 三者定位对比** + +| | | | | | +|---|---|---|---|---| +|名称|归属|执行阶段|作用范围|适用场景| +|**Filter(过滤器)**|Servlet规范|请求进入 DispatcherServlet 之前|全局 Web 请求(包括静态资源、Servlet、Spring MVC 请求)|编码设置、跨域、日志记录、权限过滤| +|**Interceptor(拦截器)**|Spring MVC|DispatcherServlet 到 Controller 方法之间|仅 Spring MVC 控制器方法|登录校验、权限校验、日志记录、请求签名| +|**AOP**|Spring AOP|方法调用前后、异常抛出等|任意 Spring Bean 方法|日志、事务、性能监控、缓存、统一异常处理、埋点| + +--- + +**📌 执行顺序** + +一张表秒懂: + +```Plain +请求 -> Filter -> DispatcherServlet -> Interceptor preHandle -> Controller 方法 + ↓ + Interceptor postHandle -> View 渲染 + ↓ + Interceptor afterCompletion + ↓ + Filter 返回 +``` + +AOP 和 Interceptor 都是“方法执行级别”,但 AOP 可拦截任意 Spring Bean 方法,Interceptor 只拦截 Controller 方法。 + +--- + +**📌 场景详细拆分** + +📌 1️⃣ Filter 用在: + +- 请求编码统一设置 + + ```Java + request.setCharacterEncoding("UTF-8"); + ``` + +- CORS 跨域处理 +- 请求/响应日志记录(包含静态资源请求) +- 防止 XSS、SQL 注入统一过滤 +- 对所有请求(静态资源、动态接口)前置处理 +- 过滤敏感请求,直接返回 403 + +✅ **Filter 不依赖 Spring 容器,归 Servlet规范管理** + +--- + +📌 2️⃣ Interceptor 用在: + +- 登录拦截 +- 权限校验(基于 session/token) +- 请求签名校验 +- 接口耗时日志 +- 特定接口限流 + +✅ **拦截器可以拿到 Controller 方法、参数、返回值,且能控制请求是否继续往下走** + +通过 `preHandle`、`postHandle`、`afterCompletion` 方法分别控制: + +```Java +public boolean preHandle(...) // 决定是否放行 +public void postHandle(...) // 方法执行完,返回前 +public void afterCompletion(...) // 完整执行结束 +``` + +--- + +📌 3️⃣ AOP 用在: + +- 方法耗时监控(@Around) +- 统一异常捕捉(@AfterThrowing) +- 日志埋点(业务日志记录) +- 缓存(方法调用缓存结果) +- 事务管理(@Transactional 底层就是 AOP) +- 参数验证 +- 操作日志记录(记录方法调用参数、返回值) + +✅ **AOP 可以作用于任意 Spring Bean 方法,不限于 Controller** + +而且基于切点表达式,可以自由定义切面。 + +--- + +📌 实际开发常见应用 + +| | | | +|---|---|---| +|场景|推荐方式|原因| +|全站跨域|Filter|全局统一,兼容静态资源| +|登录 token 验证|Interceptor|可以拿到 Controller 方法,便于精确拦截| +|记录方法调用耗时|AOP|切 Controller 或 Service 方法,非侵入| +|全站编码设置|Filter|入口统一设置| +|统一异常捕获|AOP|可以切所有 Service、Controller| +|日志埋点|AOP|精准切入,便于统一日志规范| +|特定接口限流|Interceptor 或 AOP|Interceptor 对接口层限流,AOP 对方法限流| + +--- + +📌 总结核心定位: + +- **Filter**:Web请求入口层面,早期统一处理 +- **Interceptor**:Spring MVC 控制器方法执行前后拦截 +- **AOP**:任意 Spring Bean 方法调用时切入,做业务无关横切操作 + +--- + +📌 最佳实践 + +✅**Filter 做 web 级别统一过滤** + +✅**Interceptor 做接口级别认证/权限/限流** + +✅**AOP 做通用横切事务、日志、埋点、缓存** + +--- + +📌 一句话总结: + +> Filter 拦所有,Interceptor 拦接口,AOP 拦方法 + +--- \ No newline at end of file diff --git a/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image121.png b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image121.png new file mode 100644 index 0000000..212c075 Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image121.png differ diff --git a/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image213.png b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image213.png new file mode 100644 index 0000000..9bbdcef Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image213.png differ diff --git a/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image312.png b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image312.png new file mode 100644 index 0000000..0f7959f Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image312.png differ diff --git a/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image36.png b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image36.png new file mode 100644 index 0000000..e81c59b Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image36.png differ diff --git a/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image411.png b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image411.png new file mode 100644 index 0000000..120675f Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image411.png differ diff --git a/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image510.png b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image510.png new file mode 100644 index 0000000..e71480d Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image510.png differ diff --git a/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image68.png b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image68.png new file mode 100644 index 0000000..f922744 Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image68.png differ diff --git a/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image75.png b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image75.png new file mode 100644 index 0000000..768205d Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image75.png differ diff --git a/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image85.png b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image85.png new file mode 100644 index 0000000..4534e6a Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringBoot/SpringBoot笔记/image85.png differ diff --git a/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记.md b/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记.md new file mode 100644 index 0000000..7d891c7 --- /dev/null +++ b/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记.md @@ -0,0 +1,875 @@ +--- +title: SpringMVC笔记 +date: 2025-02-14 +tags: [SpringMVC] +--- + + +## 概述 + +- **简化javaWeb的开发流程**(封装Servlet),原先的JavaWeb开发需要定义太多的Servlet了,并且不够简洁 + +MVC详细解释如下: + +- M是指业务模型(Model):通俗的讲就是我们之前用于封装数据传递的实体类。 +- V是指用户界面(View):一般指的是前端页面。 +- C则是控制器(Controller):==控制器就相当于Servlet的基本功能==,处理请求,返回响应。 + +## 简单使用 + +### 基于web.xml的使用 + +- 开启Java EE(Jakarta EE)项目,创建一个javaWeb项目 + +- 引入springMVC的依赖 + + ```XML + + org.springframework + spring-webmvc + 6.0.10 + + ``` + +- 将`DispatcherServlet` 配置到web.xml中,并配置Spring的配置类位置 + + ```XML + + + mvc + org.springframework.web.servlet.DispatcherServlet + + contextClass + org.springframework.web.context.support.AnnotationConfigWebApplicationContext + + + contextConfigLocation + com.qgs.springmvc.config.Config + + + + + + mvc + / + + ``` + +- 编写Spring的配置类,并开启扫描Bean ,即可使用`@Controller` + + ```Java + @Controller + public class TestController { + @ResponseBody + @RequestMapping("/") + public String hello(){ + return "Hello World!"; + } + } + ``` + + +### 全注解的形式 + +- 由上面的内容可知,只要我们将`DispatcherServlet` 注册到Tomcat就基本可以脱离web.xml + +- 定义一个`Initializer` 类即可,spring配置类位置、注册`DispatcherServlet`、路径配置全部在这里完成 + - 原理:Tomcat会在类路径中查找实现`ServletContainerInitializer` 接口的类,如果发现的话,就用它来配置Servlet容器,Spring提供了这个接口的实现类 `SpringServletContainerInitializer` , 通过`@HandlesTypes(WebApplicationInitializer.class)`设置,这个类反过来会查找实现`WebApplicationInitializer` 的类,并将配置的任务交给他们来完成,因此直接实现接口即可 + +```Java +public class Initializer extends AbstractAnnotationConfigDispatcherServletInitializer { + + @Override + protected Class[] getRootConfigClasses() { + return new Class[]{Config.class}; //基本的Spring配置类,一般用于业务层配置 + } + + @Override + protected Class[] getServletConfigClasses() { + return new Class[0]; //配置DispatcherServlet的配置类、主要用于Controller等配置,这里为了教学简单,就不分这么详细了,只使用上面的基本配置类 + } + + @Override + protected String[] getServletMappings() { + return new String[]{"/"}; //匹配路径,与上面一致 + } +} +``` + +- spring配置类需要加上`@EnableWebMvc` 注解,并且需要配置使用默认的Servlet访问静态资源,实现`WebMvcConfigurer` 接口 + + ```Java + @EnableWebMvc + @Configuration + @ComponentScan("com.qgs.*") + public class Config implements WebMvcConfigurer { + @Override + public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { + configurer.enable(); //开启默认的Servlet + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/static/**").addResourceLocations("/static/"); + //配置静态资源的访问路径 + } + } + ``` + + +> [!important] **配置一个初始化**`**Initializer**`**类,一个spring配置类并实现**`**WebMvcConfigurer**` **接口** + +## Controller介绍 + +![image 35.png](SpringMVC笔记/image35.png) + +- `DispatcherServlet`来处理所有的请求,也就是说它被作为一个统一的访问点,所有的请求全部由它来进行调度。 +- 我们只需要关心核心的Controller部分,SpringMVC简化了接收请求、数据形式、以及视图层的解析;==返回值默认使用视图解析器==,前后端分离不需要关心视图解析,一般配置注解进行修改 + + ![image 1 20.png](SpringMVC笔记/image120.png) + +- 详解 + + 当一个请求经过`DispatcherServlet`之后,会先走`HandlerMapping`,它会将请求映射为`HandlerExecutionChain`,依次经过`HandlerInterceptor`有点类似于之前我们所学的过滤器,不过在SpringMVC中我们使用的是拦截器,然后再交给`HandlerAdapter`,根据请求的路径选择合适的控制器进行处理,控制器处理完成之后,会返回一个`ModelAndView`对象,包括数据模型和视图,通俗的讲就是页面中数据和页面本身(只包含视图名称即可)。 + + 返回`ModelAndView`之后,会交给`ViewResolver`(视图解析器)进行处理,视图解析器会对整个视图页面进行解析,SpringMVC自带了一些视图解析器,但是只适用于JSP页面,我们也可以像之前一样使用Thymeleaf作为视图解析器,这样我们就可以根据给定的视图名称,直接读取HTML编写的页面,解析为一个真正的View。 + + 解析完成后,就需要将页面中的数据全部渲染到View中,最后返回给`DispatcherServlet`一个包含所有数据的成形页面,再响应给浏览器,完成整个过程。 + + 因此,实际上整个过程我们只需要编写对应请求路径的的Controller以及配置好我们需要的ViewResolver即可,之后还可以继续补充添加拦截器,而其他的流程已经由SpringMVC帮助我们完成了。 + + +## 参数解析 + +注解表示自己要什么 + +值从severlet拿 + +- 具体 + + 不完全是。Spring MVC Controller 方法的参数值并 不是 直接从 IoC 容器中获取的,而是基于 HandlerMethodArgumentResolver 解析 请求数据 或 容器中的 Bean 来填充参数。 + + 详细解析 + + 1. 参数类型的获取 + + - Spring MVC 使用 反射 获取 Controller 方法的参数类型。 + - 通过 MethodParameter 解析 Controller 方法的参数信息。 + + 2. 参数值的解析 + + - Spring MVC 通过 HandlerMethodArgumentResolver 解析参数值。 + - 具体参数值的来源: + - HTTP 请求数据 + - @RequestParam → 从 request.getParameter() 获取 + - @RequestBody → 解析 request body 数据(JSON、XML) + - @PathVariable → 解析 URL 路径变量 + - @RequestHeader → 获取请求头信息 + - @CookieValue → 获取 Cookie + - Servlet API + - HttpServletRequest、HttpSession → 直接注入 Servlet 相关对象 + - Spring IoC 容器 + - @Autowired、@Qualifier → 从 IoC 容器获取 Bean + - @ModelAttribute → 先从 Model 获取,找不到时尝试反射实例化 + - 默认值 & 自定义解析 + - DefaultDataBinder 进行数据绑定,将字符串转换成目标类型 + - 可通过 @InitBinder 自定义参数解析方式 + + 总结 + + - 参数类型 → 通过 反射 (MethodParameter) 获取 + - 参数值 → 通过 HandlerMethodArgumentResolver 解析,数据主要来源于: + + 1. HTTP 请求参数 + + 2. Servlet API 对象 + + 3. Spring IoC 容器中的 Bean + + 4. 数据绑定 & 默认值 + + + + 如果 Controller 方法的形参 没有任何注解,Spring MVC 依然会尝试解析它们,具体方式取决于参数的类型: + + 1. 没有注解的基本类型 + + 处理方式: + + - 默认按 @RequestParam 处理,即从 request.getParameter() 获取值。 + - 依赖 RequestParamMethodArgumentResolver 解析。 + - 如果请求中没有该参数: + - 基本类型(如 int、boolean)会抛 400 Bad Request 错误,因为它们不能为 null。 + - 包装类型(如 Integer、Boolean)默认为 null,不会报错。 + + 示例 + + @GetMapping("/test") + + public String test(int age, Boolean active) { + + return "Age: " + age + ", Active: " + active; + + } + + 请求 + + GET /test?age=25 + + 解析过程 + + 1. Spring 解析 int age,等价于 @RequestParam int age,从 request.getParameter("age") 获取值 25。 + + 2. Boolean active 因为没有 @RequestParam,如果请求没有 active 参数,则默认为 null。 + + 3. 绑定参数,调用方法。 + + 请求 GET /test(缺少 age) + + - int age 没有默认值,会抛 400 Bad Request(age 不能为 null)。 + - 解决方案:改为 Integer age,让它可以为 null。 + + 2. 没有注解的自定义对象 + + 处理方式: + + - Spring 默认按 @ModelAttribute 处理,即尝试创建对象,并使用 请求参数(query/form)填充字段。 + - 依赖 ServletModelAttributeMethodProcessor 解析。 + - 不会从 @RequestBody 解析 JSON,如果是 application/json 请求,Spring 不会自动绑定,需手动加 @RequestBody。 + + 示例 + + public class User { + + private String name; + + private int age; + + // 必须有无参构造 + + public User() {} + + // getter & setter 省略 + + } + + @PostMapping("/user") + + public String createUser(User user) { + + return "User: " + user.getName() + ", Age: " + user.getAge(); + + } + + 请求 + + POST /user?name=Tom&age=30 + + 解析过程 + + 1. Spring 看到 User 类型的参数,等价于 @ModelAttribute User user。 + + 2. 通过 反射 创建 User 实例。 + + 3. 使用 WebDataBinder 解析 request.getParameter() 并填充 user 字段。 + + 4. 调用 Controller 方法。 + + 3. 纯 JSON 请求时,默认不会绑定对象 + + 如果 Content-Type: application/json,但参数没有 @RequestBody,Spring MVC 不会解析 JSON,会报 400 错误。 + + 错误示例 + + @PostMapping("/user") + + public String createUser(User user) {  // ⚠ 没有 @RequestBody + + return "User: " + user.getName(); + + } + + 请求 + + POST /user + + Content-Type: application/json + + { + + "name": "Alice", + + "age": 22 + + } + + 结果 + + 400 Bad Request + + 解决方案 + + @PostMapping("/user") + + public String createUser(@RequestBody User user) {  // ✅ 加上 @RequestBody + + return "User: " + user.getName(); + + } + + 总结 + + | | | | | + |---|---|---|---| + |**形参类型**|**无注解时的默认解析方式**|**依赖解析器**|**数据来源**| + |基本类型 (int, boolean 等)|解析为 @RequestParam|RequestParamMethodArgumentResolver|request.getParameter()| + |包装类型 (Integer, Boolean 等)|解析为 @RequestParam,可以为 null|RequestParamMethodArgumentResolver|request.getParameter()| + |自定义对象 (User user)|解析为 @ModelAttribute|ServletModelAttributeMethodProcessor|request.getParameter()| + |JSON 请求中的自定义对象|不会自动解析,必须手动加 @RequestBody|RequestResponseBodyMethodProcessor|HttpMessageConverter (Jackson)| + + 结论 + + - 基本类型默认按 @RequestParam 处理,必须提供值,否则报错。 + - 自定义对象默认按 @ModelAttribute 处理,从表单或 URL 参数填充,不支持 JSON。 + - 处理 JSON 时,必须显式加 @RequestBody。 + + + + +> [!important] Spring MVC的参数自动绑定是通过**反射 + 策略模式 + 参数解析器**实现的,这是一种更直接的机制 + +### 常用注解 + +`**@Controller**` + +- 核心注解,表明是一个controller + +`**@ResponseBody**` :返回值不使用视图解析器,而是以响应体的形式返回 + +`**@RestController**` **:如果一个Controller都不使用视图解析器,则直接使用该注解,相当于前2个相加** + +`**@RequestMapping("/")**` + +- 请求路径的映射,值可以是一个数组,设置通配符 + - ?:表示任意一个字符,比如`@RequestMapping("/index/x?")`可以匹配/index/xa、/index/xb等等。 + - :表示任意0-n个字符,比如`@RequestMapping("/index/*")`可以匹配/index/lbwnb、/index/yyds等。 + - *:表示当前目录或基于当前目录的多级目录,比如`@RequestMapping("/index/**")`可以匹配/index、/index/xxx等。 +- 可以作用于==类、方法==上,可以通过Method设置请求类型、设置Params设置参数,还可以设置限制其他请求信息 + + ```Java + @RequestMapping(value = "/index", method = RequestMethod.POST) + @RequestMapping(value = "/index", params = {"username", "password"}) + ``` + + +**不设置注解** + +- 单个参数一般不用设置,==对象可以自动解析并绑定,并且不需要将Bean进行注册== + +![image 2 12.png](SpringMVC笔记/image212.png) + +`**@RequestParam("name")String name)**` + +- 处理键值对数据,表单(文件传输)、url等大部分场景 +- post、get都行 + +`**@RequestHeader("token")String token**` **:**获取token + +`**@CookieValue(value = "test", required = false)**`**:**获取cookie + +`**@SessionAttribute(value = "test", required = false) String test**`:获取session + +`**@RequestBody**` + +- 解析json数据 + +`@GetMapping("/json/{str}")` +`@``**PathVariable**` `String str` + +- 解析请求参数 + +||get请求|post请求| +|---|---|---| +|普通参数|`**@RequestParam**`|`**@RequestParam**`| +|请求体参数(json、表单)|GET请求通常没有请求体|`**@RequestParam**`| + +## 重定向和请求转发 + +**重定向** + +- 直接返回`redirect:地址` 字符串即可 + + ```Java + @RequestMapping("/index") + public String index(){ + return "redirect:home"; + } + ``` + + +**请求转发** + +- 直接返回`forward:地址` 字符串即可 + + ```Java + @RequestMapping("/index") + public String index(){ + return "forward:home"; + } + + @RequestMapping("/home") + public String home(){ + return "home"; + } + ``` + + +## Bean的Web作用域 + +- **request**:对于每次HTTP请求,使用request作用域定义的Bean都将产生一个新实例,请求结束后Bean也消失。 +- **session**:对于每一个会话,使用session作用域定义的Bean都将产生一个新实例,会话过期后Bean也消失。 + - Bean上使用`**@SessionScope**` 标注,在某个Controller中使用,那么这个浏览器放的所有请求(同一会话),都是同一个Bean + + ```Java + @Data + @SessionScope + public class User { + private String name; + private String password; + } + + //相同Session下的Bean是一样的 + @Controller + public class TestController { + + @Autowired + User user; + + @ResponseBody + @GetMapping("/test") + public String getUser( ){ + return user.toString(); + } + } + ``` + +- **global session**:不常用,不做讲解。 + +## **RestFul风格** + +- 允许将参数通过URL拼接传到服务端,目的是让URL看起来更简洁实用 + + ```Java + //普通形式 + http://localhost:8080/mvc?index=123456 + //RestFul风格 + http://localhost:8080/mvc/index/123456 + ``` + +- 解析参数使用`@PathVariable` + + - 参数名与占位符一致不需要设置注解值,不一致则将注解值设置为占位符,形参不变 + + ```Java + @GetMapping("/get/{str}") + public String getStr(@PathVariable String str){} + ``` + +- 我们可以按照不同功能进行划分: + - POST [http://localhost:8080/mvc/index](http://localhost:8080/mvc/index) - 添加用户信息,携带表单数据 + - GET [http://localhost:8080/mvc/index/{id}](http://localhost:8080/mvc/index/%7Bid%7D) - 获取用户信息,id直接放在请求路径中 + - PUT [http://localhost:8080/mvc/index](http://localhost:8080/mvc/index) - 修改用户信息,携带表单数据 + - DELETE [http://localhost:8080/mvc/index/{id}](http://localhost:8080/mvc/index/%7Bid%7D) - 删除用户信息,id直接放在请求路径中 + +## **Interceptor拦截器** + +- 类似于JavaWeb的过滤器(Fitter),拦截器是在Servlet之后起作用,执行流程是一致的 + + ![image 3 11.png](SpringMVC笔记/image311.png) + + +**创建拦截器** + +- 创建拦截器类实现拦截器接口`HandlerInterceptor` + + ```Java + public class TestInterceptor implements HandlerInterceptor { + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + System.out.println("controller前..."); + return true; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { + System.out.println("controller后..."); + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + System.out.println("请求完成后"); + } + } + ``` + +- 在配置类中配置拦截器,并且设置路径 + + ```Java + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new TestInterceptor()) + .addPathPatterns("/**") + .excludePathPatterns("/test"); + } + ``` + +- 如果在controller中发生异常,拦截器的`postHandle`方法不会执行,但是`afterCompletion`会执行,并且可以拿到异常(方便关闭资源) + +**多级拦截器** + +- 默认按类名顺序,控制拦截器的顺序,通过order方法,数字小的在前面 + + ```Java + registry.addInterceptor(new TestInterceptor()) + .addPathPatterns("/**") + .order(1); + ``` + + +## 异常处理 + +- 异常处理Controller,使用`@ControllerAdvice` 标注类,`@ExceptionHandler(Exception.class)` 设置异常类型,自定义返回内容 + + ```Java + //作用于所有controller的异常处理、数据绑定、模型属性 + @ControllerAdvice + public class ErrorController { + + @ResponseBody + @ExceptionHandler(Exception.class) + public String error(Exception e){ + e.printStackTrace(); + return "500"; + } + } + ``` + + +## JSON数据格式 + +- 前后端分离,后端提供数据,前端渲染数据,通常使用json格式 + +- 数据转json常用框架,比较常用的是Jackson和FastJSON,导入依赖 + + ```XML + + com.alibaba.fastjson2 + fastjson2 + 2.0.34 + + ``` + +- FastJSON基本使用,`JSONObject`类似于`map`用法,数组使用`JSONArray`, 对象转json,`JSONObject.`_`toJSONString`_`(new User())` + + ```Java + //添加响应头 + @GetMapping(value = "/test/{str}",produces = "application/json") + public String getJson(@PathVariable("str") String str1){ + JSONObject jsonObject=new JSONObject(); + jsonObject.put("key",str1); + return jsonObject.toString(); + } + ``` + +- 也可以直接返回对象(自动转json),配置一个json框架即可 + - 导入依赖 + + ```XML + + com.alibaba.fastjson2 + fastjson2-extension-spring6 + 2.0.34 + + ``` + + - 配置转换器 + + ```Java + @Override + public void configureMessageConverters(List> converters) { + converters.add(new FastJsonHttpMessageConverter()); + } + ``` + +- 接收json格式数据`@RequestBody`接收 + +## 文件上传和下载 + +- initial中配置Multipart + + ```Java + @Override + protected void customizeRegistration(ServletRegistration.Dynamic registration) { + // 直接通过registration配置Multipart相关配置,必须配置临时上传路径,建议选择方便打开的 + // 同样可以设置其他属性:maxFileSize, maxRequestSize, fileSizeThreshold + registration.setMultipartConfig(new MultipartConfigElement("E:\\桌面\\刀")); + } + ``` + + +**上传** + +- 请求体的参数`@RequestParam("file")` ,形参使用`MultipartFile` ,使用transferTo方法将文件保留 + + ```Java + @PostMapping("/upload") + public String upload(@RequestParam("file") MultipartFile multipartFile) throws IOException { + File file = new File("test.png"); + multipartFile.transferTo(file); + return "文件上传成功!"; + } + ``` + + +**下载** + +- 直接向HttpResponse返回数据即可 +- 与javaWeb差不多 +- 返回值为void即可 + + + + + + + +好!我来给你写一套**简化版手写 SpringMVC 框架**,只保留核心实现: + +- 自定义 DispatcherServlet +- HandlerMapping (URL→方法映射表) +- 参数绑定 +- 视图解析 (返回 JSON 或 String) + +完整代码,全是**可直接跑的 Servlet 版**👇 + +--- + +## 📦 项目结构: + +```Plain +com.qgs.mvc +├── DispatcherServlet.java // 前端控制器 +├── HandlerMapping.java // URL→方法映射 +├── ControllerScanner.java // 扫描映射关系 +├── DemoController.java // 示例 Controller +└── web.xml // Servlet 配置 +``` + +--- + +## 📌 1️⃣ DispatcherServlet.java + +```Java +package com.qgs.mvc; + +import javax.servlet.*; +import javax.servlet.http.*; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.util.Map; + +public class DispatcherServlet extends HttpServlet { + + @Override + public void init() { + // 启动时扫描所有映射 + ControllerScanner.initMappings(); + } + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException { + String uri = req.getRequestURI(); + Method method = HandlerMapping.getMethod(uri); + + if (method == null) { + resp.setStatus(404); + resp.getWriter().write("404 Not Found"); + return; + } + + try { + // 参数绑定,这里简单只取字符串参数 + Class[] paramTypes = method.getParameterTypes(); + Object[] args = new Object[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { + String param = req.getParameter("param" + i); + args[i] = param; + } + + // 调用方法 + Object controller = method.getDeclaringClass().newInstance(); + Object result = method.invoke(controller, args); + + // 视图解析 + PrintWriter out = resp.getWriter(); + if (result instanceof String) { + out.write((String) result); + } else { + out.write("返回值:" + result.toString()); + } + } catch (Exception e) { + e.printStackTrace(); + resp.setStatus(500); + resp.getWriter().write("500 Internal Error"); + } + } +} +``` + +--- + +## 📌 2️⃣ HandlerMapping.java + +```Java +package com.qgs.mvc; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +public class HandlerMapping { + private static final Map mappings = new HashMap<>(); + + public static void register(String uri, Method method) { + mappings.put(uri, method); + } + + public static Method getMethod(String uri) { + return mappings.get(uri); + } +} +``` + +--- + +## 📌 3️⃣ ControllerScanner.java + +```Java +package com.qgs.mvc; + +import java.lang.reflect.Method; + +public class ControllerScanner { + + public static void initMappings() { + try { + // 模拟扫描 Controller(这里只扫描 DemoController) + Class controllerClass = Class.forName("com.qgs.mvc.DemoController"); + + for (Method method : controllerClass.getDeclaredMethods()) { + if (method.isAnnotationPresent(RequestMapping.class)) { + String path = method.getAnnotation(RequestMapping.class).value(); + HandlerMapping.register(path, method); + System.out.println("映射:" + path + " -> " + method); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} +``` + +--- + +## 📌 4️⃣ 自定义 @RequestMapping 注解 + +```Java +package com.qgs.mvc; + +import java.lang.annotation.*; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequestMapping { + String value(); +} +``` + +--- + +## 📌 5️⃣ DemoController.java + +```Java +package com.qgs.mvc; + +public class DemoController { + + @RequestMapping("/hello") + public String hello(String param0) { + return "你好," + param0; + } +} +``` + +--- + +## 📌 6️⃣ web.xml 配置 + +```XML + + + dispatcher + com.qgs.mvc.DispatcherServlet + + + + dispatcher + / + + +``` + +--- + +## 📌 📖 核心流程: + +1️⃣ 项目启动 → DispatcherServlet 调用 `ControllerScanner.initMappings()` 扫描映射 + +2️⃣ 请求 `/hello?param0=张三` → DispatcherServlet 拿到 Method + +3️⃣ 参数绑定(简单版) + +4️⃣ 反射调用 Controller 方法 + +5️⃣ 写回响应 + +--- + +## ✅ 你现在访问: + +```Plain +http://localhost:8080/hello?param0=张三 +``` + +👉 返回: + +```Plain +你好,张三 +``` + +--- + +## 📖 总结: + +这就是**手写 SpringMVC 核心原理**: + +- DispatcherServlet(前端控制器) +- HandlerMapping(URL→Method映射) +- 参数绑定(简单参数) +- 视图解析(写 response) + +--- + +要不要我再帮你扩展一版**带 ThreadLocal 用户上下文 + 拦截器链**的手写版?👌 \ No newline at end of file diff --git a/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记/image120.png b/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记/image120.png new file mode 100644 index 0000000..409c1ac Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记/image120.png differ diff --git a/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记/image212.png b/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记/image212.png new file mode 100644 index 0000000..3a29132 Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记/image212.png differ diff --git a/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记/image311.png b/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记/image311.png new file mode 100644 index 0000000..b4f1b2b Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记/image311.png differ diff --git a/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记/image35.png b/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记/image35.png new file mode 100644 index 0000000..5999534 Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringMVC/SpringMVC笔记/image35.png differ diff --git a/posts/blog/编程技术/java/框架/SpringSecurity/SpringSecurity笔记.md b/posts/blog/编程技术/java/框架/SpringSecurity/SpringSecurity笔记.md new file mode 100644 index 0000000..10b2f52 --- /dev/null +++ b/posts/blog/编程技术/java/框架/SpringSecurity/SpringSecurity笔记.md @@ -0,0 +1,735 @@ +--- +title: SpringSecurity笔记 +date: 2025-02-22 +tags: [SpringSecurity] +--- + + +## 概述 + +- 基于Spring开发的权限验证框架 + - 认证(用户登录) + - 授权(权限管理) + - 攻击防护(防止伪造身份攻击) + +## 网络安全基础 + +### CSRF跨站请求伪造攻击 + +- 钓鱼网站,表单提交的地址是正常的接口地址,点击提交,则可通过你的cookie或localhost的登录凭证进行操作 +- 目前浏览器也会自动保护,SameSite保护机制,==不同域名的站点操作,默认情况下cookie会被屏蔽掉== +- 也可以通过SpringSecurity进行防御 + +### SFA会话固定攻击 + +- 点击伪官网,通过js将sessionid写入到你的浏览器,然后直接跳转到正常官网,此时你的sessionid与黑客关系,当年进行登录,则它的sessionid也进行放行 +- ==浏览器设置了HTTPOnly选项,无法修改session== +- 登录成功,使用新的sessionid即可 + +### XSS跨站脚本攻击 + +- 在富文本区域插入js脚本,类似SQL注入 + +## 基本使用 + +- 在SpringMVC基础上 + +- 导入依赖,导入2个依赖 + + ```XML + + org.springframework.security + spring-security-web + 6.1.1 + + + org.springframework.security + spring-security-config + 6.1.1 + + ``` + +- 配置initial类和mvc类似,在Spring的配置添加`@EnableWebSecurity` 注解 + + ```Java + public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer { + //不用重写任何内容 + //这里实际上会自动注册一个Filter,SpringSecurity底层就是依靠N个过滤器实现的,我们之后再探讨 + } + ``` + +- 打开任何界面要登录,则配置成功 + + ![image 37.png](SpringSecurity笔记/image37.png) + + +> [!important] 开启security,自动拦截所有post请求,报403,防止CSRF,一般不需要,需要手动关闭 + +## 认证 + +- 也就是注册放行账号(用户管理) + +### 基于内存 + +- 在配置类中注册`**UserDetailsService**` 的Bean,并手动注册账号到`InMemoryUserDetailsManager` 对象中 + + ```Java + //配置密码转换器,对密码进行加密 + @Bean + public PasswordEncoder passwordEncoder(){ + return new BCryptPasswordEncoder(); + } + + @Bean + public UserDetailsService userDetailsService(PasswordEncoder encoder){ + // 基于内存,创建多个角色 + UserDetails user = User + .withUsername("user") + .password(encoder.encode("123")) + .roles("USER") //角色目前我们不需要关心,随便写就行,后面会专门讲解 + .build(); + UserDetails admin = User + .withUsername("admin") + .password(encoder.encode("123")) + .roles("ADMIN", "USER") + .build(); + + return new InMemoryUserDetailsManager(user, admin); + } + ``` + + +### 基于数据库 + +- spring整合mybatis的依赖, + +- 在配置类中注册数据源,并创建(固定的)数据库表 + + ```Java + @Bean + public DataSource dataSource(){ + //数据源配置 + return new PooledDataSource("com.mysql.cj.jdbc.Driver", + "jdbc:mysql://localhost:3306/java", "root", "qgs"); + } + ``` + + ```SQL + create table users(username varchar(50) not null primary key,password varchar(500) not null,enabled boolean not null); + create table authorities (username varchar(50) not null,authority varchar(50) not null,constraint fk_authorities_users foreign key(username) references users(username)); + create unique index ix_auth_username on authorities (username,authority); + ``` + +- 在`**UserDetailsService**` 类中创建账号,传给`JdbcUserDetailsManager` + + ```Java + @Bean + public UserDetailsService userDetailsService(PasswordEncoder encoder,DataSource dataSource){ + // 基于内存,创建多个角色 + JdbcUserDetailsManager manager=new JdbcUserDetailsManager(dataSource); + manager.createUser(User + .withUsername("user") + .password(encoder.encode("123")) + .roles("USER") //角色目前我们不需要关心,随便写就行,后面会专门讲解 + .build()); + // 基于数据库 + return manager; + } + ``` + + +**创建账号** + +- 使用User,创建账号,传给不同的manger即可 + +```Java + UserDetails user = User + .withUsername("user") + .password(encoder.encode("123")) + .roles("USER") //角色目前我们不需要关心,随便写就行,后面会专门讲解 + .build(); +``` + +> [!important] 以上2种方式,自定义程度不够 + +### 自定义认证 + +- 从上面2种实现发现,他们都是实现了`UserDetailsManager` ,所有我们的思路是自己实现该接口,或则实现`UserDetailsService`(父接口)也可以,只需要通过`loadUserByUsername` 方法返回正确的UserDetails即可 + + ```Java + @Service + public class UserService implements UserDetailsService { + @Autowired + UserMapper userMapper; + + // 返回UserDetails对象,用于与提交内容比较 + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Account account=userMapper.select(username); + if(account==null) throw new UsernameNotFoundException("账号不存在!"); + return User + .withUsername(username) + .password(account.getPassword()) + .roles("USER") //角色目前我们不需要关心,随便写就行,后面会专门讲解 + .build(); + } + } + ``` + + +### 其他配置(前端界面) + +**自定义登陆界面** + +- 设置登陆界面、放行接口、静态资源、关闭csrf的token + + ```Java + ... + + //如果你学习过SpringSecurity 5.X版本,可能会发现新版本的配置方式完全不一样 + //新版本全部采用lambda形式进行配置,无法再使用之前的and()方法进行连接了 + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http + //以下是验证请求拦截和放行配置 + .authorizeHttpRequests(auth -> { + auth.anyRequest().authenticated(); //将所有请求全部拦截,一律需要验证 + }) + //以下是表单登录相关配置 + .formLogin(conf -> { + conf.loginPage("/login"); //将登录页设置为我们自己的登录页面 + conf.loginProcessingUrl("/doLogin"); //登录表单提交的地址,可以自定义 + conf.defaultSuccessUrl("/"); //登录成功后跳转的页面 + conf.permitAll(); //将登录相关的地址放行,否则未登录的用户连登录界面都进不去 + //用户名和密码的表单字段名称,不过默认就是这个,可以不配置,除非有特殊需求 + conf.usernameParameter("username"); + conf.passwordParameter("password"); + }) + .build(); + + //以下是退出登录相关配置 + .logout(conf -> { + conf.logoutUrl("/doLogout"); //退出登录地址,跟上面一样可自定义 + conf.logoutSuccessUrl("/login"); //退出登录成功后跳转的地址,这里设置为登录界面 + conf.permitAll(); + }) + .build(); + //csrf配置 + .csrf(conf -> { + conf.disable(); //此方法可以直接关闭全部的csrf校验,一步到位 + conf.ignoringRequestMatchers("/xxx/**"); //此方法可以根据情况忽略某些地址的csrf校验 + }) + .build(); + } + + ``` + + ```Java + + ``` + + +**记住我功能** + +- 配置后,使用SpringSecurity的登录token(cookie形式),默认保存14太 + +- 继续在配置类中设置 + + ```Java + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http + ... + .rememberMe(conf -> { + conf.alwaysRemember(false); //这里不要开启始终记住,我们需要配置为用户自行勾选 + conf.rememberMeParameter("remember-me"); //记住我表单字段,默认就是这个,可以不配置 + conf.rememberMeCookieName("xxxx"); //记住我设置的Cookie名字,也可以自定义,不过没必要 + }) + .build(); + } + ``` + +- 防止服务器中途关闭,也可以将信息存到数据库中 + + ```Java + @Bean + public PersistentTokenRepository tokenRepository(DataSource dataSource){ + JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl(); + //在启动时自动在数据库中创建存储记住我信息的表,仅第一次需要,后续不需要 + repository.setCreateTableOnStartup(true); + repository.setDataSource(dataSource); + return repository; + } + ``` + + ```Java + .rememberMe(conf -> { + conf.rememberMeParameter("remember-me"); + conf.tokenRepository(repository); //设置刚刚的记住我持久化存储库 + conf.tokenValiditySeconds(3600 * 7); //设置记住我有效时间为7天 + }) + ``` + + +## 授权 + +### 基于角色 + +### 基于权限 + +### 使用注解 + +- 开启注解权限,配置类设置`@EnableMethodSecurity` +- 在请求方法上添加权限注解,`@PostAuthorize("hasRole('user')")` + - 在`PostAuthorize`中使用springEL表达式进行设置 + - 还有PreAuthorize + +> [!important] 除了controller,只要是Spring管理的Bean都可以使用注解进行管理 + + + +## 整合Spring Boot + +### 基本配置 + +- 代码 + + ```Shell + package com.qgs.auth.cofig.security; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.authentication.AuthenticationManager; + import org.springframework.security.authentication.ProviderManager; + import org.springframework.security.authentication.dao.DaoAuthenticationProvider; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; + import org.springframework.security.config.http.SessionCreationPolicy; + import org.springframework.security.core.userdetails.UserDetailsService; + import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + import org.springframework.security.crypto.password.PasswordEncoder; + import org.springframework.security.web.SecurityFilterChain; + import org.springframework.web.cors.CorsConfiguration; + import org.springframework.web.cors.CorsConfigurationSource; + import org.springframework.web.cors.UrlBasedCorsConfigurationSource;; + + @Configuration + @EnableWebSecurity + public class SpringSecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .cors(cors -> cors.configurationSource(corsConfigurationSource())) // 使用自定义 CORS 配置 + .csrf(AbstractHttpConfigurer::disable) // 禁用 CSRF + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)) // 允许 Session + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/user/login").permitAll() // 放行登录接口 + .anyRequest().authenticated() + ); + return http.build(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.addAllowedOrigin("http://localhost:8081"); // 指定前端来源 + configuration.addAllowedMethod("*"); // 允许所有 HTTP 方法(GET, POST, OPTIONS 等) + configuration.addAllowedHeader("*"); // 允许所有请求头 + configuration.setAllowCredentials(true); // 支持凭证(如 Cookie) + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); // 应用到所有路径 + return source; + } + + //密码加密 + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + //自定义service配置到认证管理器中 + @Bean + public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(passwordEncoder); + return new ProviderManager(authProvider); + } + } + + + ``` + +- 配置过滤请求、跨域、csrf禁用 +- **选择维持状态的方式:Session、jwt** +- 配置userDetail、userDetailService + + - 用户对象、验证账号请求 + + - 代码 + + ```Shell + @Data + public class SysUser implements UserDetails { + private Long userId; + private String userName; + private String password; + private Boolean isEnabled; + private Date createTime; + private Date updateTime; + // security必须的 + private boolean isAccountNonExpired; + private boolean isAccountNonLocked; + private boolean isCredentialsNonExpired; + Collection authorities; + + + //角色id集合 + private List roleIds; + + @Override + public String getUsername() { + return userName; + } + + @Override + public boolean isAccountNonExpired() { + return isAccountNonExpired; + } + + @Override + public boolean isAccountNonLocked() { + return isAccountNonLocked; + } + + @Override + public boolean isCredentialsNonExpired() { + return isCredentialsNonExpired; + } + + @Override + public boolean isEnabled() { + return isEnabled; + } + } + + @Component + public class UserDetailsServiceImpl implements UserDetailsService { + @Autowired + SysUserService sysUserService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + SysUser sysUser = sysUserService.getUserByName(username); + if(sysUser==null) throw new UsernameNotFoundException("账号不存在!"); + return sysUser; + } + } + + ``` + +- 在controller中使用 + +### 通过session + +接管controller中的登录接口,适合前后端不分离的项目,要点使用security的登录service,并保存 + +- 代码 + + ```Shell + @PostMapping("/login") + public Result login(@RequestBody LoginInfo user, HttpServletRequest request) { + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword()); + Authentication authenticate = authenticationManager.authenticate(authenticationToken); + + // **存入 SecurityContext** + SecurityContextHolder.getContext().setAuthentication(authenticate); + + // **关键:存入 Session** + HttpSession session = request.getSession(true); + session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext()); + + SysUser sysUser = (SysUser) authenticate.getPrincipal(); + + Map map=new HashMap<>(); + map.put("userId",Long.toString(sysUser.getUserId())); + //这里的jwt仅用于前端 + String token = jwtUtils.getToken(map); + return Result.success(new LoginVo() + .setUserId(sysUser.getUserId()) + .setUserName(user.getUserName()) + .setToken(token) + ,"登陆成功"); + } + ``` + +- 前端请求开启cookie + + ```JavaScript + const config = { + baseURL: 'http://localhost:8964', + timeout: 10000, + withCredentials: true // 启用 Cookie 的发送和接收 + } + ``` + + +> [!important] 由于session依赖于cookie,所以前端axious需要开启cookie允许 + +### 通过jwt + +- 开启过滤器,验证jwt,将通过验证的用户,保存到上下文 + +- 代码 + + ```Java + //验证token 接口作用,每个请求只会执行一次 过滤操作 + @Data + @Component + public class CheckTokenFilter extends OncePerRequestFilter { + //url为空,则赋值对象 + @Value("#{'${ignore.url}'.split(',')}") + private List urlList = Collections.emptyList(); + @Autowired + private JWTUtils jwtUtils; + + @Autowired + private UserDetailsServiceImpl userDetailsService; + @Autowired + private AuthenticationEntryPoint authenticationEntryPoint; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + try { + String uri = request.getRequestURI(); + if (!urlList.contains(uri)){ + checkToken(request); + } + filterChain.doFilter(request,response); + }catch (AuthenticationException e){ + authenticationEntryPoint.commence(request,response,e); + } + } + + protected void checkToken(HttpServletRequest request){ + String token = request.getHeader("token"); + if(StringUtils.isEmpty(token)){ + throw new TokenException("请传递token"); + } + //验证token + if(!jwtUtils.verify(token)){ + throw new TokenException("token无效"); + } + //解析token + DecodedJWT tokenInfo = jwtUtils.getTokenInfo(token); + String userName = tokenInfo.getClaim("userName").asString(); + //获得用户信息、加载权限 + UserDetails userDetails = userDetailsService.loadUserByUsername(userName); + //传参:Spring Security的认证对象、密码验证、权限 + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + + //存入上下文中,表示该用户已经登录 + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + } + ``` + +- 登录接口,建议使用security提供的service + +- 登录接口代码 + + ```Java + @PostMapping("/login") + public Result login(@RequestBody LoginInfo user){ + //使用security的验证方式 密码加密、自定义异常、以及一些字段的认证 + UsernamePasswordAuthenticationToken authenticationToken= + new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword()); + Authentication authenticate = authenticationManager.authenticate(authenticationToken); + + SysUser sysUser=(SysUser) authenticate.getPrincipal(); + + Map map=new HashMap<>(); + map.put("userId",Long.toString(sysUser.getUserId())); + map.put("userName",sysUser.getUsername()); + //这里的jwt仅用于前端 + String token = jwtUtils.getToken(map); + return Result.success(new LoginVo() + .setUserId(sysUser.getUserId()) + .setUserName(user.getUserName()) + .setToken(token) + ,"登陆成功"); + } + ``` + + + + +### 权限问题 + +- 在配置类中开启方法级安全,使用注`@EnableMethodSecurity` +- 在方法中添加注解`@PreAuthorize("hasAuthority('sys:user:add')")` + +### 自定义认证失败处理器 + +登录前异常 + +- 代码 + + ```JavaScript + @Component + public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e ) throws IOException, ServletException { + // 设置响应内容类型为 JSON + response.setContentType("application/json;charset=UTF-8"); + String message; + if (e instanceof AccountExpiredException) { + message = "账户过期,登录失败"; + } else if (e instanceof BadCredentialsException) { + message = "用户名或密码错误"; + } else if (e instanceof DisabledException) { + message = "账户被禁用,登录失败"; + } else if (e instanceof LockedException) { + message = "账户被锁定,登录失败"; + } else if (e instanceof CredentialsExpiredException) { + message = "密码过期,登录失败"; + } else if (e instanceof InsufficientAuthenticationException) { + message = "无权限访问资源"; + }else { + message = "登陆失败!"; + } + + // 构造返回的 JSON 数据 + Result result = new Result().setCode("500").setMsg(message); + // 将结果写入响应 + response.getWriter().write(objectMapper.writeValueAsString(result)); + + } + } + ``` + + +登陆后,权限异常 + +- 代码 + + ```JavaScript + @Component + public class AccessDeniedHandlerImpl implements AccessDeniedHandler { + private final ObjectMapper objectMapper = new ObjectMapper(); + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { + // 设置响应内容类型为 JSON + response.setContentType("application/json;charset=UTF-8"); + Result result = new Result().setCode("700").setMsg("无权限访问,请联系管理员!"); + // 将结果写入响应 + response.getWriter().write(objectMapper.writeValueAsString(result)); + } + } + ``` + + +配置类引入 + +```JavaScript +.exceptionHandling(exception -> exception + .authenticationEntryPoint(authenticationEntryPoint) // 认证失败处理器 + .accessDeniedHandler(accessDeniedHandler) // 权限不足处理器 + ); +``` + + + +## 过滤器链的问题 + +### 顺序 + +url验证 -- token验证 -- 基于用户名+密码的表单登录 + +完全放行一个请求 + +- url放行,则不需要用户名+密码的表单登录 +- token放行,不需要验证token + +需要验证token的请求 + +- 因为在token中验证了token,所以不需要用户、密码验证 + + + + + +## 结合jwt的实际开发使用 + +依赖 + +```Shell + + + org.springframework.boot + spring-boot-starter-security + +``` + +前后端不分离(默认): + +使用非常麻烦且不实用 + +- 维持用户会话:通过cookie/session +- 为了防止csrf攻击,还需为表单生成csrf-token,伪造请求无法获得token + +> [!important] cookie/localStorage盗取风险较小, 可配置cookie无法js获取、防跨站携带;localStorage必须同源获取 + +前后端分离版本 + +### 编写核心配置类 + +关闭csrf、session:对前后端分离项目没有 + +配置过滤链接:放行链接 + +配置跨域:生产环境可能不需要 + +配置拦截器:token拦截,通过需要配置到Spring Security中 + +- 认证成功 → 返回 Authentication 对象,放到 SecurityContext 中 + +编写自定义认证管理器:提供一个密码加密Bean、查询用户信息Service(封装一个用户) + +- 可以不使用,手动检查数据 +- 建议使用,他会自行判断自动校验账号、密码、账户状态 + +编写登录失败处理器:登录失败异常处理 + +编写权限不足处理器:无权限异常 + + + +### ✅ 要进行完整的身份验证(包括检查账号状态),需要用 `AuthenticationManager`: + +```Java +UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken(username, rawPassword); + +Authentication authentication = authenticationManager.authenticate(authToken); +``` + +在这个过程中,Spring Security 会做以下几件事: + +1. **调用** `**UserDetailsService.loadUserByUsername()**` 查询用户; +2. **比对密码**(如 BCrypt); +3. **检查用户状态**: + - `userDetails.isEnabled()`:是否启用; + - `userDetails.isAccountNonLocked()`:是否被锁定; + - `userDetails.isAccountNonExpired()`:账号是否过期; + - `userDetails.isCredentialsNonExpired()`:凭据是否过期。 \ No newline at end of file diff --git a/posts/blog/编程技术/java/框架/SpringSecurity/SpringSecurity笔记/image37.png b/posts/blog/编程技术/java/框架/SpringSecurity/SpringSecurity笔记/image37.png new file mode 100644 index 0000000..65d0124 Binary files /dev/null and b/posts/blog/编程技术/java/框架/SpringSecurity/SpringSecurity笔记/image37.png differ diff --git a/posts/blog/编程技术/分布式/微服务/SpringCloud.md b/posts/blog/编程技术/分布式/微服务/SpringCloud.md new file mode 100644 index 0000000..3f39565 --- /dev/null +++ b/posts/blog/编程技术/分布式/微服务/SpringCloud.md @@ -0,0 +1,70 @@ +--- +title: 微服务笔记 +date: 2025-06-11 +tags: [微服务] +--- + +# SpringCloud + +配SpringCloud Alibaba + + + +## 服务注册(配置中心):Nacos + +配nacos-discover 和 config + +通过DiscoverClient获取服务信息,通过RestTemplate进行调用 + + + +## 远程调用:OpenFeign + +直接定义需要调用的FeignClinet,然后调用 + +* 可以设置连接时间、响应时间 +* 重试机制(超时重试) +* 拦截器(请求拦截器,方便添加token) +* 兜底返回:Fallback,需要配合一个熔断组件 + +## 服务保护:Sentinel + +底层aop+过滤器+拦截器,对**服务(方法)**进行保护;sentinel可以在非Spring体系下使用 + +接口相当于资源、网页手动定义规则(流量控制、熔断降级、系统保护),通过@SentinelResource + +**流控模式**:可以对方法级别进行保护: + +* 直接(限流) +* 链路(可以根据上游请求url进行区别限流) +* 关联(一个在使用、另一个就会受影响) + +(直接模式)**流控效果**:快速失败、冷启动(按时间渐渐增加)、匀速排队 + +**熔断规矩**(针对远程调用):慢调用、异常比例、异常数;有熔断时间,过了熔断时间会发一个探针,成功才会取消熔断 + +**热点规则**:可以根据参数区别限流 + +授权规则、系统规则 + +## 网关:Gateway + +统一入口、请求路由、负载均衡、流量控制、身份认证、协议转换、系统转换、系统监控、安全防护 + +配置断言(匹配规则)、过滤器;需要连接注册中心,配置直接写服务名即可 + +## 分布式事务:Seata + +事务协调者TC、事务管理器TM、资源管理器RM + +对方法进行@GlobalTransactional, + +二阶提交协议: + +* 一阶:本地事务提交(业务数据 + undo_log) +* 二阶:成功 + 失败 + * 成功,所有人删除undo_log、失败所有人拿到自己的前镜像、恢复、删除undo_log + + + +### 链路追踪、日志收集、指标监控、消息处理 \ No newline at end of file diff --git a/posts/blog/编程技术/分布式/微服务/微服务笔记.md b/posts/blog/编程技术/分布式/微服务/微服务笔记.md new file mode 100644 index 0000000..4313549 --- /dev/null +++ b/posts/blog/编程技术/分布式/微服务/微服务笔记.md @@ -0,0 +1,41 @@ +--- +title: 微服务笔记 +date: 2025-06-11 +tags: [微服务] +--- + + +## 基本概念 + +### 集群架构(Cluster) + +- 将多个相同的服务或程序部署在不同的机器上(一个程序部署到多个服务器),通常需要一个调度服务器做负载均衡 +- **核心思想** :**冗余 + 负载均衡** +- 高可用、高并发 + +### **分布式(Distributed)** + +- 一个系统拆成多个子系统/服务,分布在不同节点,协同完成任务。**节点间需通信、数据同步** +- 解耦 & 协作 + +--- + +- **大型项目往往既是分布式又是集群** + + 你有两个微服务: + + - 用户服务 × 2 个节点(集群) + - 订单服务 × 2 个节点(集群) + - ✅ 这是一个 **分布式 + 集群 的架构** + - ✔️ 最常见的生产环境架构 + +### 微服务(Microservices) + +- 是**分布式的一种实现**,子系统更独立、自治的服务,提升灵活性和开发效率 + +- 区别 + + 微服务一定是分布式,但是分布式大概率不是微服务 + + - 系统中引入redis服务,也相当于是分布式,满足多服务不同结点、结点通信 + - 但MySQL却不是,因为不具备,协调服务的能力,决定在于是否参与服务协调 \ No newline at end of file diff --git a/posts/blog/编程技术/前端/css.md b/posts/blog/编程技术/前端/css.md new file mode 100644 index 0000000..2a4e38c --- /dev/null +++ b/posts/blog/编程技术/前端/css.md @@ -0,0 +1,58 @@ +--- +title: css +date: 2024-09-24 +tags: [前端] +--- + + +## 内嵌样式 + +```HTML +

这是P标签

+``` + +## 内部样式 + +- **选择器** + + ```CSS + /* 通配符选择器 */ + *{ + color: #999; + } + /* 标签选择器 */ + body{ + margin: 0; + } + /* id选择器 */ + \#header{ + background: saddlebrown; + text-align: center; + height: 10vh; + } + /* class选择器 */ + .tail{ + background:chocolate; + text-align: center; + height: 10vh; + } + /* 属性选择器 */ + [name]{ + color: #000; + } + ``` + + +## 外部样式 + +- 通过`link`标签(处于`head`标签)将外部css引入 + + ```HTML + + ``` + + +## 优先级 + +- 内嵌样式>内部样式>外部样式 +- 同一样式:id选择器>class选择器 \ No newline at end of file diff --git a/posts/blog/编程技术/前端/html.md b/posts/blog/编程技术/前端/html.md new file mode 100644 index 0000000..5d87d0b --- /dev/null +++ b/posts/blog/编程技术/前端/html.md @@ -0,0 +1,6 @@ +--- +title: html +date: 2024-09-20 +tags: [前端] +--- + diff --git a/posts/blog/编程技术/前端/js.md b/posts/blog/编程技术/前端/js.md new file mode 100644 index 0000000..5720225 --- /dev/null +++ b/posts/blog/编程技术/前端/js.md @@ -0,0 +1,114 @@ +--- +title: js +date: 2024-09-20 +tags: [前端] +--- + + +实际上 JavaScript 的异步操作是比较特殊的。JavaScript 本身是单线程的,它通过事件循环(Event Loop)机制来实现异步,而不是依赖额外的线程。但在浏览器中,确实有其他线程在后台工作。 + +让我们看看 JavaScript 的异步实现: + +1. JavaScript 主线程: + +```JavaScript +console.log('开始'); + +// 定时器 +setTimeout(() => { + console.log('定时器执行'); +}, 1000); + +// Promise +fetch('') + .then(res => console.log('数据获取完成')); + +console.log('结束'); +``` + +1. 事件循环机制: + +```Plain +┌───────────────────────┐ +│ 调用栈 │ +│ (JavaScript主线程) │ +└───────────────────────┘ + ↑↓ +┌───────────────────────┐ +│ 事件队列 │ +│ (回调函数等待执行) │ +└───────────────────────┘ +``` + +浏览器中的线程: + +1. JS引擎线程(主线程) + - 执行 JavaScript 代码 + - 维护事件循环 + - 处理回调函数 +2. 浏览器提供的其他线程: + - 定时器线程(处理 setTimeout/setInterval) + - 网络线程(处理 AJAX/fetch 请求) + - DOM事件线程(处理用户交互) + - 渲染线程(渲染页面) + +工作流程: + +1. JavaScript 代码在主线程上按顺序执行 +2. 遇到异步操作时: + - 主线程将其交给相应的系统线程处理 + - 主线程继续执行后续代码 +3. 系统线程完成操作后: + - 将回调函数放入事件队列 +4. 事件循环检查调用栈: + - 如果调用栈为空,取出事件队列中的回调执行 + - 如果不为空,等待调用栈清空 + +所以: + +1. JavaScript 本身是单线程的 +2. 异步操作依赖事件循环机制 +3. 系统级的异步操作(如 I/O、网络请求等)由浏览器/系统提供的其他线程处理 +4. Node.js 也是类似的机制,通过 libuv 提供异步 I/O + +区别于 Java: + +- Java 的异步通常显式创建和管理线程 +- JavaScript 隐藏了线程管理,通过事件循环实现异步 +- JavaScript 开发者不直接接触线程操作 +- JavaScript 的并发模型更简单,但灵活性较低 + +示例 - JavaScript 的事件循环: + +```JavaScript +console.log('1'); // 立即执行 + +setTimeout(() => { + console.log('2'); // 延迟执行 +}, 0); + +Promise.resolve() + .then(() => console.log('3')); // 微任务 + +console.log('4'); // 立即执行 + +// 输出顺序:1, 4, 3, 2 +``` + +这体现了不同类型任务的执行优先级: + +1. 同步代码立即执行 +2. 微任务(Promise)优先于宏任务(setTimeout) +3. 事件循环确保异步代码在适当时机执行 + +所以总结来说: + +- JavaScript 的异步本质上还是依赖多线程 + +- 但这些线程对开发者是透明的 + +- 开发者只需要关注事件循环和回调机制 + +- 这种设计使得 JavaScript 的异步编程模型相对简单易用 + + \ No newline at end of file diff --git a/posts/blog/编程技术/前端/vue/vue笔记.md b/posts/blog/编程技术/前端/vue/vue笔记.md new file mode 100644 index 0000000..50f5cee --- /dev/null +++ b/posts/blog/编程技术/前端/vue/vue笔记.md @@ -0,0 +1,429 @@ +--- +title: vue笔记 +date: 2024-09-20 +categories: [编程技术, 前端, vue] +tags: [vue] +--- + + +## 环境、项目搭建 + +**安装nodejs** + +![image 39.png](vue笔记/image39.png) + +**项目初始化**:`npm create vue@latest` ,只有vue router选是 + +![image 1 22.png](vue笔记/image122.png) + +安装依赖、启动 + +```Shell +npm install +npm run dev +``` + +## vue语法 + +### **数据绑定** + +- html中使用`{{}}`,js中使用 `ref`、`reactive` + +![image 2 14.png](vue笔记/image214.png) + +- 组件中的值绑定,使用`v-model`属性 + +![image 3 13.png](vue笔记/image313.png) + +### **条件显示** + +- `v-if` 、`v-else` + +### **组件循环** + +- `v-for="item in data.arry"` + +![image 4 12.png](vue笔记/image412.png) + +### 事件绑定 + +- `@事件名="方法名"`,完整写法:`v-on:事件名` + +![image 5 11.png](vue笔记/image511.png) + +![image 6 9.png](vue笔记/image69.png) + +### 样式/属性绑定 + +- `:属性="'必须是js事件类型'"`,完整写法:`v-bind:属性` + +![image 7 6.png](vue笔记/image76.png) + +### onMounted方法 + +- 在页面元素加载结束后触发 + + ```JavaScript + onMounted(()=>{ + alert("加载完成!") + }) + ``` + + +## 集成Element-plus + +- 安装依赖 + + ```Shell + npm install element-plus --save + ``` + +- 导入main.js + + ```JavaScript + + //导入element-plus样式 + import ElementPlus from 'element-plus' + import zhCn from 'element-plus/es/locale/lang/zh-cn' + import 'element-plus/dist/index.css' + //必须设置,不然组件是英文信息 + app.use(ElementPlus, { + locale: zhCn, + }) + ``` + + +### 组件使用 + +- **图标** + - 导包 + + ```Shell + npm install @element-plus/icons-vue + ``` + + - main.js导入 + + ```JavaScript + // 如果您正在使用CDN引入,请删除下面一行。 + import * as ElementPlusIconsVue from '@element-plus/icons-vue' + const app = createApp(App) + for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) + } + ``` + + - 使用时,每次都要import;过大使用top进行微调 + - 导入自己的图标 +- **输入框 el-input** + - 一定要写v-model,不然无法输入 + - 设置长度,不然默认一行 + + ```JavaScript + + ``` + +- **下拉框 el-select** + + ```HTML +
+ + + +
+ ``` + +- **单选框 el-radio** + + ```HTML +
+ + + + + + + + + + + +
+ ``` + +- **多选框 el-checkbox** + + - `data.values`必须是**数组**,批量删除 + + ```HTML +
+ + + +
+ ``` + +- **图片 el-image** + - `style="width: 100px;"` 设置宽度,高度按比例变化 + + ```HTML + + + + ``` + + - 如果是本地图片,不能直接使用,需要`import`成变量,然后使用`:src`动态绑定 +- **轮播图 el-carousel** + + ```HTML + + + + + + ``` + +- **日期时间 el-date-picke** + - `value-format`设置数据格式,上面的是显示格式 + + ```HTML + + ``` + + - 开始、结束时间 + + ```undefined + + ``` + +- **数据表格 el-table** + - 通常是对象数组 + + ```HTML + + + + + + ``` + + - 插槽(slot),`scope.row`表示该列数据 + + ```HTML + + + + ``` + +- **分页组件 el-pagination** + + ```HTML + + ``` + +- **弹窗 el-dialog** + + ```HTML + +
+ 日期:{{ data.row.date }} + 名字:{{ data.row.name }} + 地址:{{ data.row.address }} +
+
+ ``` + + +## Vue-Router路由跳转 + +### **页面跳转** + +- 定义一个新路由,在`router/index.js` + + ```JavaScript + routes: [ + { path: '/', redirect:'/home'} //重定向 + { path: '/home', name: 'home', component: () => import('../views/Home.vue')}, + { path: '/test', name: 'test', component: () => import('../views/Test.vue')}, + ], + ``` + +- 使用该组件,点击跳转(**页面不刷新**) + + ```HTML + 跳转 + ``` + +- 编程式跳转,使用router对象,需要导入 + + - push方法 + - replace方法,直接代替当前路由,无法回退 + + ```HTML + 跳转 + ``` + + +### **带参跳转** + +- 传参 + + ```HTML + 跳转 + 跳转1 + ``` + +- 页面读取参数,`router.currentRoute.value.``**query**``.id` + + ```JavaScript + id:router.currentRoute.value.query.id, + ``` + + +### 嵌套路由 + +- 视图嵌套 +- 定义路由,注意:==子路由path不要斜杠==,子路由==不能与外面的路由一致== + + ```JavaScript + { path: '/a', name: 'a', component: () => import('../views/A.vue'),children:[ + // /a/b + { path: 'b', name: 'b', component: () => import('../views/B.vue')} + ]}, + ``` + + ![image 8 6.png](vue笔记/image86.png) + + 错误写法 + +- 配置好路由,在父组件添加`****` ,父级路由最好是完全不动的(多个组件的公共部分) + +### 路由守卫 + +- 设置页面信息,如标题 + + ```JavaScript + // 跳转设置标题 + router.beforeEach((to,from,next)=>{ + document.title=to.meta.title; + next(); + }) + ``` + + +### 404页面 + +- 配置路由,当路由都不匹配,则重定向到404 + + ```JavaScript + { path:'/:pathMatch(.*)',redirect:"/404"} + ``` + + ![image 9 4.png](vue笔记/image94.png) + + +## axios + +- 配置依赖 + + ```Shell + npm i axios -S + ``` + +- 封装请求 `request.js` ,后端记得配跨域 + +- 使用request,导入request.js,直接使用 + + ```undefined + //get + request.get('/stu').then(res=>{ + data.stus=res.data.data; + }) + + //get /stu?user=xxx + const selectByparam=()=>{ + request.get('/stu',{ + params:{user:data.user} + }).then(res=>{ + data.stu=res.data.data; + }) + + //get /stu/{id} + //注意🔥:是``不是'' + const selectById=()=>{ + request.get(`/stu/${data.id}`).then(res=>{ + data.stu=res.data.data; + }) + ``` + + ```JavaScript + request.post('/stu', data.student).then(res=>{ + data.r=res.data; + }) + ``` + + +## 分页 + +- 分页请求 + + ```JavaScript + const page = () => { + request.get('/page', { + params: { + pageNum: data.pageNum, + pageSize: data.pageSize + } + }).then(res => { + data.tableData = res.data.data.list; + data.total=res.data.data.total; + }) + } + ``` + + + + + + +使用数据持久化插件 + +```Shell +npm i pinia-plugin-persistedstate +``` \ No newline at end of file diff --git a/posts/blog/编程技术/前端/vue/vue笔记/image122.png b/posts/blog/编程技术/前端/vue/vue笔记/image122.png new file mode 100644 index 0000000..bc7adb6 Binary files /dev/null and b/posts/blog/编程技术/前端/vue/vue笔记/image122.png differ diff --git a/posts/blog/编程技术/前端/vue/vue笔记/image214.png b/posts/blog/编程技术/前端/vue/vue笔记/image214.png new file mode 100644 index 0000000..508a4d4 Binary files /dev/null and b/posts/blog/编程技术/前端/vue/vue笔记/image214.png differ diff --git a/posts/blog/编程技术/前端/vue/vue笔记/image313.png b/posts/blog/编程技术/前端/vue/vue笔记/image313.png new file mode 100644 index 0000000..3321b61 Binary files /dev/null and b/posts/blog/编程技术/前端/vue/vue笔记/image313.png differ diff --git a/posts/blog/编程技术/前端/vue/vue笔记/image39.png b/posts/blog/编程技术/前端/vue/vue笔记/image39.png new file mode 100644 index 0000000..f22f323 Binary files /dev/null and b/posts/blog/编程技术/前端/vue/vue笔记/image39.png differ diff --git a/posts/blog/编程技术/前端/vue/vue笔记/image412.png b/posts/blog/编程技术/前端/vue/vue笔记/image412.png new file mode 100644 index 0000000..a93c5fd Binary files /dev/null and b/posts/blog/编程技术/前端/vue/vue笔记/image412.png differ diff --git a/posts/blog/编程技术/前端/vue/vue笔记/image511.png b/posts/blog/编程技术/前端/vue/vue笔记/image511.png new file mode 100644 index 0000000..94c336d Binary files /dev/null and b/posts/blog/编程技术/前端/vue/vue笔记/image511.png differ diff --git a/posts/blog/编程技术/前端/vue/vue笔记/image69.png b/posts/blog/编程技术/前端/vue/vue笔记/image69.png new file mode 100644 index 0000000..6b8d134 Binary files /dev/null and b/posts/blog/编程技术/前端/vue/vue笔记/image69.png differ diff --git a/posts/blog/编程技术/前端/vue/vue笔记/image76.png b/posts/blog/编程技术/前端/vue/vue笔记/image76.png new file mode 100644 index 0000000..15b3940 Binary files /dev/null and b/posts/blog/编程技术/前端/vue/vue笔记/image76.png differ diff --git a/posts/blog/编程技术/前端/vue/vue笔记/image86.png b/posts/blog/编程技术/前端/vue/vue笔记/image86.png new file mode 100644 index 0000000..39479de Binary files /dev/null and b/posts/blog/编程技术/前端/vue/vue笔记/image86.png differ diff --git a/posts/blog/编程技术/前端/vue/vue笔记/image94.png b/posts/blog/编程技术/前端/vue/vue笔记/image94.png new file mode 100644 index 0000000..ce9461b Binary files /dev/null and b/posts/blog/编程技术/前端/vue/vue笔记/image94.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/DBA命令.md b/posts/blog/编程技术/数据库/MySQL/DBA命令.md new file mode 100644 index 0000000..6c2df37 --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/DBA命令.md @@ -0,0 +1,136 @@ +--- +title: DBA命令 +date: 2024-10-11 +tags: [MySQL] +--- + + +## 新建用户(root) + +- 创建用户:分为本地用户和外网用户(root用户才有权限创建) + + ```SQL + --本地用户 + create user '用户名'@'localhost' identified by '密码'; + --外网用户 + create user '用户名'@'%' identified by '密码'; + ``` + +- 创建用户没有任何权限,系统表也只能看到以下两个 + + ![image 24.png](DBA命令/image24.png) + +- root用户查看当前用户 + + ```SQL + select user,host from mysql.user; + ``` + + +## 修改用户名(root) + +- 语法格式:可以同时修改用户类型 + + ```SQL + rename user '原用户名'@'host' to '新用户名'@'host'; + ``` + +- 刷新权限才能生效:`flush privileges` + +## 修改用户密码(root) + +- 语法格式 + + ```SQL + alter user '用户名'@'host' identified by '新密码'; + ``` + +- 刷新权限才能生效:`flush privileges` + +## 删除用户(root) + +- 语法格式 + + ```SQL + drop user '用户名'@'host'; + ``` + +- 刷新权限才能生效:`flush privileges` + +## 用户授权 + +- 授权语法: + + ```SQL + grant [权限1,权限2...] + on 库名.表名 + to '用户名'@'主机名/IP地址'; + + --本机用户 + '用户名'@'localhost'; + --外网用户 + '用户名'@'%'; + ``` + +- 值 + - 权限: + + ```SQL + --所有权限 + all privileges + --细粒度权限 + select、insert、delete、update、alter、create、drop + reload(重新加载)、index(索引)、usage(登录权限)...... + ``` + + - 库名可以使用 * ,它代表所有数据库;表名可以采用 * ,它代表所有表;也可以提供具体的数据库和表,例如:powernode.emp +- 查看用户权限:`show grants for ‘用户名’@’host’` +- 给予用户授权的权力:`with grant option` + + ```SQL + # with grant option的作用是:java2用户也可以给其他用户授权了。 + grant select,insert,delete,update on *.* to 'java2'@'%' with grant option; + ``` + +- 刷新权限才能生效:`flush privileges` + +## 撤销权限 + +- 语法格式 + + ```SQL + revoke 权限 on 数据库名.表名 from '用户'@'IP地址'; + ``` + +- 刷新权限才能生效:`flush privileges` + +## 数据备份 + +- 导出数据 + + ```SQL + \#导出所有数据库 + mysqldump --all-databases > 地址/文件名.sql -uroot -p密码 + + # 导出数据库中所有的表 + mysqldump 数据库名> e:/powernode.sql -uroot -p1234 --default-character-set=utf8 + + # 导出表的数据 + mysqldump 数据库名 表 > e:/powernode.sql -uroot -p1234 --default-character-set=utf8 + ``` + +- 导入数据 + - 导入全部数据库 + + ```SQL + mysql < 地址/文件名.sql -uroot -p1234 --default-character-set=utf8 + ``` + + - 备份数据库、表,都需要先手动创建数据库,才能导入 + + ```SQL + create database 数据库; + use 数据库; + --导入数据 + source d:/powernode.sql + ``` \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/MySQL/DBA命令/image24.png b/posts/blog/编程技术/数据库/MySQL/DBA命令/image24.png new file mode 100644 index 0000000..b849b37 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/DBA命令/image24.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/MySQL优化.md b/posts/blog/编程技术/数据库/MySQL/MySQL优化.md new file mode 100644 index 0000000..622c368 --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/MySQL优化.md @@ -0,0 +1,344 @@ +--- +title: MySQL优化 +date: 2024-10-21 +tags: [MySQL] +--- + + +常见的优化手段 + +- **SQL查询优化**:通过优化查询语句、添加索引等方式进行,效果显著、成本低 +- 库表结构优化:通过规范化设计、优化索引和数据类型,对数据库结构进行调整和改进 +- 系统配置(硬件)优化:调整最大连接数、内存管理、IO调度等参数;升级硬盘、增加内存容器、升级处理器,成本较高 + +## SQL性能分析 + +### 分辨==数据库==类型 + +- 通过命令查看数据库的增删改查的使用情况 + + ```SQL + //使用关键字status,加上global为完整统计 + show [global] status like 'Com_select'; + show [global] status like 'Com_insert'; + show [global] status like 'Com_delete'; + show [global] status like 'Com_update'; + ``` + + +### 慢查询日志 + +- 将执行较慢的DQL语句自动计入日志 + + ```SQL + //查看系统变量中慢查询日志是否开启 + show variables like 'slow_query_log'; + ``` + +- 默认是不开启的,通过my.ini文件来开启 + + ![image 27.png](MySQL优化/image27.png) + + ```SQL + [mysqld] + slow_query_log=1 + long_query_time=3 + ``` + + - `1`表示开启功能,`3`表示耗时超过3秒则记录 + - 日志文件生成位置:`data`文件夹下的`主机名-slow.log`文件 + +### 查看语句执行情况 + +- 通过`show profiles`查看语句执行情况 + - 将`profiling`打开 + + ```SQL + //查看当前数据库是否支持profile操作(用户变量) + select @@have_profiling; + //查看是否打开 + select @@profiling; + //打开 + set profiling = 1; + ``` + +- 使用:展示最近执行的 SQL 查询的**执行过程**的详细信息 + + ```SQL + show profiling; + ``` + + ![image 1 13.png](MySQL优化/image113.png) + + ```SQL + --查看某个语句的详情,n表示是Query_ID + show profile for query n; + --查看cpu的使用情况 + show profile cpu for query n; + ``` + + ![image 2 6.png](MySQL优化/image26.png) + + +### explain + +- 查看DQL语句的执行计划 + + ```SQL + explain DQL语句 + ``` + + +![image 3 5.png](MySQL优化/image35.png) + +查询结果 + +- 字段分析 + - id:id越大优先级越高,反映了select语句的执行顺序 + - select_type:查询语句类型 + + |类型|描述| + |---|---| + |SIMPLE(simple)|不包含子查询或UNION操作,往往就是一个表或一个联表(join)| + |PRIMARY(primary)|表示当前查询是一个主查询(主要的查询)| + |UNION(union)|表示包含UNION操作| + |SUBQUERY(subquery)|子查询| + |DERIVED(derived)|派生表(from后面的表)| + + - table:操作的表 + - ==**type**==:访问类型(从上到下,**效率从高到低**) + + |类型|描述|索引| + |---|---|---| + |null|没有查询表的时候,例:`select 1;`|无| + |system|必须是MyISAM引擎表、表中只能有一行数据|无| + |const|根据主键或者唯一索引查询(等值查询)|等值,主键索引| + |eq_ref|==多表查询==,关联字段必须是主键或非NULL唯一索引,并且是等值连接|多表,主键索引| + |ref|使用(非主键)非唯一索引或唯一索引的前缀进行等值匹配(使用 = 或 <=> 操作符)|等值,非主键索引| + |range|使用了索引,扫描了索引树的一部分(范围查找)|范围,索引| + |index|使用索引,但也遍历整个索引树(不做条件筛选)|索引| + |all|全表扫描|无| + + ![image 4 5.png](MySQL优化/image45.png) + + - eq_ref对字段的要求 + + ```SQL + 表1.主键=表2.主键 + 表1.主键=表2.非NULL唯一索引字段 + ``` + + - ref和range区别主要:是否为唯一索引,都是范围查找 + - possible_keys:可能用到的索引 + - key:实际用到的索引 + - key_len:索引所占字节数 + - rows:查询扫描的预计行数 + - Extra:额外信息和说明,帮我们理解查询的执行过程 + +## 索引优化 + +### 最左前缀原则 + +- 当索引为复合索引时,一条查询语句条件中包含了复合索引的最左列字段时,索引才会生效 + - ==sql语句条件的最左字段与复合索引最左侧字段一致,其他字段对位置没有要求== + - 缺少字段使用部分索引 + - 索引生效时,访问类型为`ref` + - 范围查询时,为`range` + - 充分使用索引,建议范围查找时带上“=” + - 不满足最左前缀原则访问前缀为`index` + +### 索引失效 + +- 索引列参加了运算 +- 索引列模糊查询以%开头 +- 索引列是字符串类型,但查询时省略了单引号 +- 查询条件中有or,其中有字段未添加索引 +- 查询时符合条件的记录占比较大时 + - 比如查询是否为null(is not null)时,符合条件多的则不走索引 + +### 指定索引 + +当该字段是单列索引,又是复合索引时,可以指定MySQL使用那个索引 + +- use index(索引名):建议使用该索引,MySQL会根据实际效率选择 +- ignore index(索引名):忽略该索引 +- force index(索引名):强制使用该索引 + +```SQL +select * from 表名 use index(索引名) where 条件; +``` + +### 前缀索引 + +如果一个字段是varchar或text字段,字段中存储的是文本或者大文本,直接使用内容当作索引会导致索引过长,可以==截取前几个字符串作为索引创建==,也就是前缀索引 + +- 创建索引 + + ```SQL + create index index_name on 表名(字段名(长度)); + ``` + +- 索引越唯一,效率越高(查询值越接近1,效果越好) + + ```SQL + select count(distinct subtring(字段名,1,前几个字符))/count(*) + from 表名; + ``` + + +## SQL优化 + +### order by的优化 + +- 给需要排序的字段==添加索引==,如果是多字段排序使用复合索引 +- 索引可以排序,可以根据要求给索引添加排序规则 + + ```SQL + create index 索引名 on 表名(字段 asc, 字段 desc); + ``` + + - B+树叶子结点之间采用的是==双向指针==。可以从左向右(升序),也可以从右向左(降序) +- 遵循最左前缀原则 +- explain查看order by的语句时,extra列会显示 + - `using index`:使用索引,索引本来就是提前排序的,效率高 + - `using filesort`:使用文件排序,没有使用索引,查询数据在内存中排序,效率低 +- 如果无法避免`filesort`,要注意排序缓存的大小,默认缓存大小256KB,可以修改系统变量 `sort_buffer_size` + + ```SQL + show variables like 'sort_buffer_size'; + ``` + + +### group by优化 + +- 给需要分组的字段==添加索引==,如果是多字段排序使用复合索引 +- 遵循最左前缀原则 +- 没有使用索引,将`Using temporary` + +### count(*)优化 + +- 原理: + - count(主键):将每个主键取出,累加 + - count(常量值):获取每个常量值,累加 + - count(字段):取出字段的每个值,==判断是否为NULL==,不为NULL则累加 + - count(*):不用取值,底层mysql做了优化,直接统计总行数,效率最高(但不排除null数据) + +- 其他方法 + - 使用额外的其他程序来实现,例如每向表中插入一条记录时,在`redis`数据库中维护一个总行数,这样获取总行数的时候,直接从redis中获取即可,这样效率是最高的 + - 对于MyISAM存储引擎来说,当一个select语句没有where条件时,获取总行数效率是极高的,不需要统计,因为MyISAM存储引擎维护了一个单独的总行数。 + +### limit优化 + +- 当数据量特别庞大时,取数据时,(官方方案)通过==覆盖索引+子查询==进行优化 + + ```SQL + --正常写法 + select 字段 from 表名 where 条件 limit 从第几个开始,需要条数; + + --覆盖索引,根据主键子查询 + select 字段 from 表名 join + (select id from 表名 where 条件) + on 条件; + ``` + + +### 主键优化 + + + +### insert优化 + +- 批量操作,一次最多1000条,效率低 + + ```SQL + insert into 表名 values + (1,'jack',20),(2,'lucy',30),(3,'timi',22); + ``` + +- 默认自动提交事务,当插入大量数据时,建议手动开启事务和手动提交事务 +- 主键值建议采用==顺序插入==,顺序插入比乱序插入效率高 +- 大量插入,使用MySQL的load指令,可以将csv、txt文件批量导入,效率高 + - 文件要求 + + ![image 5 4.png](MySQL优化/image54.png) + + - 登录指定参数 + + ```SQL + mysql --local-infile -uroot -p... + ``` + + - 开启local_infile功能 + + ```SQL + set global local_infile = 1; + ``` + + - 执行load指令 + + ```SQL + --省略创建表的过程 + load data local infile + 本地文件地址 + into table + 表名 + fields terminated by 字段间分割符 + lines terminated by 行之间分隔符; + + --例如 + load data local infile 'E:\\powernode\\05-MySQL高级\\resources\\t_temp-100W.csv' into table t_temp fields terminated by ',' lines terminated by '\n'; + load data local infile 'E:/桌面/test/data.txt' into table insert_tb fields terminated by ',' lines terminated by '\n'; + ``` + + +### update优化 + +- ==行级锁是对索引列加锁==,主键当然有索引,所以使用了行级锁,如果索引失效,或者字段上没有索引,则会升级为表级锁 + - 没有索引时,MySQL无法精确定位要修改的记录,==必须扫描整张表来找到匹配的记录==,为了保证数据一致性,只能锁定整张表 +- 所以修改数据,建议==通过索引字段查询修改== + +- 举例理解 + - 根据主键(索引)修改 + + ```SQL + start transaction; + --根据主键修改表,行级锁 + update insert_tb + set name='hhhh' + where id=1; + -- + commit; + ``` + + ```SQL + start transaction; + --此时根据非主键修改表, + --表不会锁住 + update insert_tb + set name='hhhh' + where name='qgs'; + -- + commit; + ``` + + - 根据非索引修改 + + ```SQL + start transaction; + --根据非主键修改表,表会锁住 + update insert_tb + set name='hhhh' + where name='qgs'; + -- + commit; + ``` + + ```SQL + start transaction; + --此时根据主键修改表,表被锁定 + update insert_tb + set name='hhhh' + where id=1; + -- + commit; + ``` \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/MySQL/MySQL优化/image113.png b/posts/blog/编程技术/数据库/MySQL/MySQL优化/image113.png new file mode 100644 index 0000000..1e812b8 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/MySQL优化/image113.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/MySQL优化/image26.png b/posts/blog/编程技术/数据库/MySQL/MySQL优化/image26.png new file mode 100644 index 0000000..f90d6ce Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/MySQL优化/image26.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/MySQL优化/image27.png b/posts/blog/编程技术/数据库/MySQL/MySQL优化/image27.png new file mode 100644 index 0000000..980d991 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/MySQL优化/image27.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/MySQL优化/image35.png b/posts/blog/编程技术/数据库/MySQL/MySQL优化/image35.png new file mode 100644 index 0000000..a0f2281 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/MySQL优化/image35.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/MySQL优化/image45.png b/posts/blog/编程技术/数据库/MySQL/MySQL优化/image45.png new file mode 100644 index 0000000..60ab444 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/MySQL优化/image45.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/MySQL优化/image54.png b/posts/blog/编程技术/数据库/MySQL/MySQL优化/image54.png new file mode 100644 index 0000000..ca68001 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/MySQL优化/image54.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/MySQL安装教程.md b/posts/blog/编程技术/数据库/MySQL/MySQL安装教程.md new file mode 100644 index 0000000..1e50687 --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/MySQL安装教程.md @@ -0,0 +1,206 @@ +--- +title: MySQL安装教程 +date: 2024-10-04 +tags: [MySQL] +--- + + +# **MySQL概述** + +--- + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1619752945028-dbcb73aa-50e2-4b1c-947d-21da8d742e6d.png#averageHue=%23fef8f1&height=138&id=feuEs&originHeight=138&originWidth=209&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7176&status=done&style=shadow&title=&width=209)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1619752945028-dbcb73aa-50e2-4b1c-947d-21da8d742e6d.png#averageHue=%23fef8f1&height=138&id=feuEs&originHeight=138&originWidth=209&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7176&status=done&style=shadow&title=&width=209) + +- MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,MySQL AB公司被Sun公司收购,Sun公司又被Oracle公司收购,目前属于Oracle公司。 +- MySQL是目前最流行的关系型数据库管理系统,在WEB应用方面MySQL是最好的RDBMS应用软件之一。 国内淘宝网站就使用的是MySQL集群。 +- MySQL特点 + - MySQL有开源版本和收费版本,你使用开源版本是不收费的。 + - MySQL支持大型数据库,可以处理上千万记录的大型数据库。 + - MySQL使用标准的SQL数据库语言形式。 + - MySQL在很多系统上面都支持。 + - MySQL对Java,C都有很好的支持,当然其他的语言也支持比如Python、PHP。 + - MySQL是可以定制的,采用了GPL协议,你可以修改源码来开发自己的MySQL系统。 + +# **MySQL的下载** + +--- + +## **官网下载** + +- 第一步:打开MySQL官网[https://www.mysql.com/](https://www.mysql.com/) + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1619753207824-f47a5a2c-3ee1-4d73-881d-be1bb67a8019.png#averageHue=%23f7f9e1&height=414&id=LIcyu&originHeight=990&originWidth=1875&originalType=binary&ratio=1&rotation=0&showTitle=false&size=521061&status=done&style=shadow&title=&width=784)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1619753207824-f47a5a2c-3ee1-4d73-881d-be1bb67a8019.png#averageHue=%23f7f9e1&height=414&id=LIcyu&originHeight=990&originWidth=1875&originalType=binary&ratio=1&rotation=0&showTitle=false&size=521061&status=done&style=shadow&title=&width=784) + +- 第二步:点击"DOWNLOADS" + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1619754075151-2e1900d9-0ee0-4905-b3dc-97b5598061f1.png#averageHue=%239ca199&height=196&id=XJGNj&originHeight=392&originWidth=1073&originalType=binary&ratio=1&rotation=0&showTitle=false&size=67160&status=done&style=shadow&title=&width=536.5)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1619754075151-2e1900d9-0ee0-4905-b3dc-97b5598061f1.png#averageHue=%239ca199&height=196&id=XJGNj&originHeight=392&originWidth=1073&originalType=binary&ratio=1&rotation=0&showTitle=false&size=67160&status=done&style=shadow&title=&width=536.5) + +- 第三步:当前页继续下拉,直到找到下图链接 + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1619754206255-ee47b52c-e183-4401-a8e7-7b0e51452a00.png#averageHue=%23fdfbfa&height=177&id=NI75H&originHeight=353&originWidth=966&originalType=binary&ratio=1&rotation=0&showTitle=false&size=43043&status=done&style=shadow&title=&width=483)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1619754206255-ee47b52c-e183-4401-a8e7-7b0e51452a00.png#averageHue=%23fdfbfa&height=177&id=NI75H&originHeight=353&originWidth=966&originalType=binary&ratio=1&rotation=0&showTitle=false&size=43043&status=done&style=shadow&title=&width=483) + +- 第四步:点击上图链接,进入下面页面,其中“MySQL Community Server”是解压版mysql,“MySQL Installer for Windows”是安装版,这里我们选择解压版 + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1619754239811-b4151058-268c-406b-bcd3-72a6d5771e4b.png#averageHue=%23fefefd&height=336&id=za1XY&originHeight=672&originWidth=1035&originalType=binary&ratio=1&rotation=0&showTitle=false&size=89127&status=done&style=shadow&title=&width=517.5)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1619754239811-b4151058-268c-406b-bcd3-72a6d5771e4b.png#averageHue=%23fefefd&height=336&id=za1XY&originHeight=672&originWidth=1035&originalType=binary&ratio=1&rotation=0&showTitle=false&size=89127&status=done&style=shadow&title=&width=517.5) + +- 第五步:点击上图“MySQL Community Server” + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620375449949-470a5fb1-8af6-4130-a424-129b8961cb8c.png#averageHue=%23d8e6c3&height=354&id=c5iWO&originHeight=707&originWidth=1228&originalType=binary&ratio=1&rotation=0&showTitle=false&size=154716&status=done&style=shadow&title=&width=614)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620375449949-470a5fb1-8af6-4130-a424-129b8961cb8c.png#averageHue=%23d8e6c3&height=354&id=c5iWO&originHeight=707&originWidth=1228&originalType=binary&ratio=1&rotation=0&showTitle=false&size=154716&status=done&style=shadow&title=&width=614) + +- 第六步:点击上图第1个“Download” + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1619754349149-75c6289e-0461-4c82-bf0b-388471535683.png#averageHue=%23fafaf9&height=395&id=loI9w&originHeight=789&originWidth=1001&originalType=binary&ratio=1&rotation=0&showTitle=false&size=102325&status=done&style=shadow&title=&width=500.5)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1619754349149-75c6289e-0461-4c82-bf0b-388471535683.png#averageHue=%23fafaf9&height=395&id=loI9w&originHeight=789&originWidth=1001&originalType=binary&ratio=1&rotation=0&showTitle=false&size=102325&status=done&style=shadow&title=&width=500.5) + +- 第七步:点击上图“No thanks, just start my download.”开始下载,直到下载完毕。 + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620375547996-62d248ee-f33d-41a5-ba14-50cf38405d9b.png#averageHue=%23f6f2ed&height=33&id=H39JS&originHeight=33&originWidth=245&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2768&status=done&style=shadow&title=&width=245)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620375547996-62d248ee-f33d-41a5-ba14-50cf38405d9b.png#averageHue=%23f6f2ed&height=33&id=H39JS&originHeight=33&originWidth=245&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2768&status=done&style=shadow&title=&width=245) + +# **MySQL安装与配置** + +--- + +- 将下载的zip压缩包解压,我这里直接解压到C盘的根目录下 + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620375547996-62d248ee-f33d-41a5-ba14-50cf38405d9b.png#averageHue=%23f6f2ed&height=33&id=PdFDW&originHeight=33&originWidth=245&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2768&status=done&style=shadow&title=&width=245)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620375547996-62d248ee-f33d-41a5-ba14-50cf38405d9b.png#averageHue=%23f6f2ed&height=33&id=PdFDW&originHeight=33&originWidth=245&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2768&status=done&style=shadow&title=&width=245) + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620376986790-01106d3e-b89f-453a-98cb-f2a86d4b2544.png#averageHue=%23f6f1ef&height=205&id=VR5Fm&originHeight=205&originWidth=305&originalType=binary&ratio=1&rotation=0&showTitle=false&size=10933&status=done&style=shadow&title=&width=305)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620376986790-01106d3e-b89f-453a-98cb-f2a86d4b2544.png#averageHue=%23f6f1ef&height=205&id=VR5Fm&originHeight=205&originWidth=305&originalType=binary&ratio=1&rotation=0&showTitle=false&size=10933&status=done&style=shadow&title=&width=305) + +mysql的根目录为:C:\mysql-8.0.24-winx64 + +- 将C:\mysql-8.0.24-winx64\bin目录配置到环境变量path当中 + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620377142769-f797dd2c-4f82-4c8f-b5a2-d97bfbe52225.png#averageHue=%23f5f4f4&height=551&id=wovpn&originHeight=551&originWidth=515&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23226&status=done&style=shadow&title=&width=515)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620377142769-f797dd2c-4f82-4c8f-b5a2-d97bfbe52225.png#averageHue=%23f5f4f4&height=551&id=wovpn&originHeight=551&originWidth=515&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23226&status=done&style=shadow&title=&width=515) + +- 初始化data目录 + +使用管理员身份打开dos命令窗口(按win键,输入cmd,点击管理员身份运行) + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620377404683-4ed7c1d7-c8f1-44a3-ae32-0005183fcd09.png#averageHue=%23e3e3e3&height=635&id=enGFN&originHeight=635&originWidth=779&originalType=binary&ratio=1&rotation=0&showTitle=false&size=55630&status=done&style=shadow&title=&width=779)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620377404683-4ed7c1d7-c8f1-44a3-ae32-0005183fcd09.png#averageHue=%23e3e3e3&height=635&id=enGFN&originHeight=635&originWidth=779&originalType=binary&ratio=1&rotation=0&showTitle=false&size=55630&status=done&style=shadow&title=&width=779) + +cd命令切换到mysql的bin目录下,执行`mysqld --initialize --console`进行data目录初始化,此时会在控制台生成一个随机密码,下图红框中就是随机密码 + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620379266865-b043168d-8825-44e9-99ab-35ce6f8c8e25.png#averageHue=%232d2d2d&height=298&id=RFgFN&originHeight=298&originWidth=1034&originalType=binary&ratio=1&rotation=0&showTitle=false&size=28139&status=done&style=shadow&title=&width=1034)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620379266865-b043168d-8825-44e9-99ab-35ce6f8c8e25.png#averageHue=%232d2d2d&height=298&id=RFgFN&originHeight=298&originWidth=1034&originalType=binary&ratio=1&rotation=0&showTitle=false&size=28139&status=done&style=shadow&title=&width=1034) + +技巧:左键选中密码,直接点击右键,此时密码已经复制到剪贴板中了, +然后随便找一个文件,将密码粘贴到文件中保存起来。 + +- 安装MySQL服务:cd命令切换到bin目录下,执行命令`mysqld -install` + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620379567159-f63f3527-3ba9-48fe-8a19-57e4c3767662.png#averageHue=%23181818&height=61&id=RZu5G&originHeight=61&originWidth=352&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2866&status=done&style=shadow&title=&width=352)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620379567159-f63f3527-3ba9-48fe-8a19-57e4c3767662.png#averageHue=%23181818&height=61&id=RZu5G&originHeight=61&originWidth=352&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2866&status=done&style=shadow&title=&width=352) + +- 查看mysql服务名称:此电脑-右键-管理-服务和应用程序-服务-找MySQL服务,如下图mysql服务名称:MySQL + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620379678662-4b14e7a5-d17c-4c2b-917d-cc4d73b94ae5.png#averageHue=%23f6f4f2&height=521&id=P0jsU&originHeight=695&originWidth=796&originalType=binary&ratio=1&rotation=0&showTitle=false&size=117316&status=done&style=shadow&title=&width=597)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620379678662-4b14e7a5-d17c-4c2b-917d-cc4d73b94ae5.png#averageHue=%23f6f4f2&height=521&id=P0jsU&originHeight=695&originWidth=796&originalType=binary&ratio=1&rotation=0&showTitle=false&size=117316&status=done&style=shadow&title=&width=597) + +- 启动MySQL服务:`net start mysql`,注意start后面是mysql服务的名称 + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620379806288-826de76c-2827-419e-ab16-b6fc72afdbca.png#averageHue=%231c1c1c&height=69&id=JLnqX&originHeight=69&originWidth=353&originalType=binary&ratio=1&rotation=0&showTitle=false&size=3632&status=done&style=shadow&title=&width=353)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620379806288-826de76c-2827-419e-ab16-b6fc72afdbca.png#averageHue=%231c1c1c&height=69&id=JLnqX&originHeight=69&originWidth=353&originalType=binary&ratio=1&rotation=0&showTitle=false&size=3632&status=done&style=shadow&title=&width=353) + +停止mysql服务的命令:`net stop mysql` +注意:启停mysql服务也可以在上一步的图中点击右键进行启停服务。 + +- 登录mysql:输入`mysql -uroot -p`,然后回车,输入刚才的随机密码,然后回车,看到下图表示成功登录mysql + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620379936249-1bfaef7c-9ca7-4ddd-978a-b518acd834b0.png#averageHue=%23141313&height=305&id=EiQKK&originHeight=305&originWidth=649&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17739&status=done&style=shadow&title=&width=649)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620379936249-1bfaef7c-9ca7-4ddd-978a-b518acd834b0.png#averageHue=%23141313&height=305&id=EiQKK&originHeight=305&originWidth=649&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17739&status=done&style=shadow&title=&width=649) + +- 修改MySQL的root账户密码: + - `ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '新密码';` + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620382836782-5a7fef8d-15ce-4dca-a5d0-831b6f60f56e.png#averageHue=%23282828&height=293&id=dVHAz&originHeight=293&originWidth=760&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18567&status=done&style=shadow&title=&width=760)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620382836782-5a7fef8d-15ce-4dca-a5d0-831b6f60f56e.png#averageHue=%23282828&height=293&id=dVHAz&originHeight=293&originWidth=760&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18567&status=done&style=shadow&title=&width=760) + +- 使用新密码登录mysql + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620382883258-875c0c1a-00c7-40f8-a8dd-edb1d39e94e0.png#averageHue=%232a2a2a&height=310&id=WAkW8&originHeight=310&originWidth=687&originalType=binary&ratio=1&rotation=0&showTitle=false&size=21698&status=done&style=shadow&title=&width=687)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620382883258-875c0c1a-00c7-40f8-a8dd-edb1d39e94e0.png#averageHue=%232a2a2a&height=310&id=WAkW8&originHeight=310&originWidth=687&originalType=binary&ratio=1&rotation=0&showTitle=false&size=21698&status=done&style=shadow&title=&width=687) + +[![](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=kLin2&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=shadow&title=)](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg#averageHue=%23f9f8f8&from=url&id=kLin2&originHeight=78&originWidth=1400&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=shadow&title=) + +# **MySQL卸载** + +--- + +- 停止mysql的服务 + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620383089030-497a3bfc-74d5-43a6-9689-8f9e0b24dd73.png#averageHue=%231a1a1a&height=74&id=pHyXZ&originHeight=74&originWidth=362&originalType=binary&ratio=1&rotation=0&showTitle=false&size=3592&status=done&style=shadow&title=&width=362)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620383089030-497a3bfc-74d5-43a6-9689-8f9e0b24dd73.png#averageHue=%231a1a1a&height=74&id=pHyXZ&originHeight=74&originWidth=362&originalType=binary&ratio=1&rotation=0&showTitle=false&size=3592&status=done&style=shadow&title=&width=362) + +- 删除mysql服务 + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620383158420-3d5117c6-0c0f-421c-9570-407dac8c6612.png#averageHue=%23171717&height=58&id=OrAHb&originHeight=58&originWidth=407&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2850&status=done&style=shadow&title=&width=407)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620383158420-3d5117c6-0c0f-421c-9570-407dac8c6612.png#averageHue=%23171717&height=58&id=OrAHb&originHeight=58&originWidth=407&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2850&status=done&style=shadow&title=&width=407) + +- 删除mysql的目录 + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620383199727-89d46dc3-b4cf-4883-9a18-b0ee0c0fe6e8.png#averageHue=%23faf4f2&height=207&id=A4FLD&originHeight=207&originWidth=343&originalType=binary&ratio=1&rotation=0&showTitle=false&size=12988&status=done&style=shadow&title=&width=343)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620383199727-89d46dc3-b4cf-4883-9a18-b0ee0c0fe6e8.png#averageHue=%23faf4f2&height=207&id=A4FLD&originHeight=207&originWidth=343&originalType=binary&ratio=1&rotation=0&showTitle=false&size=12988&status=done&style=shadow&title=&width=343) + +# **登录MySQL** + +--- + +## **本地登录** + +- 如果mysql的服务是启动的,打开dos命令窗口,输入:`mysql -uroot -p`,回车,然后输入root账户的密码 + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620389190451-81319065-6d04-4094-97d0-13236236ad32.png#averageHue=%231c1b1a&height=414&id=EMVUZ&originHeight=552&originWidth=1067&originalType=binary&ratio=1&rotation=0&showTitle=false&size=51645&status=done&style=shadow&title=&width=800)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620389190451-81319065-6d04-4094-97d0-13236236ad32.png#averageHue=%231c1b1a&height=414&id=EMVUZ&originHeight=552&originWidth=1067&originalType=binary&ratio=1&rotation=0&showTitle=false&size=51645&status=done&style=shadow&title=&width=800) + +解释“`mysql -uroot -p`”: +mysql是一个命令,在bin目录下,对应的命令文件是mysql.exe,如果将bin目录配置到环境 +变量path中,才可以在以上位置使用该命令。 +`-uroot` 表示登录的用户是root,u实际上是user单词的首字母。 +`-p` 表示登录时使用密码,p实际上是password单词的首字母。 + +- 也可以将密码以明文的形式写到-p后面,这样做可能会导致你的密码泄露 + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620389582655-74210644-318a-4e71-a242-192d61ef9fd9.png#averageHue=%231d1b1a&height=355&id=AthqC&originHeight=473&originWidth=1006&originalType=binary&ratio=1&rotation=0&showTitle=false&size=52443&status=done&style=shadow&title=&width=755)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620389582655-74210644-318a-4e71-a242-192d61ef9fd9.png#averageHue=%231d1b1a&height=355&id=AthqC&originHeight=473&originWidth=1006&originalType=binary&ratio=1&rotation=0&showTitle=false&size=52443&status=done&style=shadow&title=&width=755) + +## **远程登录** + +- 假设mysql安装在A机器上,现在你要在B机器上连接mysql数据库,此时需要使用远程登录,远程登录时加上远程机器的ip地址即可 + +[![](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620389951870-e1585ee0-d1cd-4b89-973b-b3e6c4a20539.png#averageHue=%23211f1e&height=330&id=Zncdu&originHeight=440&originWidth=994&originalType=binary&ratio=1&rotation=0&showTitle=false&size=52747&status=done&style=shadow&title=&width=746)](https://cdn.nlark.com/yuque/0/2021/png/21376908/1620389951870-e1585ee0-d1cd-4b89-973b-b3e6c4a20539.png#averageHue=%23211f1e&height=330&id=Zncdu&originHeight=440&originWidth=994&originalType=binary&ratio=1&rotation=0&showTitle=false&size=52747&status=done&style=shadow&title=&width=746) + +-h中的h实际上是host单词的首字母。在-h后面的是远程计算机的ip地址。 +127.0.0.1是计算机默认的本机IP地址。 +127.0.0.1又可以写作:localhost,他们是等效的。 +注意:mysql默认情况下root账户是不支持远程登录的,其实这是一种安全策略, +为了保护root账户的安全。如果希望root账户支持远程登录,这是需要进行设置的。 + +- mysql8 开放root账户远程登录权限(危险动作) + + 第一步:现在本地使用root账户登录mysql + + 第二步:`use mysql;` + + 第三步:`update user set host = '%' where user = 'root';` + + 第四步:`flush privileges;` + + + + + + +CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'mks'; GRANT ALL PRIVILEGES ON _._ TO 'root'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES; + + + +**MySQL 8.0** 下,你可以用一条命令让 `root` 完全可以从任意 IP 登录,并且兼容大部分客户端: + +```SQL +CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'yourpassword'; GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES; + +``` + +✅ 说明: + +- `'root'@'%'` → `%` 表示任意 IP。 +- `mysql_native_password` → 保证兼容大多数客户端。 +- `GRANT ALL PRIVILEGES ON *.*` → root 对所有数据库和表都有权限。 +- `WITH GRANT OPTION` → root 可以再授权给别人。 +- `FLUSH PRIVILEGES` → 立即生效。 + +⚠️ **安全提醒**: + +- 这条命令会让数据库完全开放在网络上,非常危险,只适合 **内网测试环境**。 +- 如果在公网使用,请务必结合防火墙限制 IP 或使用 VPN。 + +如果你需要,我可以顺便写一个 **Ubuntu 系统一键开启 root 远程访问脚本**,包括修改配置、重启 MySQL、放行防火墙端口。这样你不用手动操作。 + +你希望我写吗? \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/MySQL/MySql数据库目录.md b/posts/blog/编程技术/数据库/MySQL/MySql数据库目录.md new file mode 100644 index 0000000..769e09c --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/MySql数据库目录.md @@ -0,0 +1,76 @@ +--- +title: MySql数据库目录 +date: 2024-09-12 +tags: [MySQL] +--- + + +## 概述 + +[数据库概述](数据库概述.md) + +--- + +## 数据库安装教程 + +[MySQL安装教程](MySQL安装教程.md) + +--- + +## 查询(DQL) + +[查询](查询.md) + +--- + +## DDL、DML + +[表相关操作](表相关操作.md) + +--- + +## 设计表的三范式 + +[三范式](三范式.md) + +--- + +## 视图(view) + +[视图](视图.md) + +--- + +## 事务(transaction) + +[事务](事务.md) + +--- + +## DBA命令 + +[DBA命令](DBA命令.md) + +--- + +## 其他数据库对象 + +[其他数据库对象](其他数据库对象.md) + +--- + +## 存储引擎(engine) + +[存储引擎](存储引擎.md) + +--- + +## 索引(index) + +[索引](索引.md) + +--- + +## MySQL优化 + +[MySQL优化](MySQL优化.md) \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/MySQL/三范式.md b/posts/blog/编程技术/数据库/MySQL/三范式.md new file mode 100644 index 0000000..026dee0 --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/三范式.md @@ -0,0 +1,80 @@ +--- +title: 三范式 +date: 2024-10-10 +tags: [MySQL] +--- + + +## **什么是数据库设计三范式** + +- 数据库==表设计的原则==,教你怎么设计数据库表有效,并且节省空间。 + +## **三范式** + +- 第一范式:任何一张表都应该==**有主键**==,==每个字段是====**原子性**==的不能再分 + - 例子 + - 以下表的设计不符合第一范式:无主键,并且联系方式可拆分。 + + [![](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679201425169-4ce0b510-2795-4ac8-a0ca-404ffcb6c044.png#averageHue=%23e6e2df&clientId=uf7a0608d-b7a1-4&from=paste&height=99&id=udf5679c6&originHeight=99&originWidth=429&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4785&status=done&style=shadow&taskId=ua7237fee-83ff-44dd-82a6-53445c89cce&title=&width=429)](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679201425169-4ce0b510-2795-4ac8-a0ca-404ffcb6c044.png#averageHue=%23e6e2df&clientId=uf7a0608d-b7a1-4&from=paste&height=99&id=udf5679c6&originHeight=99&originWidth=429&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4785&status=done&style=shadow&taskId=ua7237fee-83ff-44dd-82a6-53445c89cce&title=&width=429) + + - 应该这样设计: + + [![](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679201619568-bcb56e54-e4d5-4152-9833-49d97afa8d35.png#averageHue=%23dcdad8&clientId=uf7a0608d-b7a1-4&from=paste&height=106&id=ua1de331d&originHeight=106&originWidth=455&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5704&status=done&style=shadow&taskId=ua7aef1c7-2585-4b7f-8bdc-d468b6f5e71&title=&width=455)](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679201619568-bcb56e54-e4d5-4152-9833-49d97afa8d35.png#averageHue=%23dcdad8&clientId=uf7a0608d-b7a1-4&from=paste&height=106&id=ua1de331d&originHeight=106&originWidth=455&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5704&status=done&style=shadow&taskId=ua7aef1c7-2585-4b7f-8bdc-d468b6f5e71&title=&width=455) + +- 第二范式:建立在第一范式基础上的,另外要求所有==非主键字段完全依赖主键,====**不能产生部分依赖**====,多个(联合)主键就会产生部分依赖== + - 例子 + - 以下表存储了学生和老师的信息 + + [![](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679201885946-02cacd49-4288-4520-93fb-e4dae6cff5dc.png#averageHue=%23e3e2e2&clientId=uf7a0608d-b7a1-4&from=paste&height=127&id=u841b84e0&originHeight=127&originWidth=440&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4331&status=done&style=shadow&taskId=ua875c093-5656-40b7-bb25-7cd52eb2197&title=&width=440)](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679201885946-02cacd49-4288-4520-93fb-e4dae6cff5dc.png#averageHue=%23e3e2e2&clientId=uf7a0608d-b7a1-4&from=paste&height=127&id=u841b84e0&originHeight=127&originWidth=440&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4331&status=done&style=shadow&taskId=ua875c093-5656-40b7-bb25-7cd52eb2197&title=&width=440) + + - 虽然符合第一范式,但是违背了第二范式,学生姓名、老师姓名都产生了部分依赖。导致数据冗余。 + - 以下这种设计方式就是符合第二范式的: + + [![](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679202122322-da28bdc0-703b-4975-8fe4-0a7b6a222fee.png#averageHue=%23efefef&clientId=uf7a0608d-b7a1-4&from=paste&height=258&id=u6726dbc4&originHeight=258&originWidth=662&originalType=binary&ratio=1&rotation=0&showTitle=false&size=6393&status=done&style=shadow&taskId=uec7f9cf8-9d6f-47fd-8585-4d896c74e0d&title=&width=662)](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679202122322-da28bdc0-703b-4975-8fe4-0a7b6a222fee.png#averageHue=%23efefef&clientId=uf7a0608d-b7a1-4&from=paste&height=258&id=u6726dbc4&originHeight=258&originWidth=662&originalType=binary&ratio=1&rotation=0&showTitle=false&size=6393&status=done&style=shadow&taskId=uec7f9cf8-9d6f-47fd-8585-4d896c74e0d&title=&width=662) + +- 第三范式:建立在第二范式基础上的,==非主键字段====**不能传递依赖于主键字段**====,关联数据最好分表== + - 例子 + - 以下设计方式就是违背第三范式的 + + [![](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679202299108-66198c2a-933d-4bea-9e67-51425c31be7c.png#averageHue=%23e2e2e1&clientId=uf7a0608d-b7a1-4&from=paste&height=129&id=uc7c79455&originHeight=129&originWidth=435&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4338&status=done&style=shadow&taskId=u2f464a3a-5a10-4004-89bf-b89b7144111&title=&width=435)](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679202299108-66198c2a-933d-4bea-9e67-51425c31be7c.png#averageHue=%23e2e2e1&clientId=uf7a0608d-b7a1-4&from=paste&height=129&id=uc7c79455&originHeight=129&originWidth=435&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4338&status=done&style=shadow&taskId=u2f464a3a-5a10-4004-89bf-b89b7144111&title=&width=435) + + - 以上因为产生了传递依赖,导致班级名称冗余。 + - 以下这种方式就是符合第三范式的: + + [![](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679202402829-5040060c-c87f-4411-a599-6a60cc3836a0.png#averageHue=%23e8e7e7&clientId=uf7a0608d-b7a1-4&from=paste&height=270&id=u5495ef35&originHeight=270&originWidth=325&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5610&status=done&style=shadow&taskId=u197e8a0c-3b3f-4627-a574-464a40a019f&title=&width=325)](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679202402829-5040060c-c87f-4411-a599-6a60cc3836a0.png#averageHue=%23e8e7e7&clientId=uf7a0608d-b7a1-4&from=paste&height=270&id=u5495ef35&originHeight=270&originWidth=325&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5610&status=done&style=shadow&taskId=u197e8a0c-3b3f-4627-a574-464a40a019f&title=&width=325) + + +## 表设计 + +### **一对多怎么设计** + +- 口诀:==一对多====**两张表**====,====**多的表加外键**== + + [![](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679200526299-5a9122fe-b7f6-423c-9fd8-5c28a960cb75.png#averageHue=%23cad480&clientId=uf7a0608d-b7a1-4&from=paste&height=84&id=u7e79770c&originHeight=84&originWidth=474&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2499&status=done&style=shadow&taskId=u67f05804-f17a-41aa-b626-21638d20ef9&title=&width=474)](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679200526299-5a9122fe-b7f6-423c-9fd8-5c28a960cb75.png#averageHue=%23cad480&clientId=uf7a0608d-b7a1-4&from=paste&height=84&id=u7e79770c&originHeight=84&originWidth=474&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2499&status=done&style=shadow&taskId=u67f05804-f17a-41aa-b626-21638d20ef9&title=&width=474) + + [![](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679200546241-3a0db05e-74e8-4452-92b9-721c5b3d36d5.png#averageHue=%23f4f779&clientId=uf7a0608d-b7a1-4&from=paste&height=262&id=ue4c12bca&originHeight=262&originWidth=533&originalType=binary&ratio=1&rotation=0&showTitle=false&size=6129&status=done&style=shadow&taskId=uf2d1bb71-1993-4ccf-b8ae-cc8c3e51671&title=&width=533)](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679200546241-3a0db05e-74e8-4452-92b9-721c5b3d36d5.png#averageHue=%23f4f779&clientId=uf7a0608d-b7a1-4&from=paste&height=262&id=ue4c12bca&originHeight=262&originWidth=533&originalType=binary&ratio=1&rotation=0&showTitle=false&size=6129&status=done&style=shadow&taskId=uf2d1bb71-1993-4ccf-b8ae-cc8c3e51671&title=&width=533) + + +### **多对多怎么设计** + +- ==多对多====**三张表**====,====**关系表添加外键**== + + [![](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679200858013-26513a66-0af8-4b84-bd90-b52a240de65c.png#averageHue=%23efe9e9&clientId=uf7a0608d-b7a1-4&from=paste&height=283&id=ucc864c99&originHeight=283&originWidth=944&originalType=binary&ratio=1&rotation=0&showTitle=false&size=14583&status=done&style=shadow&taskId=u5e882710-c0a4-4a2c-b02f-fc57a3e10de&title=&width=944)](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679200858013-26513a66-0af8-4b84-bd90-b52a240de65c.png#averageHue=%23efe9e9&clientId=uf7a0608d-b7a1-4&from=paste&height=283&id=ucc864c99&originHeight=283&originWidth=944&originalType=binary&ratio=1&rotation=0&showTitle=false&size=14583&status=done&style=shadow&taskId=u5e882710-c0a4-4a2c-b02f-fc57a3e10de&title=&width=944) + + +### **一对一怎么设计** + +- 两种方案: + + - 第一种:即是主键,也是外键(共享) + + [![](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679201037367-a1b5661a-f127-42b0-87d9-609c61fa4839.png#averageHue=%23ededaa&clientId=uf7a0608d-b7a1-4&from=paste&height=111&id=u82a64286&originHeight=111&originWidth=501&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2087&status=done&style=shadow&taskId=u60e60695-8178-4aae-9c1b-1d9bf3b165d&title=&width=501)](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679201037367-a1b5661a-f127-42b0-87d9-609c61fa4839.png#averageHue=%23ededaa&clientId=uf7a0608d-b7a1-4&from=paste&height=111&id=u82a64286&originHeight=111&originWidth=501&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2087&status=done&style=shadow&taskId=u60e60695-8178-4aae-9c1b-1d9bf3b165d&title=&width=501) + + - 第二种:==采用一对多的思路,并设置====**外键唯一**== + + [![](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679201084526-c5773a4e-75bf-4e6d-9ac4-f8b8272d1b46.png#averageHue=%23e6e6e6&clientId=uf7a0608d-b7a1-4&from=paste&height=115&id=ufe1dc281&originHeight=115&originWidth=697&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2496&status=done&style=shadow&taskId=u50c9eb60-c470-4498-ab25-2aa5f736dbd&title=&width=697)](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679201084526-c5773a4e-75bf-4e6d-9ac4-f8b8272d1b46.png#averageHue=%23e6e6e6&clientId=uf7a0608d-b7a1-4&from=paste&height=115&id=ufe1dc281&originHeight=115&originWidth=697&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2496&status=done&style=shadow&taskId=u50c9eb60-c470-4498-ab25-2aa5f736dbd&title=&width=697) + + +## **最终的设计** + +最终以满足客户需求为原则,有的时候会拿空间换速度,违背三范式 \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/MySQL/事务.md b/posts/blog/编程技术/数据库/MySQL/事务.md new file mode 100644 index 0000000..814da75 --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/事务.md @@ -0,0 +1,430 @@ +--- +title: 事务 +date: 2024-10-11 +tags: [MySQL] +--- + + +## **概述** + +- 事务是一个最小的工作单元,在数据库当中,事务表示一件完整的业务 + - 详解 + - 一个业务的完成可能需要多条DML语句共同配合才能完成,例如转账业务,需要执行两条DML语句,先更新张三账户的余额,再更新李四账户的余额,为了保证转账业务不出现问题,就必须保证要么同时成功,要么同时失败,怎么保证同时成功或者同时失败呢?就需要使用事务机制 +- 作用:在同一个事务当中,多条DML语句会**同时成功**,或者**同时失败**,不会出现一部分成功,一部分失败的现象,保证数据安全 +- 事务只针对**DML(insert delete update)语句**有效:因为只有这三个语句是改变表中数据的 + - 单条DML语句**默认开启事务** + +## **事务四大特性:ACID** + +- **原子性**(Atomicity):是指事务包含的所有操作**要么全部成功,要么同时失败** +- **一致性**(Consistency):是指事务开始前,和事务完成后,**数据应该是一致的** +- **隔离性**(Isolation):隔离性是当多个⽤户并发访问数据库时,⽐如操作同⼀张表时,数据库为每⼀个⽤户开启的事务,不能被其他事务的操作所⼲扰,多个并发**事务之间要相互隔离** +- **持久性**(Durability):持久性是指⼀个事务⼀旦被**提交了**,那么对数据库中的数据的改变就是**永久性的**,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作 + +## **使用MySQL事务** + +- 在dos命令窗口中开启MySQL事务: + + ```SQL + start transaction; + --或者 + begin; + + --DML语句 + ... + + --回滚事务 + rollback; + --提交事务 + commit; + ``` + +- 只要执行以上的`rollback`或者`commit`,事务都会结束 +- MySQL默认情况下采用的事务机制是:自动提交,所谓自动提交就是只要执行一条DML语句则提交一次 + +## **事务隔离级别** + +[![](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679213953232-4c17a795-8b1f-45d2-907b-c5c16aff672d.png#averageHue=%23e6e3e1&clientId=u5f51673a-a60a-4&from=paste&height=203&id=ue588be16&originHeight=203&originWidth=788&originalType=binary&ratio=1&rotation=0&showTitle=false&size=16611&status=done&style=shadow&taskId=uca6fc90a-5745-4c38-aa5b-62e77ac8997&title=&width=788)](https://cdn.nlark.com/yuque/0/2023/png/21376908/1679213953232-4c17a795-8b1f-45d2-907b-c5c16aff672d.png#averageHue=%23e6e3e1&clientId=u5f51673a-a60a-4&from=paste&height=203&id=ue588be16&originHeight=203&originWidth=788&originalType=binary&ratio=1&rotation=0&showTitle=false&size=16611&status=done&style=shadow&taskId=uca6fc90a-5745-4c38-aa5b-62e77ac8997&title=&width=788) + +- **隔离级别从低到高排序:读未提交 < 读提交 < 可重复读 < 串行化** +- **不同隔离级别会存在不同的现象,现象按照严重性从高到低排序:脏读 > 不可重复读 > 幻读** + +## **查看与设置隔离级别** + +mysql默认的隔离级别:**可重复读(REPEATABLE READ)** + +- 查看当前会话的隔离级别:`select @@transaction_isolation;` +- 查看全局的隔离级别:`select @@gobal.transaction_isolation;` + +设置事务隔离级别: + +- 会话级: + +```SQL +set session transaction isolation level read committed; +``` + +- 全局级: + +```SQL +set global transaction isolation level read committed; +``` + +## **不同现象** + +### **脏读** + +- 指的是**一个事务读取了另一个事务尚未提交的数据**,即读取了另一个事务中的脏数据(Dirty Data) +- 在此情况下,如果另一个事务回滚了或者修改了这些数据,那么读取这些脏数据的事务所处理的数据就是不准确的。 + +### **不可重复读** + +- 指在一个事务内,**多次读取同一个数据行,得到的结果可能是不一样的** +- 这是由于其他事务对数据行做出了修改操作,导致数据的不一致性 + +### **幻读** + +- 指在事务执行过程中,**前后两次相同的查询条件得到的结果集不一致**,可能会变多或变少 + +## **隔离级别** + +### **读未提交(READ UNCOMMITTED)** + +- A事务与B事务,==A事务可以读取到B事务====**未提交的数据**==,这是最低的隔离级别,几乎两个事务之间没有隔离;这种隔离级别是一种理论层面的,在实际的数据库产品中,没有从这个级别起步的 +- 事务隔离级别是读未提交时,三种现象都存在:脏读、不可重复读、幻读 + +- 例子 + + 将全局事务隔离级别设置为:READ UNCOMMITTED + + ```SQL + set global transaction isolation level read uncommitted; + ``` + + 开启两个DOS命令窗口来模拟两个事务:A事务与B事务。 + + #### 读未提交 + + |A事务|B事务|文件| + |---|---|---| + |[[use powernode]]||| + |[[无标题 5]]|use powernode|| + |[[start transaction;]]||| + |[[无标题 3]]|mysql> start transaction;|| + |[[select from a; 2]]||| + |[[学习/数据库/事务/读未提交/无标题 2\|无标题 2]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709003498399-02e4239e-9064-4437-b331-8358459f8fd5.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=191&id=u3b49db14&originHeight=191&originWidth=291&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4424&status=done&style=shadow&taskId=ub68d2077-9c02-449a-9055-5854c835313&title=&width=291](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709003498399-02e4239e-9064-4437-b331-8358459f8fd5.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=191&id=u3b49db14&originHeight=191&originWidth=291&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4424&status=done&style=shadow&taskId=ub68d2077-9c02-449a-9055-5854c835313&title=&width=291)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709003498399-02e4239e-9064-4437-b331-8358459f8fd5.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=191&id=u3b49db14&originHeight=191&originWidth=291&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4424&status=done&style=shadow&taskId=ub68d2077-9c02-449a-9055-5854c835313&title=&width=291)| + |[[学习/数据库/事务/读未提交/无标题\|无标题]]|mysql> insert into a values(4);|| + |[[select from a;]]||| + |[[无标题 4]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709003545498-a496e1be-74fd-4e63-9ef0-d6e21b88a7cd.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=217&id=uebb288b9&originHeight=217&originWidth=289&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4600&status=done&style=shadow&taskId=ubb205019-9e90-432c-ab0e-6eb163886d5&title=&width=289](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709003545498-a496e1be-74fd-4e63-9ef0-d6e21b88a7cd.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=217&id=uebb288b9&originHeight=217&originWidth=289&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4600&status=done&style=shadow&taskId=ubb205019-9e90-432c-ab0e-6eb163886d5&title=&width=289)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709003545498-a496e1be-74fd-4e63-9ef0-d6e21b88a7cd.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=217&id=uebb288b9&originHeight=217&originWidth=289&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4600&status=done&style=shadow&taskId=ubb205019-9e90-432c-ab0e-6eb163886d5&title=&width=289)| + + + + + 通过以上测试,可以看到,A事务读取到了B事务还没有提交的数据。这种现象就是脏读 + + +![image 23.png](事务/image23.png) + +### **读提交(READ COMMITTED)** + +- A事务与B事务,A事务**只能读取到**B事务**提交之后的数据**,Oracle数据库默认的就是这种隔离级别 +- 由于其他事务对数据进行修改,所有存在不可重复读、幻读现象 + +- 例子 + + 将数据库的全局事务隔离级别设置为读提交:READ COMMITTED + + ```SQL + set global transaction isolation level read committed; + ``` + + 演示: + + |A事务|B事务|文件| + |---|---|---| + |[[mysql use powernode 3]]||| + |[[无标题 16]]|mysql> use powernode|| + |[[mysql start transaction; 3]]||| + |[[无标题 18]]|mysql> start transaction;|| + |[[mysql select from a; 4]]||| + |[[无标题 15]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709003985270-00489f1c-e135-4bd6-aa08-84cdddf6f007.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=191&id=u5d9d9ed4&originHeight=191&originWidth=289&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4388&status=done&style=shadow&taskId=ub714d0fd-c9f2-48d8-9130-c4b0827471a&title=&width=289](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709003985270-00489f1c-e135-4bd6-aa08-84cdddf6f007.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=191&id=u5d9d9ed4&originHeight=191&originWidth=289&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4388&status=done&style=shadow&taskId=ub714d0fd-c9f2-48d8-9130-c4b0827471a&title=&width=289)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709003985270-00489f1c-e135-4bd6-aa08-84cdddf6f007.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=191&id=u5d9d9ed4&originHeight=191&originWidth=289&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4388&status=done&style=shadow&taskId=ub714d0fd-c9f2-48d8-9130-c4b0827471a&title=&width=289)| + |[[无标题 17]]|mysql> insert into a values(4);|| + |[[mysql select from a; 5]]||| + |[[无标题 14]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709003985270-00489f1c-e135-4bd6-aa08-84cdddf6f007.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=191&id=yl5zF&originHeight=191&originWidth=289&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4388&status=done&style=shadow&taskId=ub714d0fd-c9f2-48d8-9130-c4b0827471a&title=&width=289](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709003985270-00489f1c-e135-4bd6-aa08-84cdddf6f007.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=191&id=yl5zF&originHeight=191&originWidth=289&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4388&status=done&style=shadow&taskId=ub714d0fd-c9f2-48d8-9130-c4b0827471a&title=&width=289)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709003985270-00489f1c-e135-4bd6-aa08-84cdddf6f007.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=191&id=yl5zF&originHeight=191&originWidth=289&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4388&status=done&style=shadow&taskId=ub714d0fd-c9f2-48d8-9130-c4b0827471a&title=&width=289)| + |[[无标题 13]]|mysql> commit;|| + |[[mysql select from a; 3]]||| + |[[无标题 19]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709004165277-dac7bc73-55ba-4034-bd48-b975689ffb41.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=214&id=ucc8e1eca&originHeight=214&originWidth=298&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4626&status=done&style=shadow&taskId=u2cac7a44-ba45-4bd7-86c2-2d280c9de57&title=&width=298](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709004165277-dac7bc73-55ba-4034-bd48-b975689ffb41.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=214&id=ucc8e1eca&originHeight=214&originWidth=298&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4626&status=done&style=shadow&taskId=u2cac7a44-ba45-4bd7-86c2-2d280c9de57&title=&width=298)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709004165277-dac7bc73-55ba-4034-bd48-b975689ffb41.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=214&id=ucc8e1eca&originHeight=214&originWidth=298&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4626&status=done&style=shadow&taskId=u2cac7a44-ba45-4bd7-86c2-2d280c9de57&title=&width=298)| + + + + + 通过以上测试看出,A事务只能读取到B事务提交之后的数据。这种隔离级别解决了脏读问题,但肯定是存在不可重复读和幻读问题。因为只要事务B进行了增删改操作之后并提交了,事务A读取到的数据肯定是不同的。即:不可重复读和幻读都存在。 + + +![image 1 10.png](事务/image110.png) + +### **可重复读(REPEATABLE READ)** + +- A事务和B事务,A事务开启后,读取了某一条记录,然后==B事务对这条记录进行修改并提交,====**A事务读取到的还是修改前的数据**==,这种隔离级别称为可重复读(类似缓存机制)**事务过程不允许其他人update,但允许insert** +- 使用**快照读**可以避免不可重复读,和**当前读**解决幻读 + - 针对**快照读**(普通 select 语句),是**通过 MVCC 方式解决了幻读**,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好的避免了幻读问题 + - **快照读是如何解决幻读的** + + 什么是快照读?普通的select语句都是采用的快照读。顾名思义:在整个事务的处理过程中,执行相同的一个select语句时,==每次都是读取的快照==,也就是说,当事务隔离级别是可重复读,并且执行的select语句是一个普通的select语句时,都会采用快照读的方式读取数据,底层实现原理是: + + - 底层由 MVCC(多版本并发控制)实现,实现的方式是开始事务后,在执行第一个查询语句后,会创建一个 Read View,后续的查询语句利用这个 Read View,通过这个 Read View 就可以在 undo log 版本链找到事务开始时的数据,所以事务过程中每次查询的数据都是一样的,即使中途有其他事务插入了新纪录,是查询不出来这条数据的,所以就很好的避免了幻读问题。 + + - 例子 + + 演示: + + |事务A|事务B|文件| + |---|---|---| + |[[mysql use powernode 8]]||| + |[[无标题 49]]|mysql> use powernode|| + |[[mysql start transaction; 8]]||| + |[[无标题 46]]|mysql> start transaction;|| + |[[mysql select from a; --快照读]]||| + |[[无标题 47]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006316610-12c48e73-e894-49dc-8dfd-32f8d13ec991.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=218&id=mdmNA&originHeight=218&originWidth=298&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4645&status=done&style=shadow&taskId=ua4e22c07-45ef-467a-9a47-c66b5a21472&title=&width=298](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006316610-12c48e73-e894-49dc-8dfd-32f8d13ec991.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=218&id=mdmNA&originHeight=218&originWidth=298&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4645&status=done&style=shadow&taskId=ua4e22c07-45ef-467a-9a47-c66b5a21472&title=&width=298)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006316610-12c48e73-e894-49dc-8dfd-32f8d13ec991.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=218&id=mdmNA&originHeight=218&originWidth=298&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4645&status=done&style=shadow&taskId=ua4e22c07-45ef-467a-9a47-c66b5a21472&title=&width=298)| + |[[无标题 45]]|mysql> insert into a values(5);|| + |[[无标题 48]]|mysql> commit;|| + |[[mysql select from a; --快照读 2]]||| + |[[无标题 44]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006362804-0579079b-e054-4299-b1ab-6c16a68875f0.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=218&id=sxE0q&originHeight=218&originWidth=297&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4643&status=done&style=shadow&taskId=ua8b9a51a-306e-4b30-afdf-c88138fa2a7&title=&width=297](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006362804-0579079b-e054-4299-b1ab-6c16a68875f0.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=218&id=sxE0q&originHeight=218&originWidth=297&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4643&status=done&style=shadow&taskId=ua8b9a51a-306e-4b30-afdf-c88138fa2a7&title=&width=297)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006362804-0579079b-e054-4299-b1ab-6c16a68875f0.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=218&id=sxE0q&originHeight=218&originWidth=297&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4643&status=done&style=shadow&taskId=ua8b9a51a-306e-4b30-afdf-c88138fa2a7&title=&width=297)| + + + + + + + + + + - 针对**当前读**(select ... for update 等语句),是**通过 next-key lock(记录锁+间隙锁)方式解决了幻读**,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好的避免了幻读问题 + - **当前读是如何解决幻读的** + + 当前读,顾名思义:每一次都读取最新的数据。当前读包括:update、delete、insert、select...for update。这个很好理解,因为增删改的时候都要基于最新的数据进行增删改 + + - select...for update原理是:==**对查询范围内的数据进行加锁**==,不允许其它事务对这个范围内的数据进行增删改。也就是说这个select语句范围内的数据是不允许并发的,只能排队执行,从而避免幻读问题。 + select...for update加的锁叫做:next-key lock。我们可以称其为:间隙锁 + 记录锁。间隙锁用来保证在锁定的范围内不允许insert操作。记录锁用来保证在锁定的范围内不允许delete和update操作。 + + - 例子 + + 假如有这样的数据: + + [![](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709020549539-138b4715-5526-45df-8db2-65d05b393ba1.png#averageHue=%23fbfafa&clientId=u0fbbe02e-04ac-4&from=paste&height=100&id=u99c5080d&originHeight=100&originWidth=137&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1043&status=done&style=shadow&taskId=u5dc4d53d-1b0b-4c0d-9c33-a1e5e7f2afb&title=&width=137)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709020549539-138b4715-5526-45df-8db2-65d05b393ba1.png#averageHue=%23fbfafa&clientId=u0fbbe02e-04ac-4&from=paste&height=100&id=u99c5080d&originHeight=100&originWidth=137&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1043&status=done&style=shadow&taskId=u5dc4d53d-1b0b-4c0d-9c33-a1e5e7f2afb&title=&width=137) + + SQL语句是这样写的: + + ```SQL + select * from a where id between 2 and 4 for update; + ``` + + 那么id在[2-4]区间的所有记录行被锁定,不能插入3是通过间隙锁来搞定的。不能修改或删除2和4是通过记录锁来搞定的。 + + 演示: + + |事务A|事务B|文件| + |---|---|---| + |[[mysql use powernode 9]]||| + |[[无标题 50]]|mysql> use powernode|| + |[[mysql start transaction; 9]]||| + |[[无标题 52]]|mysql> start transaction;|| + |[[mysql select from a where id between 2 and 4 for update; -- 当前读]]||| + |[[无标题 51]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709020966550-4d8580bc-d1f4-48c9-b7e3-26be1acf1bd2.png#averageHue=%23100f0e&clientId=u0fbbe02e-04ac-4&from=paste&height=82&id=u8e7603b6&originHeight=82&originWidth=395&originalType=binary&ratio=1&rotation=0&showTitle=false&size=3402&status=done&style=shadow&taskId=u2b45bd7b-d1f9-4e07-8579-11c81f9f062&title=&width=395](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709020966550-4d8580bc-d1f4-48c9-b7e3-26be1acf1bd2.png#averageHue=%23100f0e&clientId=u0fbbe02e-04ac-4&from=paste&height=82&id=u8e7603b6&originHeight=82&originWidth=395&originalType=binary&ratio=1&rotation=0&showTitle=false&size=3402&status=done&style=shadow&taskId=u2b45bd7b-d1f9-4e07-8579-11c81f9f062&title=&width=395)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709020966550-4d8580bc-d1f4-48c9-b7e3-26be1acf1bd2.png#averageHue=%23100f0e&clientId=u0fbbe02e-04ac-4&from=paste&height=82&id=u8e7603b6&originHeight=82&originWidth=395&originalType=binary&ratio=1&rotation=0&showTitle=false&size=3402&status=done&style=shadow&taskId=u2b45bd7b-d1f9-4e07-8579-11c81f9f062&title=&width=395)| + + + + + + + +- **直接使用当前读避免幻读** + + - **第一种产生幻读的场景** + + A事务与B事务。在A事务中第一次查询使用快照读,B事务插入数据。然后在A事务中第二次查询使用当前读,则会产生幻读现象 + + - 例子 + + 演示: + + |事务A|事务B|文件| + |---|---|---| + |[[mysql use powernode 6]]||| + |[[无标题 32]]|mysql> use powernode|| + |[[mysql start transaction; 6]]||| + |[[无标题 35]]|mysql> start transaction;|| + |[[mysql select from a; 7]]||| + |[[无标题 36]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709021313528-7b3b88fd-836a-4223-8b93-ef671edf6f2b.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=187&id=u7e8f9872&originHeight=187&originWidth=301&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4297&status=done&style=shadow&taskId=u49199bba-6760-4fad-9615-dc3c6eaf1ca&title=&width=301](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709021313528-7b3b88fd-836a-4223-8b93-ef671edf6f2b.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=187&id=u7e8f9872&originHeight=187&originWidth=301&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4297&status=done&style=shadow&taskId=u49199bba-6760-4fad-9615-dc3c6eaf1ca&title=&width=301)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709021313528-7b3b88fd-836a-4223-8b93-ef671edf6f2b.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=187&id=u7e8f9872&originHeight=187&originWidth=301&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4297&status=done&style=shadow&taskId=u49199bba-6760-4fad-9615-dc3c6eaf1ca&title=&width=301)| + |[[无标题 33]]|mysql> insert into a values(5);|| + |[[无标题 37]]|mysql> commit;|| + |[[mysql select from a for update; -- 产生了幻读]]||| + |[[无标题 34]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709021386345-1b47e39d-98e6-42de-bf33-1652ed601c21.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=213&id=uaadf86ec&originHeight=213&originWidth=303&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4605&status=done&style=shadow&taskId=u18140f69-1a56-44c7-9d4f-83517e41e56&title=&width=303](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709021386345-1b47e39d-98e6-42de-bf33-1652ed601c21.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=213&id=uaadf86ec&originHeight=213&originWidth=303&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4605&status=done&style=shadow&taskId=u18140f69-1a56-44c7-9d4f-83517e41e56&title=&width=303)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709021386345-1b47e39d-98e6-42de-bf33-1652ed601c21.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=213&id=uaadf86ec&originHeight=213&originWidth=303&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4605&status=done&style=shadow&taskId=u18140f69-1a56-44c7-9d4f-83517e41e56&title=&width=303)| + + + + + + - **第二种产生幻读的场景** + + 事务A与事务B,在事务A中第一次查询使用快照读,在事务B中插入一条数据,然后在事务A中更新事务B插入的那条记录,最后在事务A中再次使用快照读,则会发生幻读现象 + + - 例子 + + |事务A|事务B|文件| + |---|---|---| + |[[mysql use powernode 7]]||| + |[[无标题 42]]|mysql> use powernode|| + |[[mysql start transaction; 7]]||| + |[[无标题 39]]|mysql> start transaction;|| + |[[mysql select from a; 8]]||| + |[[无标题 40]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709021721456-7e0dd695-29c8-4e2f-a88e-4a1fa2d1df90.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=214&id=ufc5ef576&originHeight=214&originWidth=292&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4581&status=done&style=shadow&taskId=u6f2cda2c-b92a-43bf-8471-dd0a074293e&title=&width=292](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709021721456-7e0dd695-29c8-4e2f-a88e-4a1fa2d1df90.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=214&id=ufc5ef576&originHeight=214&originWidth=292&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4581&status=done&style=shadow&taskId=u6f2cda2c-b92a-43bf-8471-dd0a074293e&title=&width=292)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709021721456-7e0dd695-29c8-4e2f-a88e-4a1fa2d1df90.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=214&id=ufc5ef576&originHeight=214&originWidth=292&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4581&status=done&style=shadow&taskId=u6f2cda2c-b92a-43bf-8471-dd0a074293e&title=&width=292)| + |[[无标题 38]]|mysql> insert into a values(6);|| + |[[无标题 41]]|mysql> commit;|| + |[[mysql update a set id=100 where id=6; --主要是因为这个SQL语句的执行触发了当前读]]||| + |[[mysql select from a; -- 产生了幻读]]||| + |[[无标题 43]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709021824472-00989fbd-471a-4f5f-a44a-9781652cd3b3.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=243&id=u11717d5c&originHeight=243&originWidth=293&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5106&status=done&style=shadow&taskId=u336e9c35-982b-4abd-8342-88cea428ea0&title=&width=293](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709021824472-00989fbd-471a-4f5f-a44a-9781652cd3b3.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=243&id=u11717d5c&originHeight=243&originWidth=293&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5106&status=done&style=shadow&taskId=u336e9c35-982b-4abd-8342-88cea428ea0&title=&width=293)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709021824472-00989fbd-471a-4f5f-a44a-9781652cd3b3.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=243&id=u11717d5c&originHeight=243&originWidth=293&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5106&status=done&style=shadow&taskId=u336e9c35-982b-4abd-8342-88cea428ea0&title=&width=293)| + + + + + + + +- 锁住的数据,将无法插入、删除、修改;没锁住的数据可以修改、删除(表无法插入) + +- 例子 + + 将数据库全局隔离级别修改为可重复读: + + ```SQL + set global transaction isolation level repeatable read; + ``` + + 演示: + + |A事务|B事务|文件| + |---|---|---| + |[[mysql use powernode 4]]||| + |[[无标题 20]]|mysql> use powernode|| + |[[mysql start transaction; 4]]||| + |[[无标题 23]]|mysql> start transaction;|| + |[[mysql select empno,ename,sal from emp where empno=7369; 2]]||| + |[[无标题 22]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709005877270-b84cdf55-866b-4b3b-b575-46552dfb84c0.png#averageHue=%23151312&clientId=u0fbbe02e-04ac-4&from=paste&height=109&id=u9a905011&originHeight=109&originWidth=344&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5276&status=done&style=shadow&taskId=u85710df6-3409-4242-a3d5-92ea27f7fc2&title=&width=344](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709005877270-b84cdf55-866b-4b3b-b575-46552dfb84c0.png#averageHue=%23151312&clientId=u0fbbe02e-04ac-4&from=paste&height=109&id=u9a905011&originHeight=109&originWidth=344&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5276&status=done&style=shadow&taskId=u85710df6-3409-4242-a3d5-92ea27f7fc2&title=&width=344)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709005877270-b84cdf55-866b-4b3b-b575-46552dfb84c0.png#averageHue=%23151312&clientId=u0fbbe02e-04ac-4&from=paste&height=109&id=u9a905011&originHeight=109&originWidth=344&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5276&status=done&style=shadow&taskId=u85710df6-3409-4242-a3d5-92ea27f7fc2&title=&width=344)| + |[[无标题 25]]|mysql> update emp set ename='SMITH',sal=8000 where empno=7369;|| + |[[无标题 24]]|mysql> commit;|| + |[[mysql select empno,ename,sal from emp where empno=7369;]]||| + |[[无标题 21]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709005948358-4f23bd69-d6ed-4963-a349-ba35ecc61dc0.png#averageHue=%23151311&clientId=u0fbbe02e-04ac-4&from=paste&height=145&id=ub810f157&originHeight=145&originWidth=336&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7381&status=done&style=shadow&taskId=ud25e00ad-83dc-4fa2-9858-de459ea77f8&title=&width=336](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709005948358-4f23bd69-d6ed-4963-a349-ba35ecc61dc0.png#averageHue=%23151311&clientId=u0fbbe02e-04ac-4&from=paste&height=145&id=ub810f157&originHeight=145&originWidth=336&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7381&status=done&style=shadow&taskId=ud25e00ad-83dc-4fa2-9858-de459ea77f8&title=&width=336)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709005948358-4f23bd69-d6ed-4963-a349-ba35ecc61dc0.png#averageHue=%23151311&clientId=u0fbbe02e-04ac-4&from=paste&height=145&id=ub810f157&originHeight=145&originWidth=336&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7381&status=done&style=shadow&taskId=ud25e00ad-83dc-4fa2-9858-de459ea77f8&title=&width=336)| + + + + + 通过以上测试得知:当事务隔离级别设置为可重复读时,避免了不可重复读问题。 + + 那么在MySQL当中,当事务隔离级别设置为可重复读时,能够避免幻读问题吗?测试一下: + + |事务A|事务B|文件| + |---|---|---| + |[[mysql use powernode 2]]||| + |[[无标题 9]]|mysql> use powernode|| + |[[mysql start transaction; 2]]||| + |[[无标题 11]]|mysql> start transaction;|| + |[[mysql select from a;]]||| + |[[无标题 10]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006316610-12c48e73-e894-49dc-8dfd-32f8d13ec991.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=218&id=ud9dadda5&originHeight=218&originWidth=298&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4645&status=done&style=shadow&taskId=ua4e22c07-45ef-467a-9a47-c66b5a21472&title=&width=298](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006316610-12c48e73-e894-49dc-8dfd-32f8d13ec991.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=218&id=ud9dadda5&originHeight=218&originWidth=298&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4645&status=done&style=shadow&taskId=ua4e22c07-45ef-467a-9a47-c66b5a21472&title=&width=298)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006316610-12c48e73-e894-49dc-8dfd-32f8d13ec991.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=218&id=ud9dadda5&originHeight=218&originWidth=298&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4645&status=done&style=shadow&taskId=ua4e22c07-45ef-467a-9a47-c66b5a21472&title=&width=298)| + |[[无标题 8]]|mysql> insert into a values(5);|| + |[[无标题 7]]|mysql> commit;|| + |[[mysql select from a; 2]]||| + |[[无标题 12]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006362804-0579079b-e054-4299-b1ab-6c16a68875f0.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=218&id=u4c4264a7&originHeight=218&originWidth=297&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4643&status=done&style=shadow&taskId=ua8b9a51a-306e-4b30-afdf-c88138fa2a7&title=&width=297](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006362804-0579079b-e054-4299-b1ab-6c16a68875f0.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=218&id=u4c4264a7&originHeight=218&originWidth=297&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4643&status=done&style=shadow&taskId=ua8b9a51a-306e-4b30-afdf-c88138fa2a7&title=&width=297)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006362804-0579079b-e054-4299-b1ab-6c16a68875f0.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=218&id=u4c4264a7&originHeight=218&originWidth=297&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4643&status=done&style=shadow&taskId=ua8b9a51a-306e-4b30-afdf-c88138fa2a7&title=&width=297)| + + + + + 通过以上测试得知:**当事务隔离级别设置为可重复读时,也避免了幻读问题。是完全避免了幻读问题吗?并不是。**请看以下测试: + + |事务A|事务B|文件| + |---|---|---| + |mysql> use powernode|[[学习/数据库/事务/无标题/无标题 4\|无标题 4]]|| + ||[[mysql use powernode]]|| + |mysql> start transaction;|[[学习/数据库/事务/无标题/无标题 2\|无标题 2]]|| + ||[[mysql start transaction;]]|| + |mysql> select * from a;|[[学习/数据库/事务/无标题/无标题 5\|无标题 5]]|| + ||[[学习/数据库/事务/无标题/无标题 3\|无标题 3]]|[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006612649-1614f4b7-446f-487d-9c1b-000a7e5589d3.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=241&id=ud98016d5&originHeight=241&originWidth=288&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4986&status=done&style=shadow&taskId=u400d7356-cb23-442b-bf41-3a6c7851f3a&title=&width=288](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006612649-1614f4b7-446f-487d-9c1b-000a7e5589d3.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=241&id=ud98016d5&originHeight=241&originWidth=288&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4986&status=done&style=shadow&taskId=u400d7356-cb23-442b-bf41-3a6c7851f3a&title=&width=288)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006612649-1614f4b7-446f-487d-9c1b-000a7e5589d3.png#averageHue=%230f0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=241&id=ud98016d5&originHeight=241&originWidth=288&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4986&status=done&style=shadow&taskId=u400d7356-cb23-442b-bf41-3a6c7851f3a&title=&width=288)| + ||[[mysql insert into a values(6);]]|| + ||[[mysql commit;]]|| + |mysql> select * from a **for update;**|[[无标题 6]]|| + ||[[学习/数据库/事务/无标题/无标题\|无标题]]|[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006674069-b52a691f-2cc1-4721-bf23-0451f4bb7535.png#averageHue=%230e0d0d&clientId=u0fbbe02e-04ac-4&from=paste&height=269&id=uaeabeaf9&originHeight=269&originWidth=295&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5453&status=done&style=shadow&taskId=u127a2aaf-fd6d-421c-be74-3f50b8fa988&title=&width=295](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006674069-b52a691f-2cc1-4721-bf23-0451f4bb7535.png#averageHue=%230e0d0d&clientId=u0fbbe02e-04ac-4&from=paste&height=269&id=uaeabeaf9&originHeight=269&originWidth=295&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5453&status=done&style=shadow&taskId=u127a2aaf-fd6d-421c-be74-3f50b8fa988&title=&width=295)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709006674069-b52a691f-2cc1-4721-bf23-0451f4bb7535.png#averageHue=%230e0d0d&clientId=u0fbbe02e-04ac-4&from=paste&height=269&id=uaeabeaf9&originHeight=269&originWidth=295&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5453&status=done&style=shadow&taskId=u127a2aaf-fd6d-421c-be74-3f50b8fa988&title=&width=295)| + + + + + 通过以上测试得知:**当事务隔离级别设置为可重复读,MySQL会尽最大努力避免幻读问题,但这种隔离级别无法完全避免幻读问题。** + + + + +### **串行化(SERIALIZABLE)** + +- 这种隔离级别最高,避免了所有的问题,缺点是效率低,因为这种隔离级别会导致事务排队处理,即对表的修改是单线程,不支持并发 + +- 例子 + + 设置数据库全局隔离级别为串行化: + + ```Plain + set global transaction isolation level serializable; + ``` + + 演示: + + |事务A|事务B|文件| + |---|---|---| + |[[mysql use powernode 5]]||| + |[[无标题 28]]|mysql> use powernode|| + |[[mysql start transaction; 5]]||| + |[[无标题 31]]|mysql> start transaction;|| + |[[mysql select from a; 6]]||| + |[[无标题 27]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709013080885-bf7ad024-3bdd-4497-997b-1bdc2c81a7da.png#averageHue=%230e0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=262&id=u98eae3de&originHeight=262&originWidth=292&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5406&status=done&style=shadow&taskId=u7fd54c94-2d24-4710-a2f2-4eb6a0f50ad&title=&width=292](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709013080885-bf7ad024-3bdd-4497-997b-1bdc2c81a7da.png#averageHue=%230e0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=262&id=u98eae3de&originHeight=262&originWidth=292&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5406&status=done&style=shadow&taskId=u7fd54c94-2d24-4710-a2f2-4eb6a0f50ad&title=&width=292)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709013080885-bf7ad024-3bdd-4497-997b-1bdc2c81a7da.png#averageHue=%230e0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=262&id=u98eae3de&originHeight=262&originWidth=292&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5406&status=done&style=shadow&taskId=u7fd54c94-2d24-4710-a2f2-4eb6a0f50ad&title=&width=292)| + |[[mysql insert into a values(7);]]||| + |[[无标题 26]]|mysql> select * from a;|| + |[[无标题 30]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709013571200-5bfdf9a5-8238-4601-92b7-5eacbd2de16f.png#averageHue=%230e0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=122&id=ubd8c88a4&originHeight=122&originWidth=284&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2887&status=done&style=shadow&taskId=u18918bf7-edf5-48f1-95c0-21c1e7ae7e7&title=&width=284](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709013571200-5bfdf9a5-8238-4601-92b7-5eacbd2de16f.png#averageHue=%230e0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=122&id=ubd8c88a4&originHeight=122&originWidth=284&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2887&status=done&style=shadow&taskId=u18918bf7-edf5-48f1-95c0-21c1e7ae7e7&title=&width=284)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709013571200-5bfdf9a5-8238-4601-92b7-5eacbd2de16f.png#averageHue=%230e0e0d&clientId=u0fbbe02e-04ac-4&from=paste&height=122&id=ubd8c88a4&originHeight=122&originWidth=284&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2887&status=done&style=shadow&taskId=u18918bf7-edf5-48f1-95c0-21c1e7ae7e7&title=&width=284)| + |[[mysql commit; 2]]||| + |[[无标题 29]]||[![https://cdn.nlark.com/yuque/0/2024/png/21376908/1709013621752-5b81823b-362d-4cdc-8b49-f941eebd827f.png#averageHue=%230e0d0d&clientId=u0fbbe02e-04ac-4&from=paste&height=288&id=ufcf1f12d&originHeight=288&originWidth=297&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5866&status=done&style=shadow&taskId=u385c19ee-7e7d-43de-8d5d-e9cee03e4e4&title=&width=297](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709013621752-5b81823b-362d-4cdc-8b49-f941eebd827f.png#averageHue=%230e0d0d&clientId=u0fbbe02e-04ac-4&from=paste&height=288&id=ufcf1f12d&originHeight=288&originWidth=297&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5866&status=done&style=shadow&taskId=u385c19ee-7e7d-43de-8d5d-e9cee03e4e4&title=&width=297)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709013621752-5b81823b-362d-4cdc-8b49-f941eebd827f.png#averageHue=%230e0d0d&clientId=u0fbbe02e-04ac-4&from=paste&height=288&id=ufcf1f12d&originHeight=288&originWidth=297&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5866&status=done&style=shadow&taskId=u385c19ee-7e7d-43de-8d5d-e9cee03e4e4&title=&width=297)| + + + + + 通过以上测试得知:当事务隔离级别设置为串行化时,事务只能排队执行,不支持并发。 + + +### **总结可重复读的幻读问题** + +MySQL的可重复读隔离级别(默认隔离级),根据不同的查询方式,分别提出了避免幻读的方案: + +- 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读。 +- 针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读。 + +我举例了两个发生幻读场景的例子。 + +- 第一个例子:对于快照读, MVCC 并不能完全避免幻读现象。因为当事务 A 更新了一条事务 B 插入的记录,那么事务 A 前后两次查询的记录条目就不一样了,所以就发生幻读。 +- 第二个例子:对于当前读,如果事务开启后,并没有执行当前读,而是先快照读,然后这期间如果其他事务插入了一条记录,那么事务后续使用当前读进行查询的时候,就会发现两次查询的记录条目就不一样了,所以就发生幻读。 + +所以,MySQL 可重复读隔离级别并没有彻底解决幻读,只是很大程度上避免了幻读现象的发生。 +要避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事务之后,马上执行 select ... for update 这类当前读的语句,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录。 + + + + + +### 从其他事务的角度了解事务 + +读未提交:可以读到未提交的事务 + +读提交:只能读到提交后的事务 + +可重复读(mvcc):开启事务后,只能读到事务开始前的数据;即使你提交了事务,我读到的数据也还是事务开启那一刻的数据 + +> [!important] 如果想看到当前真实数据,使用for update(或其他更新语句,这些语句可能会与其他事务冲突然后进行等待),会等待其他事务完成 +> +> 新增、删除、修改,都是正常的,只要不修改相同数据或触发索引都不会影响;冲突的可能如下:新增了相同id的数据(索引冲突)、修改相同数据、删除相同数据 + +串行:按事务顺序执行 \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/MySQL/事务/image110.png b/posts/blog/编程技术/数据库/MySQL/事务/image110.png new file mode 100644 index 0000000..85a2ad0 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/事务/image110.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/事务/image23.png b/posts/blog/编程技术/数据库/MySQL/事务/image23.png new file mode 100644 index 0000000..a9c6edb Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/事务/image23.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/其他.md b/posts/blog/编程技术/数据库/MySQL/其他.md new file mode 100644 index 0000000..7a88073 --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/其他.md @@ -0,0 +1,189 @@ +--- +title: 其他 +date: 2024-10-19 +tags: [MySQL] +--- + + +数据库崩溃,迁移问题 + +只要数据库文件没损害,将目录拷出来,并使用新数据库程序启动,再导出数据即可 + +- 详情 + + 好的!我来给你用简单通俗的方式讲解一下你没懂的“方法二”。 + + **🎯 方法二的目标:** + + 你已经有旧数据库的数据文件(比如 .ibd 文件、.frm 文件等), + + 但你不想直接替换新服务器的数据目录,而是想把这些数据“提取出来”,导入一个新的干净数据库中。 + + **🧠 方法二的核心思路:** + + 旧数据库虽然坏了,但数据文件还在,我们可以强制启动数据库,从中导出数据(导成 SQL 文件),然后在新数据库中执行这些 SQL 语句来恢复数据。 + + 这就像: + + 💾“把坏机器里的 Excel 文件拷出来 → 在新电脑打开 Excel 重新保存”。 + + **🔧 步骤详细解释(以 MySQL 为例):** + + **✅ 前提准备:** + + - 你手上已经有旧数据库的文件目录,比如 /var/lib/mysql,或者你已经打包好了(比如 mysql_data_backup.tar.gz) + - 你已经在一台新的服务器上装好了 MySQL + + **📦 步骤 1:把旧数据文件放到新服务器上** + + scp mysql_data_backup.tar.gz user@new_server:/tmp/ + + 然后解压到比如 /tmp/mysql_old/ 目录中(不要覆盖现有的数据库): + + mkdir /tmp/mysql_old + + tar -xzvf mysql_data_backup.tar.gz -C /tmp/mysql_old + + **🚨 步骤 2:** + + **使用“恢复模式”启动 MySQL** + + **(重点!)** + + MySQL 有一个特殊参数叫做 innodb_force_recovery,可以强行忽略某些错误启动数据库,让你有机会导出数据。 + + 1. 编辑配置文件(通常是 /etc/my.cnf 或 /etc/mysql/my.cnf): + 在 [mysqld] 部分加上: + + innodb_force_recovery = 1 + + datadir = /tmp/mysql_old + + 2. 启动 MySQL: + + sudo systemctl restart mysqld + + 1. 如果失败,把 innodb_force_recovery 从 1 调高(最多到 6),再试。 + + **📤 步骤 3:导出数据成 SQL 文件** + + 成功启动后,用 mysqldump 把所有数据库导出为 SQL 文件: + + mysqldump -u root -p --all-databases > /tmp/all.sql + + 这个 SQL 文件里包含了所有数据库结构 + 数据。 + + **📥 步骤 4:在新数据库中导入 SQL 文件** + + 1. 把恢复模式注释掉(删掉或注释掉 innodb_force_recovery) + 2. 启动你正常配置的新数据库 + 3. 执行导入命令: + + mysql -u root -p < /tmp/all.sql + + 现在你就把“坏掉数据库里的数据”完整地导入到了新数据库中! + + **🧠 总结一句话:** + + 方法二就是通过 强制启动旧数据库 + 导出 SQL 文件 + 在新数据库中导入 来恢复数据,适用于不想拷贝原目录、只想提取数据的场景。 + + 如果你告诉我你手里有哪些文件、能不能访问原数据库的文件系统,我可以一步一步帮你操作,甚至给你生成 shell 脚本。你现在是在用 MySQL 吗?还是别的数据库? + + + + + + + +## 练习 + +1、每科分数大于80 + +```SQL +-- 每科分数大于80 +select stu.name,c.cname,s.fenshu +from sc s +join student stu on stu.sno=s.sno +join course c on c.cno = s.cno +where stu.sno= (select sno from sc group by sno having min(fenshu)>80) +``` + +2、 + +(1)查询身份证号为440401430103082的申请日期 + +```SQL +select a.g_applydate +from g_cardapply a +join g_cardapplydetail b +on a.g_applyno=b.g_applyno +where a.g_idcard= '440401430103082'; +``` + +(2)查询同一个身份证号码有两条以上记录的身份证号码及记录个数 + +```SQL +select count(g_applyno) +from g_cardapplydetail +group by g_idcard; +``` + +(3)将身份证号码为440401430103082的记录在两个表中的申请状态均改为07 + +```SQL +update g_cardapply,g_cardapplydetail +set g_state=07 +where g_applyno= +(select g_applyno +from g_cardapply +where g_idcard= '440401430103082';) +``` + +(4)删除g_cardapplydetail表中所有姓李的记录 + +```SQL +delete from g_cardapplydetail +where g_name like '李%' +``` + +3、 + +(1)统计如下:课程不及格[0~59]的多少个,良[60~80]多少个,优[81-100]多少个 + +```SQL +--全部 +select +sum(score between 0 and 59), +sum(score between 60 and 80), +sum(score between 81 and 100) +from stuscore; +--分科目 +select +subject, +sum(score between 0 and 59), +sum(score between 60 and 50), +sum(score between 81 and 100) +from stuscore group by subject +``` + +(2)计算科科及格的人的平均成绩 + +```SQL +select +stuid,avg(score) +from stuscore group by stuid having min(score)>60; +``` + + + + + +数据库设计通常分为6个阶段 +1:需求分析:分析用户的需求,包括数据、功能和性能需求; +2:概念结构设计:主要采用E-R模型进行设计,包括画E-R图; +3:逻辑结构设计:通过将E-R图转换成表,实现从E-R模型到关系模型的转换; +4:数据库物理设计:主要是为所设计的数据库选择合适的存储结构和存取路径; +5:数据库的实施:包括编程、测试和试运行; +6:数据库运行与维护:系统的运行与数据库的日常维护。) +主要讨论其中的第3个阶段,即逻辑设计。 +逻辑设计主要是建立数据库的逻辑模型,也就是关系数据模型。 \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/MySQL/其他数据库对象.md b/posts/blog/编程技术/数据库/MySQL/其他数据库对象.md new file mode 100644 index 0000000..c2c4162 --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/其他数据库对象.md @@ -0,0 +1,368 @@ +--- +title: 其他数据库对象 +date: 2024-10-14 +tags: [MySQL] +--- + + +## 存储过程 + +**什么是存储过程** + +- 过程化SQL语言,是**在普通SQL语句的基础上增加了编程语言的特点**,把数据操作语句(DML)和查询语句(DQL)组织在过程化代码中,通过逻辑判断、循环等操作实现复杂计算的程序语言 +- 每一个存储过程都是一个数据库对象,就像table和view一样,存储在数据库当中,一次编译永久有效(通过存储过程名字调用存储过程) +- 优、缺点 + - 速度快 + + ```Plain + 降低了应用服务器和数据库服务器之间网络通讯的开销 + 尤其在数据量庞大的情况下效果显著 + ``` + + - 移植性差、编写难度大、维护性差 + + ```Plain + 每一个数据库都有自己的存储过程的语法规则,这种语法规则不是通用的 + ``` + +- 实际应用少 + +### 存储过程的应用 + +**创建和调用** + +- 语法格式 + + ```SQL + create procedure 存储过程的名称() + begin + --SQL语句 + end; + ``` + +- 调用:`call 存储过程的名称();` + +**查看和删除** + +- 语法格式:查看创建存储过程的语句 + + ```SQL + show create procedure 存储过程的名称; + ``` + +- 通过数据库`information_schema`系统表`ROUTINES`查看存储过程的详细信息:存储了所有存储过程、函数、触发器的详细信息,包括名称、返回值类型、参数、创建时间、修改时间等。 + + ```SQL + select * from information_schema.routines where routine_name = 'p1'; + ``` + + ![image 25.png](其他数据库对象/image25.png) + + - 详情 + - SPECIFIC_NAME:存储过程的具体名称,包括该存储过程的名字,参数列表 + - ROUTINE_SCHEMA:存储过程所在的数据库名称 + - ROUTINE_NAME:存储过程的名称 + - ROUTINE_TYPE:PROCEDURE表示是一个存储过程,FUNCTION表示是一个函数 + - ROUTINE_DEFINITION:存储过程的定义语句 + - CREATED:存储过程的创建时间 + - LAST_ALTERED:存储过程的最后修改时间 + - DATA_TYPE:存储过程的返回值类型、参数类型等 +- 删除:`drop procedure if exists 存储过程的名称;` + +## delimiter命令 + +- 用于改变解释语句的定界符,MySQL默认使用分号`;`作为语句的定界符,使用该命令可以进行修改 + + ```SQL + --修改为//,即sql语句执行到//,便运行语句 + delimiter // + ``` + +- 防止SQL语句误执行 + +## MySQL的变量 + +### 系统变量 + +- 用于控制MySQL服务器的默认行为的参数(**配置参数**),系统变量分为全局(global)或会话(session)作用域两种 + - 全局作用域:作用于所有数据库的连接 + - 会话作用域:作用于当前连接 +- 查看:不写作用域,默认是`session` + + ```SQL + --查看所有的系统变量 + show [global|session] variables; + --模糊查询系统变量 + show [global|session] variables like ''; + --查看某个系统变量 + select @@[global|session] 系统变量名; + ``` + +- 设置(修改配置文件),可以不写@@ + + ```SQL + set [global|session] 系统变量 = 值; + set @@[global|session] 系统变量名 = 值; + ``` + + +> [!important] 无论是全局设置还是会话设置,当mysql服务重启之后,之前配置都会失效 + +- 永久配置详情 + + - 可以通过修改MySQL根目录下的`my.ini`配置文件达到永久修改的效果(`my.ini`是MySQL数据库默认的系统级配置文件,默认是不存在的,需要新建,并参考一些资料进行配置) + - windows系统是my.ini + - linux系统是my.cnf + - my.ini文件通常放在mysql安装的根目录下,如下图: + + [![](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709128860158-306db5d6-e5df-41c6-8936-a35ab1df1ab2.png#averageHue=%23fdf9f6&clientId=uc80f783a-0512-4&from=paste&height=235&id=uaa939f07&originHeight=235&originWidth=198&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5032&status=done&style=shadow&taskId=u9937fe52-2196-4484-976d-b76b7d52e29&title=&width=198)](https://cdn.nlark.com/yuque/0/2024/png/21376908/1709128860158-306db5d6-e5df-41c6-8936-a35ab1df1ab2.png#averageHue=%23fdf9f6&clientId=uc80f783a-0512-4&from=paste&height=235&id=uaa939f07&originHeight=235&originWidth=198&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5032&status=done&style=shadow&taskId=u9937fe52-2196-4484-976d-b76b7d52e29&title=&width=198) + + + 这个文件通常是不存在的,可以新建,新建后例如提供以下配置: + + ```SQL + [mysqld] + autocommit=0 + ``` + + 这种配置就表示永久性关闭自动提交机制(不建议这样做) + + +### 用户变量 + +- 用户自定义的变量,只在当前会话有效,所有的用户变量`@`开始 +- 作用:可以帮助简化复杂查询、减少查询次数,或在某个事务或会话中**临时存储计算结果** +- 赋值(创建) + + ```SQL + --使用=赋值 + set @用户变量名 = 值; + --使用:=赋值 + set @用户变量名:= 值; + --动态赋值(一个值) + select 字段 into @用户变量名 from 表名 where 条件; + ``` + +- 读取 + + ```SQL + select@用户变量名1, @用户变量名2; + ``` + + +> [!important] 注意:mysql中变量不需要声明,直接赋值就行。如果没有声明变量,直接读取该变量,返回null + +### 局部变量 + +- 用于存储过程中的局部变量,使用`declare`声明,==在begin和end之间有效== +- 变量的声明:可以赋默认值(没有赋初值不能使用) + + ```SQL + declare 变量名 数据类型 [default ...]; + ``` + +- 赋值 + + ```SQL + set 变量名 = 值; + set 变量名 := 值; + --动态 + select 字段 into 变量名 from 表名 where 条件; + ``` + + +## 存储过程的常用语句 + +### 参数 + +三种形式 + +- in:入参 +- out:出参 +- inout:即是入参,又是出参 + +```SQL +create procedure 名称 +(in 参数名 参数类型,out 参数名 参数类型,inout 参数名 参数类型) +``` + +### if语句 + +- 语法格式:条件或(条件)都可以 + + ```SQL + if 条件 then + ... + elseif 条件 then + ... + elseif 条件 then + ... + else + ... + end if; + ``` + + +### case语句 + +- 语法格式:可以分值或分条件 + + ```SQL + case + when 值1/条件1 then + .... + when 值2/条件2 then + .... + when 值3/条件3 then + .... + else + end case; + ``` + + +### while语句 + +- 语法格式 + + ```SQL + while 条件 do + 循环条件; + end while + ``` + + +### repeat循环 + +- 语法结构:效果类似do…while + + ```SQL + --条件后面没有分号 + repeat + 循环体; + until 条件 + end repeat; + ``` + + +### loop语句 + +- 语法格式:拥有跳过、跳出循环的功能 + + ```SQL + 循环名:loop + 循环体; + leave 循环名; + iterate 循环名; + end loop; + ``` + + +### 游标(cursor) + +- 是一种数据库对象,==用于在存储过程==中逐行处理查询结果,可以对一个结果集进行移动遍历 +- 使用: + - 声明:**局部变量的声明需要在游标声明之前完成** + + ```SQL + declare 游标名称 cursor for 查询语句; + ``` + + - 打开:`open 游标名称;` + - 使用:通过游标获取数据(每次读取一行) + + ```SQL + fetch 游标名称 into 变量[,变量,变量...] + ``` + + - 关闭:`close 游标名称;` + +> [!important] 由于使用游标遍历数据时,不能判断是否遍历结束,所有会导致死循环,使用异常机制解决该问题 + +### **捕捉异常并处理** + +- 语法格式 + + ```SQL + declare 处理机制 handler for 异常种类 异常发生时执行的语句 + ``` + + - 异常处理制 + - `continue`:发生异常后,程序不会终止(捕捉) + - `exit`:发生异常后,终止存储过程(上抛) + - 异常种类 + - SQLSTATE sqlstate_value,例如:SQLSTATE '02000' + - SQLWARNING,代表所有01开头的SQLSTATE + - NOT FOUND,代表所有02开头的SQLSTATE + - SQLEXCEPTION,代表除了01和02开头的所有SQLSTATE + - 执行语句:修改变量、关闭游标 +- 应用到游标上 + + ```SQL + --游标关闭后直接退出存储过程,不退出依然死循环 + declare exit handler for not found close pc; + --退出死循环,在循环外关闭游标 + declare continue handler for not found set ok = false; + ``` + + +## 存储函数 + +- 与存储过程基本一致,特点带==有返回值== +- 语法格式:参数不需要in、out + + ```SQL + create function 函数名(参数列表) returns 数据类型 [特征] + begin + 函数体 + return ...; + end; + ``` + + - 特征值 + - `deterministic`:==标记为确定性函数==(每次传入同一个参数,返回值相同),属于优化策略,直接返回缓存结果 + - `no sql`:==标记该函数不会查询数据库==,这样MySQL就可以不使用查询缓存和优化器存储 + - `reads sql data`:==标记函数需要查询数据库====,与上面反之== +- 调用:`函数名(参数)` +- 删除时,不需要参数:`drop function 函数名;` +- 返回值使用用户变量接收:`set @变量名 = 函数名(参数);` + +## 触发器 + +- 是一种数据库对象,与表相关的特殊程序 +- 它可以在特定的数据操作(insert、update、delete)触发时自动执行 + - 强制实施业务规则:插入检查 + - 数据审计:追踪数据 + - 执行特定业务操作 +- 分为两种:`before`和`after`两种 +- 语法格式 + + ```SQL + create trigger 触发器名称 + before/after insert/update/delete on 表名 for each row + begin + --触发器执行的sql语句 + end; + ``` + + - `for each row`:表示触发器在每行受到 DML 的影响之后都会执行 + - sql语句中不要包含select语句 + + ![image 1 11.png](其他数据库对象/image111.png) + +- **new和old关键字** + - new:在insert或update操作期间,new表示新表 + - old:在delete或update操作期间,old表示旧表 +- 应用:记得创建log表 + + ```SQL + drop trigger if exists ut; + create trigger ut + after update on course + for each row + begin + insert log values(now(),null,'course',new.cno,concat('id:',new.cno,'值被修改了 cname:',old.cno,'->',new.cno)); + end; + ``` + + +> [!important] 需要注意的是,触发器是一种高级的数据库功能,只有在必要的情况下才应该使用,例如在需要实施强制性业务规则时。过多的触发器和复杂的触发器逻辑可能会影响查询性能和扩展性。 \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/MySQL/其他数据库对象/image111.png b/posts/blog/编程技术/数据库/MySQL/其他数据库对象/image111.png new file mode 100644 index 0000000..7c4fdb9 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/其他数据库对象/image111.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/其他数据库对象/image25.png b/posts/blog/编程技术/数据库/MySQL/其他数据库对象/image25.png new file mode 100644 index 0000000..02efb1b Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/其他数据库对象/image25.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/存储引擎.md b/posts/blog/编程技术/数据库/MySQL/存储引擎.md new file mode 100644 index 0000000..91de242 --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/存储引擎.md @@ -0,0 +1,73 @@ +--- +title: 存储引擎 +date: 2024-10-15 +tags: [MySQL] +--- + + +- 存储引擎决定了数据在磁盘上的==**存储方式和访问方式**==,面对不同需求选择正确的存储引擎 +- MySQL常见存储引擎 + - InnoDB:支持事务和行级锁,适用于高并发读写应用 + - MyISAM:不支持事务,适用于读操作较多的应用 + - Memory:全部存储在内存中,适用于读写速度要求高的应用 + - Archive:只支持插入和查询,自动数据压缩,适用于存储大量历史记录 +- 查看数据库支持哪些存储引擎 + + ```SQL + show engines \g; + ``` + + +### 指定存储引擎 + +- 创建表时,指定存储引擎,不写==默认InnoDB== + + ```SQL + create table 表名 + ( + --字段、约束 + ) + engine = 存储引擎; + ``` + + +### 修改存储引擎 + +- 通过alter语句,修改存储引擎 + + ```SQL + alter table 表名 engine = 存储引擎; + ``` + +- 修改存储引擎前 + 1. 执行修改之前备份你的数据,因为可能会造成数据的丢失或不可用 + 2. 不是所有的存储引擎都支持相同的功能,要确保你选择的新存储引擎支持你应用程序所需的功能 + 3. 修改表的存储引擎可能会影响到现有的应用程序和查询,确保在修改之前评估和测试所有的影响 + +总而言之,==修改存储引擎需要谨慎进行==,且需要考虑到可能的影响和风险。建议在进行修改之前进行适当的测试和备份。 + +### **常用的存储引擎及适用场景** + +在实际开发中,以下存储引擎是比较常用的: + +- InnoDB: + - MySQL默认的事务型存储引擎 + - 支持ACID事务 + - 具有较好的并发性能和数据完整性 + - 支持行级锁定 + - 适用于大多数应用场景,尤其是需要事务支持的应用。 +- MyISAM: + - 是MySQL早期版本中常用的存储引擎 + - 支持全文索引和表级锁定 + - 不支持事务 + - 由于其简单性和高性能,在某些特定的应用场景中会得到广泛应用,如**读密集**的应用。 +- MEMORY: + - 称为HEAP,是将表存储在内存中的存储引擎 + - 具有非常高的读写性能,但数据会在服务器重启时丢失。 + - 适用于需要快速读写的临时数据集、缓存和临时表等场景。 +- CSV: + - 将数据以纯文本格式存储的存储引擎 + - 适用于需要处理和导入/导出CSV格式数据的场景。 +- ARCHIVE: + - 将数据高效地进行压缩和存储的存储引擎 + - 适用于需要长期存储大量历史数据且不经常查询的场景。 \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/MySQL/数据库概述.md b/posts/blog/编程技术/数据库/MySQL/数据库概述.md new file mode 100644 index 0000000..093b267 --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/数据库概述.md @@ -0,0 +1,94 @@ +--- +title: 数据库概述 +date: 2024-10-10 +tags: [MySQL] +--- + + +## 概述 + +### 什么是数据库 + +- 数据仓库,简称DB(DataBase) + +### 数据库类型 + +- 关系型数据库 + - 表之间存在关系模型,“一对一、一对多”等关系 +- 非关系型数据库(NoSQL) + + +### 数据库管理系统 + +- 是为管理数据库而设计的应用软件,简称DBMS(DataBase Management System),一般具有存储、截取、安全保障、备份等功能 +- 常见数据库管理系统: + - MySQL - 免费,用的最多的,开源数据库,适用于中小型 + - Microsoft SQL Server - 收钱的,但是提供技术支持,适用于Windows Server + - Oracle - 收钱的,大型数据库系统 + +### 什么是SQL + +- 结构化查询语言(Structured Query Language),简称SQL,是一种特殊目的的编程语言 +- 分类 + - **DDL** + - 数据定义语言 (**Data Definition Language, DDL**) 是SQL语言集中,负责数据结构定义与数据库对象定义的语言,由==**create、alter与drop**==三个语法所组成,最早是由 Codasyl (Conference on Data Systems Languages) 数据模型开始,现在被纳入 SQL 指令中作为其中一个子集 + - **DML** + - 数据操纵语言(**Data Manipulation Language, DML**)是SQL语言中,负责对数据库对象运行数据访问工作的指令集,以==**insert、update、delete**==三种指令为核心,分别代表插入、更新与删除 + - **DQL** + - 数据查询语言(**Data Query Language, DQL**)是SQL语言中,负责进行数据查询而不会对数据本身进行修改的语句,这是最基本的SQL语句。保留字==**select**==是DQL(也是所有SQL)用得最多的动词,其他DQL常用的保留字有FROM,WHERE,GROUP BY,HAVING和ORDER BY。这些DQL保留字常与其他类型的SQL语句一起使用 + - DCL + - 数据控制语言 (Data Control Language) 在SQL语言中,是一种可对数据访问权进行控制的指令,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。由 ==GRANT 和 REVOKE== 两个指令组成。DCL以控制用户的访问权限为主,GRANT为授权语句,对应的REVOKE是撤销授权语句 + - TPL + - 数据==事务管理==语言(Transaction Processing Language)它的语句能确保被DML语句影响的表的所有行及时得以更新。TPL语句包括BEGIN TRANSACTION,COMMIT和ROLLBACK + - CCL + - 指针控制语言(Cursor Control Language),它的语句,像DECLARE CURSOR,FETCH INTO和UPDATE WHERE CURRENT用于对一个或多个表单独行的操作 +- DBMS、SQL、DB之间的关系 + - DBMS(MySQL级别)通过执行SQL来操作DB中的数据 + +--- + +## 数据库表的概述 + +|name|age|gender| +|---|---|---| +|张三|20|男| +|李四|22|女| + +- 表(table)是数据存储的基本单元 +- 行和列 + - 行:记录(一行就是一条数据) + - 列:字段(name字段、age字段、gender字段) +- 字段包含以下属性 + - 字段名:name、age、gender都是字段名字 + - 字段数据类型:数据类型(字符类型、数字类型) + - 数据长度:每个字段可能有长度限制 + - 约束:数据不能重复、不能为空等约束 + +## **MySQL命令行基本命令** + +### 引入数据 + +**sql脚本:**文件名为.sql结尾,该文件编写了大量的SQL语句,使用该文件初始化数据库数据 + +- 在控制台,登录MySQL +- 创建数据库:`create database 数据库名` +- 使用该数据库:`use 数据库名` +- 使用source命令执行sql脚本:`source E:\powernode.sql` + + - 路径别携带中文和特殊符号 + + ![1620435073900-d9e19c5e-9b0e-4d09-a3ee-74471ec9ebb8.png](数据库概述/1620435073900-d9e19c5e-9b0e-4d09-a3ee-74471ec9ebb8.png) + +- 使用其他的mysql客户端工具也可以执行sql脚本,比如navicat。使用source命令执行sql脚本的优点:**可支持大文件** + +### 数据库基本操作 + +- 查看数据库:`show databases;` +- 创建数据库:`create database 数据库名` +- 使用数据库:`use 数据库名;` +- 删除数据库:`drop database 数据库名;` +- 查看当前使用的数据库:`select database();` +- 查看表:`show tables;` +- 查看表结构:`desc/describe 表名` +- 退出MySQL:`exit`、`quit`、`ctrl+c` +- 查看版本:`select version();` 、`mysql --verison` 命令行查看 \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/MySQL/数据库概述/1620435073900-d9e19c5e-9b0e-4d09-a3ee-74471ec9ebb8.png b/posts/blog/编程技术/数据库/MySQL/数据库概述/1620435073900-d9e19c5e-9b0e-4d09-a3ee-74471ec9ebb8.png new file mode 100644 index 0000000..5c5cdf0 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/数据库概述/1620435073900-d9e19c5e-9b0e-4d09-a3ee-74471ec9ebb8.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询.md b/posts/blog/编程技术/数据库/MySQL/查询.md new file mode 100644 index 0000000..447a5a3 --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/查询.md @@ -0,0 +1,946 @@ +--- +title: 查询 +date: 2024-10-06 +tags: [MySQL] +--- + + +### 简单查询 + +- 语法结构:`select 字段名,字段名,字段名 from 表名;` + - 必须以”;“结尾 + - 大小写都可以 +- 当语句书写错误,使用`\c`终止 + +![image 12.png](查询/image12.png) + +- 使用`*`表示所有字段,在Java中不使用,效率低 +- 查询字段**可参与数学运算** + +![image 1 8.png](查询/image18.png) + +- 起别名,使用`as`关键字或省略`as`空格分割 + + - 当别名中有空格时,使用单引号括起来(双引号也行) + + ![image 2 4.png](查询/image24.png) + + ![image 3 3.png](查询/image33.png) + + +### 条件查询 + +- 语法格式 + + ```SQL + select + ... + from + ... + where + 过滤条件; + ``` + + - 执行顺序是:from、where、select +- 常见条件 + + |**条件**|**说明**| + |---|---| + |=|等于| + |<>或!=|不等于| + |>=|大于等于| + |<=|小于等于| + |>|大于| + |<|小于| + |between...and...|等同于 >= and <=| + |is null|为空| + |is not null|不为空| + |<=>|安全等于(可读性差,很少使用了)。| + |and 或 &&|并且| + |or 或 \||或者| + |in|在指定的值当中| + |not in|不在指定的值当中| + |exists|| + |not exists|| + |like|模糊查询| + + +使用细节 + +- 使用`=`条件,发现MySQL并不会区分数据的大小写 +- 当查询条件是,XX年级**和**XX年级的学生时,使用的是`**or**`**条件**,表示不是XX年级就是XX年级,不能理解为和(and) +- `and`和`or`的优先级,`**and**`的优先级更高,所有同时使用时怕出现逻辑错误**直接打括号** +- `between…and…`:等同于 `>= and ≤`,支持数字、日期、字符等数据类型,包含左右两个边界值,左小右大 +- `is null`、`is not null` 判断是否为null的唯一写法 +- `in`等同于`or` ,`not in`等同于`and` + + ```SQL + //in + sal in(1600, 3000, 5000) 等同于 + sal = 1600 or sal = 3000 or sal = 5000 + //not in + job not in('MANAGER','SALESMAN') 等同于 + job <> 'MANAGER' and job <> 'SALESMAN' + ``` + + - `in`、`not in`与NULL:不能搭配 + - `comm in(NULL, 300)`=`comm = NULL or comm = 300` + + `comm = NULL` 为false,后面是`or`所以无影响 + + - `comm not in(NULL, 300)` = `comm <> NULL and comm <> 300` + + `comm <> NULL` 为false,但后面是`and`所以一定查询不到数据 + + - `in`和`or`的效率问题,`or`的效率为O(n),而`in`的效率为O(log n), 当n越大的时候效率相差越明显(**也就是说数据量越大的时候,**==**in**==**的效率越高**) +- 模糊查询,`字段` `**like**` `‘通配符表达式’` ,如果要查询包含‘_’、’%’字符的数据,可以使用`\`转义 + - `**_**`表示一个字符 + - `**%**`表示0-任意个字符 + +### 排序操作 + +- 语法:`select .. from .. order by 字段 asc/desc` +- 多个字段排序:`select .. from .. order by 字段1 asc/desc,字段2 asc/desc` 当字段1相同,比较字段2 +- 默认升序(asc),where和order by一起,where先执行,后排序 + +### **distinct去重** + +- 语法:`select distinct 字段 from 表;` +- `distinct`必须放在字段最前面,并且所有(查看)字段相同才去重 + +## 数据处理函数 + +**前置知识** + +- `select`可以直接操作常量(字面量) + +![image 4 3.png](查询/image43.png) + +### **字符串相关** + +**大小写转换** + +- 转大写:`upper`和`ucase` +- 转小写:`lower`和`lcase` + + ![image 5 3.png](查询/image53.png) + + +**截取字符串** + +- `substr('str', 起始下标, 截取长度)` +- `substr('被截取的字符串', 起始下标)`,截取到字符串末尾 + +> [!important] 注意:起始下标从**1**开始,不是从0开始(1表示从左侧开始的第一个位置,-1表示从右侧开始的第一个位置) + +![image 6 2.png](查询/image62.png) + +**获取字符串长度** + +- 获取字符串个数:`char_length` +- 获取字符串大小占的字节数:`length` + + ![image 7 2.png](查询/image72.png) + + +**字符串拼接** + +- 语法:`concat(’’str1,’str2’,’str3’…)` + + ![image 8 2.png](查询/image82.png) + + +**去除字符串前后空白** + +- 去除前后空白:`trim(’ str1 ’)` +- 去除前后指定字符:`trim(both ‘0’ from ‘00011000’)` +- 去除指定前缀:`trim(leading ‘x’ from ‘xxxx123’)` +- 去除指定后缀:`trim(trailing ‘0’ from ‘1111000’)` + +### 数字相关 + +**生成随机数** + +- 生成0到1的随机浮点数:`rand()` +- 生成指定浮点数:`rand(1)` + +**数字保留** + +- 保留小数位:`truncate(9,99,1)` +- 保留小数四舍五入:`round(2.42,2)` +- 取整 + - `ceil`函数:返回大于或等于数值x的最小整数(进位) + - `floor`函数:返回小于或等于数值x的最大整数(去零头) +- 取整四舍五入:`round(2.1)` + +### 空处理 + +- null运算,结果一定位null,所有要将null进行代替处理 +- `ifnull(x,y)` ,当x为null时,则当成y处理 + + ![image 9 2.png](查询/image92.png) + + +### 日期和时间相关函数 + +- 日期:年、月、日 +- 时间:时、分、秒 + +**获取日期和时间** + +- 当前日期和时间 + - `**now()**`:获取的是执行select语句的时刻 + - `sysdate()`:获取的是执行sysdate()函数的时刻 +- 当前日期:`**curdate()**`、`current_date()`、`current_date` +- 当前时间:`**curtime()**`、`current_time()`、`current_time` +- 获取单独的年、月、日、时、分、秒 + - `year()`、`month()`、`day()`、`hour()`、`minute()`、`second()` + +![image 10 2.png](查询/image102.png) + +**时间间隔** + +- 添加时间:`date_add(日期, interval expr 单位)` + - 日期:一个日期类型的数据 + - interval:关键字,翻译为“间隔”,固定写法 + - expr:指定具体的间隔量,一般是一个数字。**也可以为负数,如果为负数,效果和date_sub函数相同**。 + - 单位: + - year:年 + - month:月 + - day:日 + - hour:时 + - minute:分 + - second:秒 + - microsecond:微秒(1秒等于1000毫秒,1毫秒等于1000微秒) + - week:周 + - quarter:季度 +- 使用复合单位:`date_add(日期,interval '3.2' day_hour)` + - SECOND_MICROSECOND + - MINUTE_MICROSECOND + - MINUTE_SECOND:几分几秒之后 + - HOUR_MICROSECOND + - HOUR_SECOND + - HOUR_MINUTE:几小时几分之后 + - DAY_MICROSECOND + - DAY_SECOND + - DAY_MINUTE + - DAY_HOUR:几天几小时之后 + - YEAR_MONTH:几年几个月之后 + +**格式化日期** + +- 语法格式:`date_format(日期, '日期格式')` + + - 第一个参数:date类型 + + - 第二个参数:`%Y-%m-%d %H:%i:%s` + + - %Y:四位年份 + - %y:两位年份 + - %m:月份(1…12) + - %d:日(1…30) + - %H:小时(0…23) + - %i:分(0…59) + - %s:秒(0…59) + + + + +**字符串转日期** + +- 当插入日期时,使用字符串格式会自动类型转换,变成日期类型 + + ![image 11 2.png](查询/image112.png) + + 格式为:%Y-%m-%d %H:%i:%s + +- 手动设置格式,字符串转日期 + + ![image 12 2.png](查询/image122.png) + + +**XXX的第X天** + +- 一周的第X天:`dayofweek(now())` +- 一月的第X天:`dayofmonth(now())` +- 一年的第X天:`dayofyear(now())` +- 日期所在月的最后一天:`last_day(now())` + +**时差** + +- 相差几天:`datediff(’2024-10-6’,’2024-11-2’)` +- 相差时、分、秒:`timediff(’2024-1-1 1:1:1’,’2024-1-1 2:1:1’)` + +> 前后时间无要求 + +### if函数 + +- 类似三目运算符:`if(10>20,’yes’,’no’)` +- 应用:如果是xxx,显示xxx,不是就正常显示 + + ![image 13.png](查询/image13.png) + +- 可以使用`case…when…then…else…end`代替(适合多条件) + + ```SQL + select name,job + case job + when '学生' then sal*5 + when '老师' then sal*12 + else sal + end + as sal + from emp; + ``` + + +### cast函数 + +- 类型转换函数:`cast(值 as 数据类型)` + +- 可用的数据类型包括: + - date:日期类型 + - time:时间类型 + - datetime:日期时间类型 + - signed:有符号的int类型(有符号指的是正数负数) + - char:定长字符串类型 + - decimal:浮点型 + +### 加密函数 + +- 使用md5算法加密处理:`md5(’字符串’)` +- 无法解密 + +## 分组函数 + +分组的概念可以理解为 + +分组函数的执行原则:**先分组,然后对每一组数据执行分组函数**。如果==没有分组语句group by的话,====**整张表的数据自成一组**== + +- 五个分组函数,所有的分组函数都是自动忽略NULL的 + - max:最大值 + - min:最小值 + - avg:平均值 + - sum:求和(可以计算布尔表达式的结果) + - count:计数 + - count(*)包含了null,即所有数据,设置具体字段不统计null + +> [!important] ==**分组函数不能直接使用在where子句当中**====,原因:分组的行为是在where执行之后才开始的,即执行where时,数据未分组== + +## 分组查询 + +### group by + +- 按某个字段分组,在where之后执行,可以多字段分组 + + ```SQL + group by 字段; + group by 字段1,字段2; + ``` + +- 当select语句中有`group by`的化,select后面**只能有参加分组的字段和分组函数**,可以理解为map只要key,和对value_list的操作,也就是一个组一行数据 + +### having + +- 必须写在`group by`后面,是对分组后的数据进行过滤 +- 与`where`的不同:一个是分组前、一个是分组后 + + ![image 14.png](查询/image14.png) + + +### 组内排序 + +- 由于分组后,一个组只有一行数据,所以要将排序内容进行整合成字符串 + - `group_concat(保留字段 order by 排序字段 desc)` + - 该函数会根据排序字段排序,然后使用`,`将保留字段拼接 +- 有时候只需要排序的部分内容,所以要对排序内容进行截取 + - `substring_index(’字符串’,’,’,’1’)` + - 其中`,`表示分隔符,`1`表示截至到第几个分隔符 + +## 单表DQL语句总结 + +- 执行顺序,`order by`最后,`from`第一 + +```SQL +select.......5 +from.........1 +where........2 +group by.....3 +having.......4 +order by.....6 +``` + +- 详情 + + 这是一个非常经典且重要的 SQL 面试题:**SQL 语句中各个子句的执行顺序**。 + + 你给出的 SQL: + + ```SQL + SELECT A + FROM B + WHERE C + GROUP BY D + HAVING F -- (虽然没写,但通常和 GROUP BY 一起出现) + ORDER BY E; + ``` + + 我们来详细说明 **从逻辑上 SQL 引擎是如何一步步执行这些子句的**,也就是它们的 **实际执行顺序(Evaluation Order)**。 + + --- + + ## ✅ SQL 执行顺序(逻辑执行顺序) + + > ⚠️ 注意:这是 逻辑上的执行顺序,不是你写的顺序(书写顺序是 SELECT-FROM-WHERE...),而是数据库引擎 处理数据的步骤顺序。 + + |步骤|子句|说明| + |---|---|---| + |1|`FROM`|先确定数据来源,加载表 B 的数据(可能涉及 JOIN)| + |2|`WHERE`|对 FROM 的结果进行行级过滤,保留满足条件 C 的记录| + |3|`GROUP BY`|将 WHERE 过滤后的数据按 D 字段分组| + |4|`HAVING`|对分组后的结果进行过滤(只能用聚合函数或分组字段)| + |5|`SELECT`|选择要返回的列(如 A),计算表达式、别名等| + |6|`ORDER BY`|对最终结果排序,可以使用 SELECT 中的别名| + + --- + + ### 🔁 举个例子说明流程 + + 假设我们有这张表 `orders`: + + |id|customer|amount|order_date| + |---|---|---|---| + |1|Alice|100|2025-03-15| + |2|Bob|200|2025-03-20| + |3|Alice|150|2025-04-01| + |4|Bob|300|2025-04-05| + + 执行如下 SQL: + + ```SQL + SELECT customer, SUM(amount) AS total + FROM orders + WHERE order_date >= '2025-03-01' + GROUP BY customer + HAVING SUM(amount) > 200 + ORDER BY total DESC; + ``` + + ### 执行步骤分解: + + |步骤|操作|结果| + |---|---|---| + |1. `FROM orders`|加载所有订单数据|4 条记录| + |2. `WHERE ...`|只保留 2025-03-01 后的数据|仍为 4 条(都符合)| + |3. `GROUP BY customer`|按 customer 分组|[Alice: 100+150], [Bob: 200+300]| + |4. `HAVING SUM(amount) > 200`|过滤分组|Alice(250)✅, Bob(500)✅ → 都保留| + |5. `SELECT`|计算 `SUM(amount)` 并取 `customer`|输出两行:Alice/250, Bob/500| + |6. `ORDER BY total DESC`|按 total 降序排列|Bob(500), Alice(250)| + + --- + + ## 📌 为什么理解执行顺序很重要? + + 1. ❌ 你在 `WHERE` 中不能用 `SUM()` —— 因为 `WHERE` 在 `GROUP BY` 之前执行 + + ✅ 应该用 `HAVING` + + 2. ✅ 你可以在 `ORDER BY` 中使用 `SELECT` 中定义的别名(如 `total`),因为 `ORDER BY` 最后执行 + 3. ✅ `SELECT` 中的列如果不在 `GROUP BY` 中,也不能是非聚合字段(MySQL 有些宽松模式除外) + + --- + + ## ✅ 总结:A ~ E 的执行顺序(对应你的问题) + + 你问的是: + + > SELECT A FROM B WHERE C GROUP BY D ORDER BY E + > + > A~E 的执行顺序? + + 这里的 A~E 是占位符,对应: + + - A → `SELECT` + - B → `FROM` + - C → `WHERE` + - D → `GROUP BY` + - E → `ORDER BY` + + 所以它们的 **实际执行顺序是**: + + ### 🔢 执行顺序: + + ```Plain + B (FROM) + ↓ + C (WHERE) + ↓ + D (GROUP BY) + ↓ + A (SELECT) + ↓ + E (ORDER BY) + ``` + + --- + + ### 🧩 补充:完整逻辑执行顺序(含其他常见子句) + + |顺序|子句|说明| + |---|---|---| + |1|`FROM` / `JOIN`|从哪张表读数据| + |2|`WHERE`|行过滤| + |3|`GROUP BY`|分组| + |4|`HAVING`|分组后过滤| + |5|`SELECT`|选择字段、计算表达式| + |6|`DISTINCT`|去重(在 SELECT 后立即处理)| + |7|`ORDER BY`|排序| + |8|`LIMIT` / `OFFSET`|分页| + + --- + + ✅ **结论一句话总结:** + + > SQL 的书写顺序是 SELECT -> FROM -> WHERE -> GROUP BY -> ORDER BY, + > + > 但 **执行顺序是:**`**FROM → WHERE → GROUP BY → SELECT → ORDER BY**` + + 理解这一点,能帮你写出更正确、高效的 SQL! + + +## 连接查询 + +**什么是连接查询** + +- 两张以上的表联合查询称为多表查询,又叫连接查询 + +**连接查询的分类** + +- 内连接 + - 等值连接 + - 非等值连接 + - 自连接 +- 外连接 + - 左外连接 + - 右外连接 +- 全连接 + +### 笛卡尔积现象 + +- 当两张表进行连接查询时,自动进行笛卡尔积连接(a表行数*b表行数),所以通常我们需要添加条件将无效数据过滤 + +### 内连接 + +**什么叫内连接** + +![c2966305-0cbb-461a-b78d-d20bc55670c9.png](查询/c2966305-0cbb-461a-b78d-d20bc55670c9.png) + +- 满足条件的记录才会出现在结果集中 + +**等值连接** + +- 写法:inner可以省略 + +```SQL +select 字段1,字段2 +from 表1 +inner join 表2 +on 条件1=条件2 +``` + +- 隐式内连接:`select 字段 from 表1,表2 where 条件1=条件2` + - `,`代替`join` + - `where`代替`on` + +**非等值连接** + +- 条件不再是相等,可能是范围关系 + + ![image 15.png](查询/image15.png) + + +**自连接** + +- 自己与自己连接查询,父级字段常用 + + ![image 16.png](查询/image16.png) + + +### 外连接 + +**什么叫外连接** + +- 将连接的表其中一张表的数据全部查询,没有满足的数据匹配则与NULL匹配 + +**左连接** + +![image 17.png](查询/image17.png) + +- 保留左边表:`select…from a left join b on a.id=b.id;` + +**右连接** + +![image 18.png](查询/image18.png) + +- 保留右边表:`select…from a right join b on a.id=b.id;` + +### 全连接 + +- MySQL不支持`full join`,oracle数据库支持 +- 两种表全部查询出来,没有匹配的数据为null + +![image 19.png](查询/image19.png) + +### 多张表连接 + +- 多张表进行连接,在前一张表的基础上添加`join on` + +```SQL +select e.ename,d.dname,s.grade +from emp e +join dept d +on e.deptno = d.deptno +join salgrade s +on e.sal between s.losal and s.hisal; +``` + +## 子(嵌套)查询 + +**什么是子查询** + +- select语句嵌套select语句,可以嵌套的位置 + + ```SQL + select ..(select).. + from ..(select).. + where ..(select).. + ``` + + +**where后面使用子查询** + +- where后面不能使用分组函数,通常使用子程序解决 + + ![image 20.png](查询/image20.png) + + +**from后面使用子查询** + +- 当作一张临时表来处理 + +**select后面使用子查询** + +- 当作一个字段 + +### exists、not exists + +- 从主表进行子查询遍历,子查询有结果,则将主表中的行进行显示 + + ![image 21.png](查询/image21.png) + +- 常常用于查询关联数据,放在where后面 +- **in与exists的区别** + - `in` 用于匹配一个列表或一个子查询的结果 + - `in`操作符可同时匹配多个值,而`exists`只能匹配一组条件 + - 对于大型表,`exists`通常比`in`子句更高效,因为`exists`只需要判断是否存在符合条件的记录,找到第一个匹配后就会停止搜索 + +## union&union all + +- 将两个结果集,进行拼接,所有字段必须一致 +- `union all`和`union`都可以完成的话,优先选择`union all`,`union all`因为不需要去重,所以效率高一些 +- `or`也可以完成相同功能,两者区别,考虑走**索引优化之类**的选择union all,其它选择or +- 分页的作用,语法格式:`limit 开始下标,长度` + - 位置处于sql语句的最后 + - 开始下标是指第几条,从0开始算 +- 通用分页 + - 第1页:`limit 0, 3` + - 第pageNo页:`limit (pageNo - 1)*pageSize, pageSize` + + + + + +1、取得每个部门最高薪水的人员名称 + +```SQL +//使用连接 +select a.ename from emp a join (select deptno,max(sal) sal from emp group by deptno) b on a.sal=b.sal and a.deptno=b.deptno; +//使用exists 并列的可以计算出来 +select a.ename from emp a where exists(select 1 from (select deptno,max(sal) sal from emp group by deptno) b where a.sal=b.sal and a.deptno=b.deptno); +//使用分组排序 +select substring_index(group_concat(ename order by sal desc),',',1) max_sal_name from emp group by deptno; +``` + +2、哪些人的薪水在部门的平均薪水之上 + +```SQL +//exists +select * from emp a where exists(select 1 from (select deptno,avg(sal) sal from emp group by deptno) b where a.sal>b.sal and a.deptno=b.deptno); +//连接 +select * from emp a join (select deptno,avg(sal) sal from emp group by deptno) b on a.sal>b.sal and a.deptno=b.deptno; +``` + +3、取得每个部门平均薪水的等级 + +```SQL +select b.deptno,a.grade from salgrade a join (select deptno,avg(sal) sal from emp group by deptno) b on b.sal between a.losal and a.hisal; +``` + +4、取得部门中(所有人的)平均的薪水等级 + +```SQL +select * from emp a join salgrade b on a.sal between b.losal and b.hisal; + + +select t.deptno,avg(t.grade) from (select * from emp a join salgrade b on a.sal between b.losal and b.hisal) t group by t.deptno; +``` + +5、不准用组函数(Max),取得最高薪水(给出两种解决方案) + +```SQL +//排序 +select sal from emp order by sal desc limit 0,1; +//分组排序 +select substring_index(group_concat(sal order by sal desc),',',1) max_sal from emp; +//自连接,求出最大之外的数据,not in求最大 +``` + +6、取得平均薪水最高的部门的部门编号(至少给出两种解决方案) + +```SQL +//求出max,还要嵌套 +select max(a.sal) from (select deptno,avg(sal) sal from emp group by deptno) a; +//分组、排序 +select deptno,avg(sal) sal from emp group by deptno order by sal desc limit 0,1; +//组内排序 +select substring_index(group_concat(a.deptno order by a.sal desc),',',1) from (select deptno,avg(sal) sal from emp group by deptno)a; +``` + +7、取得平均薪水最高的部门的部门名称 + +```SQL +select dname from dept a where a.deptno = (select deptno sal from emp group by deptno order by avg(sal) desc limit 0,1); +//也可以先将表连接,然后通过部门名称分组,重复上面的思路 +``` + +8、求平均薪水的等级最低的部门的部门名称 + +```SQL +select dname from dept where deptno = (select deptno from emp group by deptno order by avg(sal) limit 0,1); +``` + +9、求每个部门的平均薪水等级(将以上的执行结果当做临时表t,t和salgrade s表进行连接,条件:t.avgsal between .s.losal and s.hisal) + +```SQL +select t.deptno,s.grade from salgrade s join (select avg(sal) avgsal,deptno from emp group by deptno) t on t.avgsal between s.losal and s.hisal; +``` + +10、找到最低的部门名称(以上结果继续按照grade进行升序,然后limit 1) + +```SQL +select d.dname from dept d join (select t.deptno,s.grade from salgrade s join (select avg(sal) avgsal,deptno from emp group by deptno) t on t.avgsal between s.losal and s.hisal) x where d.deptno=x.deptno order by x.grade limit 0,1; +``` + +11、取得比普通员工(员工代码没有在mgr字段上出现的)的最高薪水还要高的领导人姓名 + +```SQL +//求普通员工的最高工资 +select max(a.sal) from emp a where not exists (select 1 from emp b where a.empno=b.mgr); +select max(sal) from emp where empno not in(select distinct mgr from emp where mgr is not null) +//不需要判断是否为领导,工资大于最高的一定是领导 +select a.ename from emp a where exists (select 1 from emp b where a.empno=b.mgr) and a.sal>(select a.sal from emp a where not exists (select 1 from emp b where a.empno=b.mgr) order by sal desc limit 0,1); +``` + + +12、取得薪水最高的前五名员工 + +```SQL +select a.ename,a.sal from emp a where not exists (select 1 from emp b where a.empno=b.mgr) order by sal desc limit 0,5; +``` + +13、取得薪水最高的第六到第十名员工 + +```SQL +select a.ename,a.sal from emp a where not exists (select 1 from emp b where a.empno=b.mgr) order by sal desc limit 5,5; +``` + +14、取得最后入职的5名员工 + +```SQL +select a.ename,a.sal from emp a where not exists (select 1 from emp b where a.empno=b.mgr) order by a.hiredate desc limit 0,5; +``` + +15、取得每个薪水等级有多少员工 + +```SQL +//不考虑普不普通 +select grade,count(*) sum from salgrade left join emp on sal between losal and hisal group by grade; +``` + +16、列出所有员工及领导的姓名 + +```SQL +select a.ename,b.ename from emp a left join emp b on a.mgr=b.empno; +``` + + +17、列出受雇日期早于其直接上级的所有员工的编号,姓名,部门名称 + +```SQL +select c.empno,c.ename,d.dname from (select a.empno,a.ename,a.deptno from emp a join emp b on a.mgr=b.empno where a.hiredate > b.hiredate) c join dept d on c.deptno=d.deptno; +``` + + +18、列出部门名称和这些部门的员工信息,同时列出那些没有员工的部门 + +```SQL +select d.dname,e.* from dept d left join emp e on d.deptno=e.deptno; +``` + + +19、列出至少有5个员工的所有部门 + +```SQL +select dname from dept where deptno=(select deptno from emp group by deptno having count(*) >5); +``` + + +20、列出薪金比"SMITH"多的所有员工信息 + +```SQL +select * from emp where sal>(select sal from emp where ename='smith'); +``` + + +21、列出所有"CLERK"(办事员)的姓名及其部门名称,部门的人数 + +```SQL +select a.ename,b.dname,c.count from +(select ename,deptno from emp where job='clerk') a +join (select dname,deptno from dept) b on a.deptno=b.deptno +join (select deptno,count(*) count from emp group by deptno) c on c.deptno=b.deptno; +``` + + +22、列出最低薪金大于1500的各种工作及从事此工作的全部雇员人数 + +```SQL + select e.job,count(*) from (select * from emp where sal>1500) e group by e.job; +``` + + +23、列出在部门"SALES"<销售部>工作的员工的姓名,假定不知道销售部的部门编号 + +```SQL +select ename from emp a join dept b on a.deptno = b.deptno; +``` + + +24、列出薪金高于公司平均薪金的所有员工,所在部门,上级领导,雇员的工资等级 + +```SQL +select avg(sal) from emp; +select a.dname,b.ename,c.grade from dept a +join emp b on a.deptno=b.deptno +join salgrade c on b.sal between c.losal and hisal +where b.sal>(select avg(sal) from emp); +``` + + +25、列出与"SCOTT"从事相同工作的所有员工及部门名称 + +```SQL +select job from emp where ename='scott'; +select a.*,b.dname from emp a +join dept b on a.deptno = b.deptno +where a.job = (select job from emp where ename='scott'); +``` + + +26、列出薪金等于部门30中员工的薪金的其他员工的姓名和薪金 + +```SQL +select a.ename,a.sal from emp a where exists +(select 1 from emp b where b.deptno =30 and a.deptno!=30 and a.sal=b.sal ); +``` + + +27、列出薪金高于在部门30工作的所有员工的薪金的员工姓名和薪金.部门名称 + +```SQL +select sum(sal) from emp where deptno=30; +select a.ename,a.sal,b.dname from emp a +join dept b on a.deptno = b.deptno +where a.sal>(select sum(sal) from emp where deptno=30); +``` + + +28、列出在每个部门工作的员工数量,平均工资和平均服务期限 + +```SQL +select deptno,count(sal) 人数,truncate(avg(sal)) '平均薪资(元)',avg(datediff(now(),hiredate)) '平均工作时长(天)' from emp group by deptno; +``` + + +29、列出所有员工的姓名、部门名称和工资 + +```SQL +select ename,dname,sal from emp a +join dept b on a.deptno=b.deptno; +``` + + +30、列出所有部门的详细信息和人数 + +```SQL +select a.*,b.count from dept a +left join (select deptno,count(*) count from emp group by deptno) b on a.deptno=b.deptno; +``` + + +31、列出各种工作的最低工资及从事此工作的雇员姓名 + +```SQL +select ename from emp a join +(select deptno,min(sal) min from emp group by deptno) b on a.deptno=b.deptno and a.sal=min; +``` + + +32、列出各个部门的MANAGER(领导)的最低薪金 + +```SQL +//找出领导并联立部门 +select * from dept left join (select * from emp where job='manager') b on dept.deptno=b.deptno; +//分部门,获取薪水 +select c.deptno,min(c.sal) min from (select a.deptno,b.sal from dept a left join (select * from emp where job='manager')b on a.deptno=b.deptno) c group by c.deptno; +``` + + +33、列出所有员工的年工资,按年薪从低到高排序 + +```SQL +select ename,(sal+ifnull(comm,0))*12 yearsal from emp order by yearsal; +``` + + +34、求出员工领导的薪水超过3000的员工名称与领导名称 + +```SQL +select a.ename,b.ename boss from emp a join emp b on a.mgr=b.empno and a.sal >= 3000 and b.sal>=3000; +``` + + +35、求出部门名称中,带'S'字符的部门员工的工资合计、部门人数 + +```SQL +select * from emp where ename like '%S%'; +select s.deptno,count(*),sum(sal) from (select * from emp where ename like '%S%') s group by s.deptno; +``` + + +36、给任职日期超过30年的员工加薪10% + +```SQL +//不能乘110%,是无效的 +select ename,sal*1.1 from emp where datediff(now(),hiredate)>30*365; +``` \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/MySQL/查询/c2966305-0cbb-461a-b78d-d20bc55670c9.png b/posts/blog/编程技术/数据库/MySQL/查询/c2966305-0cbb-461a-b78d-d20bc55670c9.png new file mode 100644 index 0000000..456430d Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/c2966305-0cbb-461a-b78d-d20bc55670c9.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image102.png b/posts/blog/编程技术/数据库/MySQL/查询/image102.png new file mode 100644 index 0000000..35d9a6f Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image102.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image112.png b/posts/blog/编程技术/数据库/MySQL/查询/image112.png new file mode 100644 index 0000000..cd31883 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image112.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image12.png b/posts/blog/编程技术/数据库/MySQL/查询/image12.png new file mode 100644 index 0000000..2a32438 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image12.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image122.png b/posts/blog/编程技术/数据库/MySQL/查询/image122.png new file mode 100644 index 0000000..54725c5 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image122.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image13.png b/posts/blog/编程技术/数据库/MySQL/查询/image13.png new file mode 100644 index 0000000..e31161b Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image13.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image14.png b/posts/blog/编程技术/数据库/MySQL/查询/image14.png new file mode 100644 index 0000000..44cb9d0 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image14.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image15.png b/posts/blog/编程技术/数据库/MySQL/查询/image15.png new file mode 100644 index 0000000..4736b6f Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image15.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image16.png b/posts/blog/编程技术/数据库/MySQL/查询/image16.png new file mode 100644 index 0000000..1739aaf Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image16.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image17.png b/posts/blog/编程技术/数据库/MySQL/查询/image17.png new file mode 100644 index 0000000..cf52873 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image17.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image18.png b/posts/blog/编程技术/数据库/MySQL/查询/image18.png new file mode 100644 index 0000000..53c2ac5 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image18.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image19.png b/posts/blog/编程技术/数据库/MySQL/查询/image19.png new file mode 100644 index 0000000..2525d97 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image19.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image20.png b/posts/blog/编程技术/数据库/MySQL/查询/image20.png new file mode 100644 index 0000000..14bf720 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image20.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image21.png b/posts/blog/编程技术/数据库/MySQL/查询/image21.png new file mode 100644 index 0000000..f3b9471 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image21.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image24.png b/posts/blog/编程技术/数据库/MySQL/查询/image24.png new file mode 100644 index 0000000..1743f51 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image24.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image33.png b/posts/blog/编程技术/数据库/MySQL/查询/image33.png new file mode 100644 index 0000000..a45268e Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image33.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image43.png b/posts/blog/编程技术/数据库/MySQL/查询/image43.png new file mode 100644 index 0000000..8aa46da Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image43.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image53.png b/posts/blog/编程技术/数据库/MySQL/查询/image53.png new file mode 100644 index 0000000..0dfa370 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image53.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image62.png b/posts/blog/编程技术/数据库/MySQL/查询/image62.png new file mode 100644 index 0000000..7e762c9 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image62.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image72.png b/posts/blog/编程技术/数据库/MySQL/查询/image72.png new file mode 100644 index 0000000..2bbaedd Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image72.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image82.png b/posts/blog/编程技术/数据库/MySQL/查询/image82.png new file mode 100644 index 0000000..2b5e96e Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image82.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/查询/image92.png b/posts/blog/编程技术/数据库/MySQL/查询/image92.png new file mode 100644 index 0000000..cdfd9d1 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/查询/image92.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/索引.md b/posts/blog/编程技术/数据库/MySQL/索引.md new file mode 100644 index 0000000..41b25db --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/索引.md @@ -0,0 +1,199 @@ +--- +title: 索引 +date: 2024-10-15 +tags: [MySQL] +--- + + +- 是一种能够提高检索(查询)效率的==提前排好序的数据结构== + +## 索引的创建和删除 + +**创建** + +- 主键会自动添加索引,该索引被称为**主键索引** +- `unique`约束的字段也会自动添加索引,该索引称为**唯一索引** +- 添加外键,会自动在子表的外键列上创建**普通索引** +- 给指定字段添加索引 + + ```SQL + create table 表名 + ( + ... + --字段 + ... + index 索引名(字段名,字段名,字段名...) + ); + ``` + +- 表创建后,添加索引 + + ```SQL + alter table 表名 add index 索引名(字段名); + -- + create index 索引名 on 表名(字段); + ``` + + +**删除:**`alter table 表名 drop index 索引名;` + +查看:`show index from 表名;` + +## 分类 + +不同的==存储引擎==有不同的索引类型和实现 + +- 按照数据结构分类 + - B+树 索引(InnoDB存储引擎):采用B+树的数据结构 + - Hash 索引(仅memory存储引擎支持):常用哈希表 +- 按照物理存储分类 + + - 聚集索引:==索引和表中数据在一起==,数据存储是按照索引顺序存储,**聚集索引影响数据存储顺序**。一张表只能有一个聚集索引(通常是主键) + - 详解 + + - 当存储引擎是`InnoDB`的,主键上的索引属于聚集索引 + + InnoDB的物理存储方式:当创建一张表t_user,并使用InnoDB存储引擎时,会在硬盘上生成这样一个文件: + + - t_user.ibd (InnoDB data表索引 + 数据) + - t_user.frm (存储表结构信息) + - 非聚集索引:==索引和表中数据分开存储==,索引独立表空间,**只是额外的查找路径**。一张表可以有多个非聚集索引 + - 详解 + + - 当存储引擎是`MyISAM`的,任意字段上的索引都是非聚集索引 + + MyISAM的物理存储方式:当创建一张表t_user,并使用MyISAM存储引擎时,会在硬盘上生成这样一个文件: + + - t_user.MYD (表数据) + - t_user.MYI (表索引) + - t_user.frm (表结构) + + > [!important] 从MySQL8.0开始,不再生成frm文件了,引入了数据字典,用数据字典来统一存储表结构信息,例如: + > + > - information_schema.TABLES (表包含了数据库中所有表的信息,例如表名、数据库名、引擎类型等) + > - information_schema.COLUMNS(表包含了数据库中所有表的列信息,例如列名、数据类型、默认值等) + +- 按照字段特性分类 + - 主键索引(primary key) + - 唯一索引(unique) + - 普通索引(index) + - 全文索引(fulltext:仅 `InnoDB`和`MyISAM` 存储引擎支持) + +> [!important] 如果没有设置任何索引,数据在表中按插入顺序存储,没有特定的层次结构。这样的表称为 +> +> **堆表**(Heap Table),在这种情况下,查询==可能需要扫描整个表来查找数据==,因为没有索引来加速数据访问 + +## 存储原理 + +### 树结构存储 + +- MySQL索引默认使用B+Three,主键就是结点的值,叶子结点包含单行完整数据(聚集索引) + + - mysql为什么选择B+树作为索引的数据结构,而不是B树? + 1. 非==叶子节点上可以存储更多的键值,阶数可以更大==,更矮更胖,磁盘IO次数少,数据查询效率高 + 2. ==所有数据都是有序存储在叶子节点上,让范围查找,分组查找效率更高== + 3. ==数据页之间、数据记录之间采用链表链接,让升序降序更加方便操作== + + ![image 26.png](索引/image26.png) + + > [!important] 优点:聚集索引将数据存储在索引树的叶子节点上。可以减少一次查询,因为查询索引树的同时可以获取数据。 + > + > 缺点:对数据进行修改或删除时需要更新索引树,会增加系统的开销 + +- ==没有主键(聚集索引)的表的字段索引==(非聚集索引),叶子结点包含列的地址 + + - 如果一张表没有主键索引(没有主键),那还会创建B+树吗? + - 当一张表没有主键索引时,默认会使用一个隐藏的内置的聚集索引(clustered index),这个聚集索引是基于表的==物理存储顺序构建的(按地址构建)==,通常是使用B+树来实现的 + + ![image 1 12.png](索引/image112.png) + +- 有主键的表的非主键索引,也称**二级索引**(非聚集索引),叶子结点包含主键值,通过主键值进行回表查询数据 + + ![image 2 5.png](索引/image25.png) + + - 查询原理:`select * from t_user where age = 30;` + + ![image 3 4.png](索引/image34.png) + + - 避免回表,提高sql执行效率 +- 多个字段联合索引,称为**复合索引**(Compound Index),多个字段的值组合起来作为索引的值,以提高多列查询的效率 + + - 适合:`where 字段 and 字段` + + ![image 4 4.png](索引/image44.png) + + - 优缺点:减少索引的数量、提高查询性能、覆盖查询、创建和维护可能会增加索引的存储空间和对于写操作的影响 +- 总结:适合查询多,修改少的表 + +### Hash索引 + +- 支持Hash索引的存储引擎有: + - InnoDB(不支持手动创建Hash索引,系统会自动维护一个`自适应的Hash索引`) + - 对于InnoDB来说,即使手动指定了某字段采用Hash索引,最终`show index from 表名`的时候,还是`BTREE`。 + - Memory(支持Hash索引) +- 存储原理和Java一致(key就是字段值) + +## 索引优化 + +### 覆盖索引 + +- covering index,==索引中已经包含了所有需要获取的字段(复合索引)==的查询方式称为覆盖索引 +- 好处 + - 查询语句可以通过索引完成,避免回表操作,减少 IO 开销,适合查询特定字段的场景往往需要复合索引; + - 覆盖索引可以把随机 IO 变成顺序 IO 加快查询效率,即从多次随机IO变成一个顺序IO + +> [!important] 但当结果集较大时,MySQL 可能会选择全表扫描 + +### 索引下推 + +**索引下推(Index Condition Pushdown,简称 ICP)** + +- 是**MySQL 5.6**版本中提供的一项索引优化功能,它允许存储引擎在索引遍历过程中,执行部分`where`字句的判断条件,==直接过滤掉不满足条件的记录,从而减少回表次数==,提高查询效率 + +- 原理详解 + - MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处理查询解析、分析、优化、缓存以及与客户端的交互等操作,而存储引擎层负责数据的存储和读取,MySQL 支持 InnoDB、MyISAM、Memory 等多种存储引擎 + - 索引下推的**下推**其实就是指将部分上层(Server 层)负责的事情,交给了下层(存储引擎层)去处理 + - **除了可以减少回表次数之外,索引下推还可以减少存储引擎层和 Server 层的数据传输量** + +- 索引下推应用范围: + 1. 适用于 InnoDB 引擎和 MyISAM 引擎的查询 + 2. 适用于执行计划是 range, ref, eq_ref, ref_or_null 的范围查询 + 3. 对于 InnoDB 表,仅用于非聚簇索引。索引下推的目标是减少全行读取次数,从而减少 I/O 操作。对于 InnoDB 聚集索引,完整的记录已经读入 InnoDB 缓冲区。在这种情况下使用索引下推 不会减少 I/O + 4. 子查询不能使用索引下推,因为子查询通常会创建临时表来处理结果,而这些临时表是没有索引的 + 5. 存储过程不能使用索引下推,因为存储引擎无法调用存储函数 + +# 索引的优缺点 + +- 索引是数据库中一种重要的数据结构,用于加速数据的检索和查询操作。它的优点和缺点如下: +- 优点: + 1. 提高查询性能:通过创建索引,可以大大减少数据库查询的数据量,从而提升查询的速度。 + 2. 加速排序:当查询需要按照某个字段进行排序时,索引可以加速排序的过程,提高排序的效率。 + 3. 减少磁盘IO:索引可以减少磁盘IO的次数,这对于磁盘读写速度较低的场景,尤其重要。 +- 缺点: + 1. 占据额外的存储空间:索引需要占据额外的存储空间,特别是在大型数据库系统中,索引可能占据较大的空间。 + 2. 增删改操作的性能损耗:每次对数据表进行插入、更新、删除等操作时,需要更新索引,会导致操作的性能降低。 + 3. 资源消耗较大:索引需要占用内存和CPU资源,特别是在大规模并发访问的情况下,可能对系统的性能产生影响。 + +# 何时用索引 + +- 常见场景 + 1. 表数据量庞大,通常超过百万条数据 + 2. 经常出现在where,order by,group by后面的字段建议添加索引 + 3. 创建索引的字段尽量具有很强的唯一性 + 4. 如果字段存储文本,内容较大,一定要创建前缀索引 + 5. 尽量使用复合索引,使用单列索引容易回表查询 + 6. 如果一个字段中的数据不会为NULL,建议建表时添加not null约束,这样优化器就知道使用哪个索引列更加有效 + 7. 不要创建太多索引,当对数据进行增删改的时候,索引需要重新重新排序 + 8. 如果很少的查询,经常的增删改不建议加索引 + +- 在以下情况下建议使用索引: + 1. **频繁执行查询操作的字段**:如果这些字段经常被查询,使用索引可以提高查询的性能,减少查询的时间 + 2. **大表**:当表的数据量较大时,使用索引可以快速定位到所需的数据,提高查询效率 + 3. **需要排序或者分组的字段**:在对字段进行排序或者分组操作时,索引可以减少排序或者分组的时间 + 4. **外键关联的字段**:在进行表之间的关联查询时,使用索引可以加快关联查询的速度 +- 在以下情况下不建议使用索引: + 1. 频繁执行更新操作的表:如果表经常被更新数据,使用索引可能会降低更新操作的性能,因为每次更新都需要维护索引 + 2. 小表:对于数据量较小的表,使用索引可能并不会带来明显的性能提升,反而会占用额外的存储空间 + 3. 对于唯一性很差的字段,一般不建议添加索引。当一个字段的唯一性很差时,查询操作基本上需要扫描整个表的大部分数据。如果为这样的字段创建索引,索引的大小可能会比数据本身还大,导致索引的存储空间占用过高,同时也会导致查询操作的性能下降 + +总之,索引需要根据具体情况进行使用和权衡,需要考虑到表的大小、查询频率、更新频率以及业务需求等因素。 \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/MySQL/索引/image112.png b/posts/blog/编程技术/数据库/MySQL/索引/image112.png new file mode 100644 index 0000000..db6b618 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/索引/image112.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/索引/image25.png b/posts/blog/编程技术/数据库/MySQL/索引/image25.png new file mode 100644 index 0000000..b17c391 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/索引/image25.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/索引/image26.png b/posts/blog/编程技术/数据库/MySQL/索引/image26.png new file mode 100644 index 0000000..ca3c22f Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/索引/image26.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/索引/image34.png b/posts/blog/编程技术/数据库/MySQL/索引/image34.png new file mode 100644 index 0000000..edb6dc3 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/索引/image34.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/索引/image44.png b/posts/blog/编程技术/数据库/MySQL/索引/image44.png new file mode 100644 index 0000000..7ebdf39 Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/索引/image44.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/表相关操作.md b/posts/blog/编程技术/数据库/MySQL/表相关操作.md new file mode 100644 index 0000000..c137ef7 --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/表相关操作.md @@ -0,0 +1,348 @@ +--- +title: 表相关操作 +date: 2024-10-10 +tags: [MySQL] +--- + + +## 数据类型 + +### 整数类型 + +- **tinyint**:1个字节(微小整数,**代替布尔类型**) +- smallint:2个字节(小整数) +- mediumint:3个字节(中等大小的整数) +- **int(integer):4个字节(普通大小整数)** +- **bigint:8个字节(大整数)** + +### 浮点数类型 + +- float:4个字节,单精度(最多5位小数) +- **double:8个字节,双精度(最多16位小数)** + +### 定点数类型 + +- decimal:定点数类型,存储更精确的数据,底层实际上采用字符串的形式存储数字 + - decimal(m, d),m个有效数字,d个小数(有效数字最多65个,小数位最多30个) + +> [!important] 浮点数也可以使用(a,b)这种形式 + +### 日期和时间类型 + +- year:1个字节,只存储年,格式YYYY +- **time**:3个字节,只存储时间,格式HH:MM:SS / HHMMSS +- **date**:3个字节,只存储年月日,格式:YYYY-MM-DD +- **datetime**:8个字节,存储年月日+时分秒,格式:YYYY-MM-DD HH:MM:SS(从公元1000年~公元9999年) +- timestamp:4个字节,存储年月日+时分秒,格式:YYYY-MM-DD HH:MM:SS(从公元1980年~公元2040年)或者格式为 YYYYMMDDHHMMSS(采用这种格式不需要使用单引号,当然你使用单引号也可以) + +- datetime和timestamp的区别 + - **存储范围**: + - `DATETIME`: 从 `1000-01-01 00:00:00` 到 `9999-12-31 23:59:59`。 + - `TIMESTAMP`: 从 `1970-01-01 00:00:01` UTC 到 `2038-01-19 03:14:07` UTC。 + - **时区影响**: + - `DATETIME`: 不受时区影响,存储的是输入的时间。 + - `TIMESTAMP`: 受时区影响,存储的是 UTC 时间,并在读取时转换为当前时区。 + - **默认值和自动更新**: + - `TIMESTAMP`: 可以设置为在插入或更新行时自动更新为当前时间。 + - `DATETIME`: 需要手动管理时间值的更新。 + +### 字符串类型 + +**char** + +- **char(m):**m长度是0~255个字符,**固定长度字符串**,自动去除空格,不管字符长度,**存储所占大小固定**,当插入字符长度过大,会报错 + +**varchar** + +- **varchar(m):**m长度是0~16383个字符,**长度可变的字符串**,varchar 的最大实际长度由最长的行的大小和使用的字符集确定,而实际占用的空间=字符串的实际长度+ 1(结尾分隔符),当插入字符长度过大,会报错 + +![image 22.png](表相关操作/image22.png) + +**text** + +- tinytext :表示长度为 255字符的 TEXT 列 +- **text**:表示长度为 65535字符的 TEXT 列 +- mediumtext: 表示长度为 16777215字符的 TEXT 列 +- longtext: 表示长度为 4294967295 或 4GB 字符的 TEXT 列 + +**enum** + +- 语法:`<字段名> enum('值1','值2',...)` +- 该字段插入值时,只能是指定的枚举值 + +**set** + +- 语法:`<字段名> set('值1','值2','值3',...)` 注意:值不可重复。 +- 该字段插入值时,只能是指定的值。 + +> [!important] enum和set用法一致 + +### 二进制类型 + +- blob:小的,最大长度65535个字节 +- mediumblob:中等的,最大长度16777215个字节 +- longblob:大的,最大长度4GB的字节 + +> [!important] (binary large object)二进制大对象,可以存储图片、声音、视频等文件,但往往不使用数据库存文件,直接存在硬盘保存URL即可 + +## 表结构的增删改查(DDL) + +- 表结构的增删改查,往往针对的是字段操作,表结构一般不经常修改,并且一般通过可视化界面修改,所有使用率低 + +### 创建表 + +- 语法格式 + + ```SQL + create table 表名{ + 字段名 数据类型, + 字段名 数据类型, + 字段名 数据类型, + ... + }; + ``` + + +### 查看建表语句 + +- 语法格式 + + ```SQL + show create table 表名; + ``` + + +### 修改表结构(alter) + +- 修改表名:`rename` + + ```SQL + alter table 表名 rename 新表名; + ``` + +- 新增字段(需要数据类型):`add` + + ```SQL + alter table 表名 add 字段名 数据类型; + ``` + +- 修改字段名(需要数据类型):`change` + + ```SQL + alter table 表名 change 旧字段名 新字段名 数据类型; + ``` + +- 修改字段数据类型:`modify column` + + ```SQL + alter table 表名 modify column 字段名 数据类型; + --添加主键自增 + alter table 表名 modify column 字段名 数据类型 auto_increment; + ``` + +- 删除字段:`drop` + + ```SQL + alter table 表名 drop 字段名; + ``` + + +## 表数据的增删改(DML) + +### 插入数据 + +- 语法格式,字段名和值要一一对应,字段==可以不写全==(只写必要);字段==也可以不写====,但要把值全部都写上,并且顺序和建表时的顺序相同== + + ```SQL + insert into 表名(字段名1, 字段名2, 字段名3,......) + values (值1,值2,值3,......); + --省略字段 + insert into 表名 values(值1,值2,值3,...); + --一次性插入多条数据 + insert into 表名(字段名1, 字段名2, 字段名3,......) + values(值1,值2,值3,...),(值1,值2,值3,...); + ``` + + +### 删除数据 + +- 语法格式: + + ```SQL + --将所有的记录全部删除 + delete from 表名; + --删除符合条件的记录 + delete from 表名 where 条件; + ``` + +- 该方式属于DML的方式,这种删除的数据是可以通过**事务回滚**的方式重新恢复,所有删除效率低 +- 还有一种删除表数据的方式,叫:表被截断,这种方式不支持事务,不可以回滚,数据不可恢复,删除效率高 + + ```SQL + truncate table 表名; + ``` + + +### 修改数据 + +- 语法格式: + + ```SQL + --记得写条件,不然全部数据都会修改 + update 表名 + set 字段名1=值1, 字段名2=值2, 字段名3=值3 + where 条件; + ``` + + +## 约束(constraint) + +创建表时,通常需要对字段添加一定约束,保证数据的完整性、有效性 + +### 非空约束 + +- 语法格式:表示插入的一组数据中,该字段不能为null + + ```SQL + 字段名 数据类型 not null + ``` + + +### 检查约束 + +- 语法格式:表示插入字段,需要满足条件 + + ```SQL + 字段名 数据类型, + check(字段名满足条件) + ``` + + +### 唯一性约束 + +- 语法格式:设置该字段唯一,数据值不能重复,==但可以有多个null== + + ```SQL + 字段名 数据类型 unique + ``` + +- 以上为列级约束,还有一种:**表级约束** + + ```SQL + --可以为多个字段添加联合唯一,即除非两个字段的一样 + unique(字段1,字段2) + --可以给约束起名(有默认值),方便删除 + constraint 约束名 unique(字段名1,字段名2) + ``` + + +### 主键约束 + +- 主键:primary key,简称PK +- 主键约束的字段不能为null,并且不能重复(unique not null) +- 任何一张表都应该有主键,没有主键的表可以视为无效表 +- 主键分类: + - 根据字段数量分类 + + - 单一主键:1个字段作为主键(推荐) + + ```SQL + id int primary key + ``` + + - 复合主键:2个或2个以上的字段作为主键 + + ```SQL + primary key(id,name) + ``` + + - 根据业务分类 + - 自然主键:主键和业务无关,只是一个单纯的数据(建议) + - 业务主键:主键和业务挂钩,如账号作为主键 +- ==主键自增==:MySQL为主键提供的自动维护机制(属于字段属性) + + ```SQL + id int primary key auto_increment + ``` + + +### 外键约束 + +- 对外键的理解:解决数据冗余,浪费空间 + + ![image 1 9.png](表相关操作/image19.png) + +- 外键约束:foreign key,简称FK +- 添加了外键约束的字段中的数据==必须来自其他字段==,不能随便填 +- 假设给a字段添加了外键约束,要求a字段中的数据必须来自b字段,==b字段不一定是主键,但至少要有唯一性== +- 外键约束可以给单个字段添加,叫做单一外键;也可以给多个字段联合添加,叫做复合外键(复合外键很少用) + +- a表如果引用b表中的数据,可以把b表叫做父表,把a表叫做子表 + - 理解成栈结构,父先,子后 + - 创建表时,先创建父表,再创建子表 + - 插入数据时,先插入父表,在插入子表 + - 删除数据时,先删除子表,再删除父表 + - 删除表时,先删除子表,再删除父表 + +- 添加外键 + + ```SQL + --创建父表 + ... + --创建子表,添加外键约束 + foreign key(字段) references 父表(字段) + --给外键约束起名 + constraint 约束名 foreign key(字段) references 父表(字段) + ``` + + +**级联删除** + +- 当父表删除数据,子表相关数据会级联删除(谨慎使用) + + ```SQL + foreign key(字段) references 父表(字段) on delete cascade + ``` + + +**级联置空** + +- 当父表删除数据时,子表相关数据置空 + + ```SQL + foreign key(字段) references 父表(字段) on delete set null + ``` + + +**级联更新** + +- 当父表更新数据,子表相关数据会级联更新 + + ```SQL + foreign key(字段) references 父表(字段) on update cascade + ``` + + +> 建议级联置空、更新一起使用:`on delete set null on update cascade` + +### 增、删约束 + +**添加约束** + +- 语法格式 + + ```SQL + alter table 表名 add + constraint 约束名 foreign key(字段) references 父表(字段) + on delete cascade; + ``` + +- 所有的约束都存储在数据库:information_schema,中的一个系统表当中:table_constraints + +**删除约束** + +- 语法格式 + + ```SQL + alter table 表名 drop foreign key 约束名; + ``` \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/MySQL/表相关操作/image19.png b/posts/blog/编程技术/数据库/MySQL/表相关操作/image19.png new file mode 100644 index 0000000..8ad4a4a Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/表相关操作/image19.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/表相关操作/image22.png b/posts/blog/编程技术/数据库/MySQL/表相关操作/image22.png new file mode 100644 index 0000000..77e07de Binary files /dev/null and b/posts/blog/编程技术/数据库/MySQL/表相关操作/image22.png differ diff --git a/posts/blog/编程技术/数据库/MySQL/视图.md b/posts/blog/编程技术/数据库/MySQL/视图.md new file mode 100644 index 0000000..1d777a0 --- /dev/null +++ b/posts/blog/编程技术/数据库/MySQL/视图.md @@ -0,0 +1,40 @@ +--- +title: 视图 +date: 2024-10-10 +tags: [MySQL] +--- + + +[![](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg?x-oss-process=image%2Fresize%2Cw_1177%2Climit_0%2Finterlace%2C1%2Finterlace%2C1#averageHue=%23f9f8f8&from=url&id=I2lTi&originHeight=66&originWidth=1177&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=shadow&title=)](https://cdn.nlark.com/yuque/0/2023/jpeg/21376908/1692002570088-3338946f-42b3-4174-8910-7e749c31e950.jpeg?x-oss-process=image%2Fresize%2Cw_1177%2Climit_0%2Finterlace%2C1%2Finterlace%2C1#averageHue=%23f9f8f8&from=url&id=I2lTi&originHeight=66&originWidth=1177&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=shadow&title=) + +## 什么是视图 + +- 临时表 +- **视图作用** + - 如果开发中有一条非常复杂的SQL,而这个SQL在多处使用,会给开发和维护带来成本。使用视图可以降低开发和维护的成本 + - 视图可以隐藏表的字段名 + +## 视图的使用 + +- 创建视图:只能将select语句创建为视图 + + ```SQL + --创建或代替视图 + create or replace view 视图名 as DQL; + ``` + +- 修改视图 + + ```SQL + alter view 视图名 as DQL; + ``` + +- 删除视图 + + ```SQL + --删除/如果存在删除 + drop view if exists 视图名; + ``` + + +**对视图增删改(DML:insert delete update)可以影响到原表数据** \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/Redis/Redis笔记.md b/posts/blog/编程技术/数据库/Redis/Redis笔记.md new file mode 100644 index 0000000..79bdc8e --- /dev/null +++ b/posts/blog/编程技术/数据库/Redis/Redis笔记.md @@ -0,0 +1,690 @@ +--- +title: Redis笔记 +date: 2025-04-07 +tags: [Redis] +--- + + +## 安装Redis + + + +## 使用Redis + +基本数据类型:String + +设置过期时间:`set` key ex 时间 + +```Shell +set EX 秒 +set PX 毫秒 +``` + +查看过期时间:-1 表示永久时间 + +```Shell +ttl +-- 毫秒显示 +pttl +-- 转换为永久 +persist +``` + + + +我们来看看,如何向Redis数据库中添加数据: + +```SQL +set +-- 一次性多个 +mset [ ]... +``` + +所有存入的数据默认会以**字符串**的形式保存,键值具有一定的命名规范,以方便我们可以快速定位我们的数据属于哪一个部分,比如用户的数据: + +```SQL +-- 使用冒号来进行板块分割,比如下面表示用户XXX的信息中的name属性,值为lbw +set user:info:用户ID:name lbw +``` + +我们可以通过键值获取存入的值: + +```SQL +get +``` + +你以为Redis就仅仅只是存取个数据吗?它还支持数据的过期时间设定: + +```SQL +set EX 秒 +set PX 毫秒 +``` + +当数据到达指定时间时,会被自动删除。我们也可以单独为其他的键值对设置过期时间: + +```SQL +expire 秒 +``` + +通过下面的命令来查询某个键值对的过期时间还剩多少: + +```SQL +ttl +-- 毫秒显示 +pttl +-- 转换为永久 +persist +``` + +那么当我们想直接删除这个数据时呢?直接使用: + +```SQL +del ... +``` + +删除命令可以同时拼接多个键值一起删除。 + +当我们想要查看数据库中所有的键值时: + +```SQL +keys * +``` + +也可以查询某个键是否存在: + +```SQL +exists ... +``` + +还可以随机拿一个键: + +```SQL +randomkey +``` + +我们可以将一个数据库中的内容移动到另一个数据库中: + +```SQL +move 数据库序号 +``` + +修改一个键为另一个键: + +```SQL +rename <新的名称> +-- 下面这个会检查新的名称是否已经存在 +renamex <新的名称> +``` + +如果存放的数据是一个数字,我们还可以对其进行自增自减操作: + +```SQL +-- 等价于a = a + 1 +incr +-- 等价于a = a + b +incrby b +-- 等价于a = a - 1 +decr +``` + +最后就是查看值的数据类型: + +```SQL +type +``` + +Redis数据库也支持多种数据类型,但是它更偏向于我们在Java中认识的那些数据类型。 + +## 基本数据类型 + +### Hash + +普通 —> Map + +hash —> Map> + +**操作** + +- 设置: `hset` key +- 获取:`hget` key +- 某个字段是否存在:`hexists` key +- 删除Hash中的某个字段:`hdel` key + - 没有值的key,相当于删除了 +- 一次性获取所有的字段和值:`hgetall` key +- 查看Hash中一共存了多少个键值对:`hlen` key +- 一次性获取所有字段的值:`hvals` key + +> [!important] 唯一需要注意的是,Hash中只能存放字符串值,不允许出现嵌套的的情况。 + +### List + +类似字符串数组(队列),向一个已存在或是不存在的List中添加数据,如果不存在,会自动创建 + +**操作** + +- 插入 + - 向列表头部添加元素:`lpush` key + - 向列表尾部添加元素:`rpush` key + - 在指定元素前面/后面插入元素:`linsert` key before/after <指定元素> +- 获取(移除获取):下标索引从0开始,-1表示倒一,-2表示倒二 + - 根据下标获取元素:`lindex` key <下标> + - 获取并移除头部元素:`lpop` key + - 获取并移除尾部元素:`rpop` key + - 获取指定范围内的:`lrange` key start stop + - lrange a 0 -1 + +特殊 + +push和pop连着用呢:`rpoplpush` 当前数组 目标数组 + +- rpop、lpop、lpush、lpop混用 + +支持阻塞操作,类似于生产者和消费者,比如我们想要**等待列表中有了数据后再进行**pop操作: + +```SQL +-- 如果列表中没有元素,那么就等待,如果指定时间(秒)内被添加了数据,那么就执行pop操作,如果超时就作废,支持同时等待多个列表,只要其中一个列表有元素了,那么就能执行(key可以是多个) +blpop ... timeout +``` + +### Set + +Set集合其实就像Java中的HashSet一样(我们在JavaSE中已经讲解过了,HashSet本质上就是利用了一个HashMap,但是Value都是固定对象,仅仅是Key不同)它==不允许出现重复元素,不支持随机访问==,但是能够利用Hash表提供极高的查找效率。 + +**操作** + +- 向Set中添加一个或多个值:`sadd` key … +- 查看Set集合中有多少个值:`scard` key +- 是否包含指定值:`sismember` key +- 列出所有值:`smembers` key +- 集合之间的运算:存储目标集合 + - 差集:`sdiff` key1 key2 + - `sdiffstore` 目标 key1 key2 + - 夹集:`sinter` key1 key2 + - `sinterstore` 目标 key1 key2 + - 并集:`sunion` key1 key2 + - `sunionstore` 目标 key1 key2 +- 移动指定值到另一个集合中:`smove` key 目标 value +- 移除操作: + - 随机移除一个幸运儿:`spop` key + - 移除指定:`srem` key ... + +### SortedSet + +那么如果我们要求Set集合中的数据按照我们指定的顺序进行排列怎么办呢?这时就可以使用SortedSet,它支持我们为每个值设定一个分数,**分数的大小决定了值的位置**,所以它是有序的。 + +**操作** + +- 添加一个带分数的值:`zadd` key [ ]... + +同样的: + +```SQL +-- 查询有多少个值 +zcard key +-- 移除 +zrem key ... +-- 获取区间内的所有 +zrange key start stop +``` + +由于所有的值都有一个分数,我们也可以根据分数段来获取: + +```SQL +-- 通过分数段查看 +zrangebyscore key start stop [withscores] [limit] +-- 统计分数段内的数量 +zcount key start stop +-- 根据分数获取指定值的排名 +zrank key +``` + +## 持久化 + +2种存储方式:存储过程、存储数据 + +### RDB + +**默认使用该方式**,配置文件中设置了保存时机,多少分钟自动保存什么的 + +手动保存 + +- `save`命令存储,直接存储数据,`bgsave` 后台线程进行保存,防止主线程堵塞 + +> [!important] 缺点: +> +> - 数据开销小大 +> - 突发状况,关键数据依然有丢失风险,与保存时间点错开 + +### AOF + +以日志形式保存所有操作,服务器重启所有操作复现(默认是关闭的) + +日志保存策略 + +- always:每次执行操作都保存 +- everysec:每秒保存一次(开启默认配置),秒级丢失 +- no: + +修改配置文件开启:`**appendonly yes**` + +> [!important] 缺点: +> +> - 会导致文件无限变大 +> - 文件太大,复原极其消耗资源 + +**优化** + +- 默认当文件到达一定大小时(可修改),自动优化;语句优化(合并语句) + + ```Shell + -- 百分比计算,这里不多介绍 + auto-aof-rewrite-percentage 100 + -- 当达到这个大小时,触发自动重写 + auto-aof-rewrite-min-size 64mb + ``` + +- 手动触发优化:`bgrewriteaof` + +## 事务和锁机制 + +通过队列的方式,存储命令行,提交事务统一执行,所有事务中只有修改操作 + +- 开启事务:multi +- 提交事务:exec +- 取消事务:discard + +> [!important] 中途被其他请求修改,可以直接修改数据,不会阻塞请求 + +**锁** + +使用`watch`来监视一个目标,如果执行事务之前被监视目标发生了修改,则取消本次事务 + +- 底层使用乐观锁,并使用**版本号机制**,避免ABA问题,可以保证**事务的数据一致性** + +**场景:抢票场景** + +- 2个请求,查询都有票,都扣除票数,导致超卖; +- ==监听安排在查询前==,如果在查询后,那么查询结果可能与监听结果不一致(中途被修改) +- 流程:监听 —> 查询 —> 事务 —> 修改 —> 提交(可能失败) + + - 原理: + - 监听记录当前版本号(避免ABA问题),**保证事务数据中途没人修改** + + ```Shell + WATCH stock:product123 + GET stock:product123 + if stock > 0: + MULTI + DECR stock:product123 + EXEC + else: + UNWATCH + return "Sold out" + ``` + + +## ⚠️ 注意事项 + +- `watch` 之后如果不想执行事务了,需要 `unwatch` +- 如果用 `watch`,就不要在 `multi` 块里做非原子的逻辑判断(逻辑应该在事务外先判断) +- 执行 `exec` 时才会检查被监控的键是否被改动 +- 一般搭配重试机制(比如重试 3 次后放弃) + +> [!important] Redis是单线程的,并且都是原子操作,可以避免高并发的线程安全问题;但是无法保证事务安全性,通过watch(乐观锁)来解决事务问题 + +--- + +**乐观锁带来的问题** + +- 有票但购票失败!监听同一版本,快的购票成功,慢的失败,但是库存是足够的 +- 解决方式:==使用Lua脚本,直接不使用乐观锁== + + + +## 缓存一致性的问题 + +- cap缓存策略 +- 数据库和缓存的一致是无法保持的 + +![image 8.png](Redis笔记/image8.png) + +## 结合SpringBoot Cache进行开发 + + + + + +### 限流 + +## **🔍 为什么 Nginx 限流之后,还建议用 Redis 再次限制请求频率?** + +### **📌 1. 分层防御原则(Defense in Depth)** + +- **Nginx 层限流** :快速拦截恶意或高频请求,减轻后端压力; +- **Redis + 应用层限流** :更精细控制业务逻辑级别的请求频率(如针对某个 Email 或用户 ID 的请求频率); + +> 比如: +> +> - Nginx 可以限制每个 IP 每秒最多 100 请求; +> - Redis 可以限制每个 Email 每分钟只能发一次验证码。 + +两者作用不同,互为补充。 + + + +## SpringBoot中使用Redis + +### **StringRedisTemplate** + +`**StringRedisTemplate**` 是 Spring Data Redis 提供的一个专门用于操作 Redis 中 **字符串类型数据** 的工具类,它是 `**RedisTemplate**` 的一个子类,专为处理字符串(`**String**`)键值对而设计 + +**特点:** + +- Key 和 Value 都是 `**String**` 类型。 +- 默认使用 `**StringSerializer**` 序列化/反序列化。 +- 使用方便,适合处理简单的字符串键值对操作。 +- 常用于缓存、计数器、锁等场景。 + +**常用方法** + +> `**ops**`是"operations"(操作)的缩写 + +`**opsForXxx()**`方法遵循以下模式: + +- `**opsFor**` - 表示"operations for"(针对...的操作) +- `**Xxx**` - 表示特定的Redis数据结构类型 + +1. `**opsForValue()**` - 针对字符串(Value)的操作 + - 操作Redis的String类型 + - 返回`**ValueOperations**`接口 +2. `**opsForList()**` - 针对列表(List)的操作 + - 操作Redis的List类型 + - 返回`**ListOperations**`接口 +3. `**opsForSet()**` - 针对集合(Set)的操作 + - 操作Redis的Set类型 + - 返回`**SetOperations**`接口 +4. `**opsForZSet()**` - 针对有序集合(Sorted Set)的操作 + - 操作Redis的ZSet类型 + - 返回`**ZSetOperations**`接口 +5. `**opsForHash()**` - 针对哈希(Hash)的操作 + - 操作Redis的Hash类型 + - 返回`**HashOperations**`接口 + +`**increment(key, delta)**`**:递增操作(适用于数字)** + +**💡 用途:** + +将存储在 `**key**` 中的数字值增加 `**delta**`。如果 key 不存在,会先设置为 `**0**` 再执行加法。 + +> ⚠️ 注意:这个 key 对应的 value 必须是一个可以解析为整数的字符串,否则会抛出异常。 + +- 返回的是当前 key 的新值; +- 如果 Redis 操作失败或 key 不可用,可能返回 `**null**` + +**📝应用场景:** + +- 访问统计(如 PV) +- 分布式限流 +- 秒杀库存控制 + +`**setIfAbsent(key, value)**`**:只有 key 不存在时才设置** + +**💡 用途:** + +原子性地设置 key-value,仅当 key 不存在时才设置成功。 + +**📝 应用场景:** + +- 实现分布式锁 +- 防止重复提交、幂等校验 + +`**expire(key, timeout, unit)**`**:设置 key 的过期时间** + +**💡 用途:** + +为某个 key 设置过期时间,单位可自定义(秒、毫秒等) + +**📝 应用场景:** + +- 缓存数据设置 TTL(生存时间) +- 临时 token、验证码等数据自动失效 + + + + + +## 限流算法 + +> 在一定时间内容,限制访问次数 + +### 固定窗口 + +以第一次请求为时间线,从第一个请求为**开始时间**到一定时间段,限制访问的次数,从**业务逻辑来说没有错**,适用于密码错误次数、以及一些限制用户操作;但从限流的角度是有问题的! + +例如:1分钟请求1个ip请求100次 + +```Shell +00:01 ---> 发送1次请求 到期时间:01:01 +00:59 ---> 发送99次请求 只做计数操作,到期时间不变 +01:01(刚到期) ---> 发送100次请求 +``` + +从这个时间线可以看出,00:59-01:01这**2秒中请求199次**,远超100个请求的限制,无法满足我们的流量限流 + +- 实现思路 + + redis:第一次访问,创建请求记录+过期时间,后面的请求没超过则请求数加1,请求超出直接拒绝 + + 无redis:记录第一次访问记录,及其时间,后面请求根据请求时间、次数决定增加次数、还是替话当前记录、还是拒绝请求 + + +### 滑动窗口 + +可以解决限流的问题,是通过**统计过去 N 秒内所有请求数量**,同样的问题 + +```Shell +00:01 ---> 发送1次请求 到期时间:01:01 +00:59 ---> 发送99次请求 记录每条请求,分别设置到期时间 +01:01 ---> 发送100次请求 统计访问请求(除了第1次请求到期,其他都没过期) +``` + +最终 00:59-01:01这**2秒中请求100次**,没有超额,很好的解决了问题,但是每次请求都用Redis进行记录,**开销太大了**,不推荐! + +- 实现思路 + + redis:每次请求进行记录并设置过期时间,访问时统计次数,没超标则添加记录,否则拒绝 + + 无redis:使用一个循环队列,每次增加先判断有没有慢,满了拿队尾元素进行比较,超出时间则移除队尾元素,并添加新记录到队首 + + +### 令牌桶算法 + +> 令牌桶是一个以固定速率往桶里放令牌的限流算法。 +> +> 每次请求需要消耗一个令牌,桶里没有令牌就拒绝请求。 + +📊 核心原理 + +- 系统以**恒定速率 r** 往桶里放令牌(比如 5个/s) +- 桶的容量是固定的,超过就丢弃 +- 每次请求先取一个令牌: + - ✅ 拿到 → 放行 + - ❌ 没令牌 → 拒绝 + +⚠️ 支持**突发流量**,因为可以在短时间内用掉桶里的积攒的令牌。 + +--- + +📌 举个例子 + +- 桶容量:10 个 +- 产生速率:2 个/s +- 假设 0秒到 5秒没有请求,桶里积攒了 10 个令牌(上限 10) +- 第 5秒 突然来了 8 个请求 + + → 前 8 个都放行,令牌减到 2 + +- 再来第 9 个请求,桶里还有 2,放行 +- 再来第 11 个请求,桶空了,拒绝 + + + +📌 实现方式 + +实现核心: + +1. **记录最后一次放令牌的时间** +2. 当前时间 - 最后时间 × 速率 = 新生成的令牌数 +3. 当前桶内令牌数 + 新令牌数,不能超过桶容量 +4. 请求来时,判断桶里有无令牌,有就减1放行,没有就拒绝 + +--- + +📌 伪代码版实现 + +```Java +class TokenBucket { + int capacity; // 桶容量 + double tokens; // 当前令牌数量 + double rate; // 每秒放入令牌速率 + long lastTime; // 上次放令牌的时间戳 + + public TokenBucket(int capacity, double rate) { + this.capacity = capacity; + this.rate = rate; + this.tokens = capacity; + this.lastTime = System.currentTimeMillis(); + } + + synchronized boolean allowRequest() { + long now = System.currentTimeMillis(); + double delta = (now - lastTime) / 1000.0; + // 补充新令牌 + tokens = Math.min(capacity, tokens + delta * rate); + lastTime = now; + + if (tokens >= 1) { + tokens -= 1; + return true; // 放行 + } else { + return false; // 拒绝 + } + } +} + +``` + +--- + +📌 Redis 实现方式思路 + +因为单机多线程靠锁就行 + +分布式限流要靠**Redis + Lua 脚本**,保证原子性 + +Redis key 存: + +- 当前令牌数量 +- 上次放令牌时间戳 + +每次请求: + +- 用 Lua 脚本计算当前令牌数 +- 判断是否够放行 +- 更新令牌数 & 时间戳 + +👇 Lua 脚本思路: + +```Lua +-- KEYS[1] = token key +-- ARGV[1] = rate +-- ARGV[2] = capacity +-- ARGV[3] = now (ms) + +local tokens = tonumber(redis.call('GET', KEYS[1]) or ARGV[2]) +local lastTime = tonumber(redis.call('GET', KEYS[1]..":ts") or ARGV[3]) +local now = tonumber(ARGV[3]) + +local delta = (now - lastTime) / 1000 +local newTokens = math.min(ARGV[2], tokens + delta * ARGV[1]) + +if newTokens < 1 then + return 0 +else + redis.call('SET', KEYS[1], newTokens - 1) + redis.call('SET', KEYS[1]..":ts", now) + return 1 +end + +``` + +--- + +## 缓存数据不一致、穿透、雪崩问题 + +### 数据不一致 + +> 数据库数据与缓存不一致 + +解决方案:无法是先修改数据库、还是先修改缓存问题 + +**Cache Aside Pattern(旁路缓存模式)** + +- 写操作较少、读多写少的业务 +- 更新时,直接删除缓存 + +👉 实际开发里**用得最多的是:先删缓存,再更新数据库;** + +**延迟双删策略** + +1. 删除缓存 +2. 更新数据库 +3. 延迟 500ms 再删除一次缓存 + +**为什么?** + +防止并发场景下: + +- A 线程删了缓存,还没来得及更新数据库 +- B 线程查缓存发现没了,查了数据库旧值,再写入缓存,脏了 + +标准做法是**异步延迟执行**,要么用**MQ 延迟消息**、要么用**线程池定时任务**,要么用**Redisson 延迟队列** + +📌 总结一句话: + +> 缓存一致性没有完美解,核心是选定方案,权衡一致性、性能、复杂度,控制脏数据窗口最小。 + +### 穿透 + +> 请求的数据,既不在缓存中,也不在数据库中,每次请求都会直接打到数据库上,导致数据库压力巨大。 + +解决方案:缓存空值、布隆过滤器 + +**缓存空值**:设置一个null值进行缓存,抗一下 + +布隆过滤器:加载所有key,除非是非常大的项 + +![image 1 5.png](Redis笔记/image15.png) + +### 雪崩 + +> 同一时间,大量缓存失效,请求都打到数据库上了 + +设置不同的过期时间(设置不同的缓存时间) + +- 简单易用 + +使用互斥锁锁,限制访问数据库的请求 + +- 只允许一个请求去读数据库,其他的等待 +- 设置过期时间,防止死锁 + + + +## 一些设计 + +### 分布式id有序性 + +保证id有序+id不能被查出来 + +- 使用Redis生成id,时间戳+序号 \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/Redis/Redis笔记/image15.png b/posts/blog/编程技术/数据库/Redis/Redis笔记/image15.png new file mode 100644 index 0000000..d79dbdc Binary files /dev/null and b/posts/blog/编程技术/数据库/Redis/Redis笔记/image15.png differ diff --git a/posts/blog/编程技术/数据库/Redis/Redis笔记/image8.png b/posts/blog/编程技术/数据库/Redis/Redis笔记/image8.png new file mode 100644 index 0000000..58aedd7 Binary files /dev/null and b/posts/blog/编程技术/数据库/Redis/Redis笔记/image8.png differ diff --git a/posts/blog/编程技术/数据库/SQL Server/SQL Server题目.md b/posts/blog/编程技术/数据库/SQL Server/SQL Server题目.md new file mode 100644 index 0000000..9222b03 --- /dev/null +++ b/posts/blog/编程技术/数据库/SQL Server/SQL Server题目.md @@ -0,0 +1,607 @@ +--- +title: SQL Server题目 +date: 2024-12-25 +tags: [SQL Server] +--- + + +## 数据库系统概况 + +数据库(DB)、数据库系统(DBS)和数据库管理系统(DBMS)之间的关系是( ) + +- _A、_DBMS包括DB和DBS +- _B、_DBS包括DB和DBMS +- _C、_三者属于平级关系 +- _D、_DB包括DBS和DBMS + +- 解释: + + - **DB(Database,数据库)**:是指存储在计算机中的数据集合,按一定的结构和格式组织,可以被管理和访问。 + - **DBMS(Database Management System,数据库管理系统)**:是指用于创建、管理、维护和操作数据库的软件系统。它提供了数据的存储、检索、更新等功能,是管理数据库的工具。 + - **DBS(Database System,数据库系统)**:是指一个完整的数据库环境,通常包括数据库(DB)和数据库管理系统(DBMS)。它是数据库和数据库管理系统的结合体。 + + 因此,**DBS包括DB和DBMS**,即数据库系统是由数据库和数据库管理系统共同组成的。 + + + + +SQL.Server2005有4个系统数据库,下列不是系统数据库的为( ) + +- _A、_msdb +- _B、_model +- _C、_master +- _D、_pubs + +- 解释: + + 在 SQL Server 2005 中,有四个系统数据库: + + 1. **master**:保存有关 SQL Server 实例的核心信息,例如数据库的元数据、登录信息等。 + 2. **msdb**:用于存储 SQL Server Agent 的任务和作业信息,还用于备份、维护计划和警报。 + 3. **model**:作为创建新数据库时的模板数据库,每次创建新数据库时,都会从 `model` 数据库复制结构。 + 4. **tempdb**:存储临时数据,例如临时表、排序操作的临时结果等。 + + `**pubs**` 不是系统数据库,而是一个示例数据库,用于展示 SQL Server 的功能。 + + +> [!important] **关系模型中的关系**,也就是**表**(table) + +在数据库系统中,同一个外模式( ) + +- _A、_不能被一个用户的多个应用程序使用 +- _B、_只能被一个用户的一个应用程序使用 +- _C、_可以被所有用户的应用程序使用 +- _D、_可以被同一个用户的多个应用程序使用 + +- 解释: + + 在数据库系统中,**外模式**(External Schema)是用户视图(User View)的另一种表述,表示用户如何看待数据库的数据结构。外模式通常是由多个用户或应用程序定义的,描述用户对数据库的访问需求。 + + - **外模式**是一个针对用户的视图,它与**内模式**(数据库的物理存储)和**概念模式**(数据库的逻辑结构)不同。外模式定义了用户能看到的数据结构和数据内容。 + - 同一个用户可以根据自己的需求,通过不同的应用程序访问相同的外模式。所以,外模式可以被同一个用户的多个应用程序使用。 + + 选项解释: + + - **A**:外模式通常可以被多个应用程序使用,而不限制一个用户只使用一个应用程序。 + - **B**:外模式可以被多个应用程序使用,因此这个选项不正确。 + - **C**:外模式是用户级的视图,通常是针对特定用户定义的,因此并不适用于所有用户。 + - **D**:这是正确的,因为同一个用户可以通过不同的应用程序使用相同的外模式。 + + 因此,正确答案是 **D**。 + + +数据库系统三级模式结构包括**外模式**、**概念模式**和**内模式**。==外模式是====**用户视图**====,概念模式是====**整个数据库的逻辑结构**====,内模式是====**数据的物理存储方式**==。 + +- 优点包括: + - 1. 数据独立性:用户可以在不影响其他用户的情况下修改外模式 + - 2. 数据安全性:通过外模式可以限制用户访问敏感数据 + - 3. 方便数据管理:不同的用户可以根据需要定义自己的视图,提高了数据的灵活性和可用性 + +> [!important] **DBS(Database System,数据库系统)**:指的是一个完整的数据库系统,它不仅包括数据库管理系统(DBMS),还包括数据库(DB)、用户和应用程序之间的所有交互以及管理和维护系统的其他组件。DBS 是一个更广泛的概念,DBMS 是其中的核心组件。 +> +> - **MySQL** 是 **DBMS**,即它是一个管理数据库的系统 + +## 关系数据库 + +非关系模型中数据结构的基本单位是( ) + +- A实体间多对多的联系 +- B记录 +- C两个记录型间的联系 +- D基本层次联系 + +- 解释 + + 非关系数据库(NoSQL 数据库)有多种数据模型,比如键值对模型、文档模型、列族模型和图形模型等。这些模型的设计通常是基于灵活的、层次化的结构,而不像关系型数据库那样强制使用表格的结构。 + + 在一些类型的非关系数据库中(特别是**层次模型**),数据存储单位往往是 **层次关系**,例如父子关系的嵌套。 + + - **A. 实体间多对多的联系**:这是关系型数据库中常见的概念,不是非关系模型的基本单位。 + - **B. 记录**:这是关系型数据库中常见的概念,指表中的一行数据。 + - **C. 两个记录型间的联系**:这个说法也与关系型数据库中的“外键”和表之间的关系相关,属于关系数据库的术语。 + - **D. 基本层次联系**:在非关系型数据库中,特别是层次型数据库中,数据是以层次结构存储的,节点间通过父子关系链接,层次联系是其基本单位。 + +> [!important] **元组(Tuple)** 通常指的是 **关系型数据库中的一行数据**,也可以理解为一个==**记录**==或一个数据条目 +> +> - 表的一列为**属性** + +( )属于信息世界的模型,实际上是现实世界到机器世界的一个中间层次。(2.0分) + +- A关系模型 +- B数据模型 +- CER图 +- D概念模型 + +- 解释 + + - **A. 关系模型**:关系模型是数据库中数据组织和表示的一种方法,它主要用于描述如何通过表格来存储和管理数据,是一种物理层面的数据库模型,不是现实世界与机器世界之间的中间层次。 + - **B. 数据模型**:数据模型是描述数据存储和数据结构的抽象方法,它通常是数据库设计的基础,涵盖了从概念模型到物理模型的多个层次。数据模型可以包括关系模型、实体-关系模型(ER图)等,但它本身不是中间层次。 + - **C. ER图**:ER图(实体-关系图)是用于表示实体、属性和它们之间关系的一种图形化工具。它是用来描述现实世界对象及其关系的概念工具,是概念模型的一部分,但不单独作为中间层次。 + - **D. 概念模型**:概念模型是对现实世界中对象及其关系的抽象表示,它是将现实世界的数据需求抽象为机器可处理的形式的一种方法。概念模型通常不依赖于具体的实现,是从现实世界到机器世界的一个中间层次,它为物理数据库设计提供基础。 + + ### 总结: + + **概念模型**用于将现实世界中的信息转化为机器可理解的结构,是现实世界和机器世界之间的一个过渡层次。因此,正确答案是 **D. 概念模型**。 + + +数据作的网状模型应满足的条件是( ) + +- A必须有两个以上的结点 +- B有且仅有一个结点无双亲.其余结点都只有一个双亲 +- C允许一个以上的无双亲,也允许-个结点有一个双亲 +- D每个结点有且仅有一个双亲 + +- 解释 + + 在**网状模型**中,数据之间的关系通过多个结点和它们之间的多重连接来表示。网状模型比层次模型更灵活,它允许多个父结点和多个子结点的关系。 + + 选项分析: + + - **A. 必须有两个以上的结点**:虽然网状模型通常有多个结点,但不强制要求一定要有两个以上的结点。一个结点也可以在某些特殊情况下存在。 + - **B. 有且仅有一个结点无双亲,其余结点都只有一个双亲**:这个描述是层次模型的特征(树状结构),在网状模型中并不成立。网状模型中结点的父子关系是多重的,并且没有这种单一父结点的限制。 + - **C. 允许一个以上的无双亲,也允许一个结点有一个双亲**:网状模型允许某些结点没有父结点(即无双亲),并且允许结点具有多个父结点(即多个双亲),这正是网状模型的灵活性所在。 + - **D. 每个结点有且仅有一个双亲**:这是树形结构的特征,要求每个结点只有一个父节点,而网状模型允许结点有多个父节点。 + + 总结: + + 网状模型的特点是结点之间的关系可以是多对多的,允许结点有多个父结点和子结点,因此,正确答案是 **C**。 + + +> [!important] **候选码(Candidate Key)** 是关系数据库中的一个重要概念,用于唯一标识表中的每一行数据(key),多个候选码(id,身份证) +> +> 主码,候选码中的一个 +> +> 全码,全部属性都是候选码 + +概念模型是按______的观点对数据建模,强调其语义表达能力 + +- 解释 + + “用户”或“业务” + + +数据模型的三要素包含______、______和______部分 + +- 解释 + + 数据模型的三要素是: + + 1. **数据结构**(描述数据的组织) + 2. **数据操作**(定义数据的操作) + 3. **数据完整性**(确保数据的有效性和一致性) + +> [!important] **数据模型** 是一种描述数据结构、数据之间关系以及数据操作的抽象框架,是一个总的概念 +> +> - 关系模型是数据模型的一种具体实现 +> - **数据结构**、**数据操作、数据完整性** + +> [!important] **关系的完整性**分为用—**用户完整性**、**参照完整性**、**数据结构完整性** + +> [!important] **关系操作有3种不同的描述方式**,分别是_**关系代数**___、_**关系演算**___和__**结构化查询语言/SQL**__。 + +E-R图的表示方法是: + +- **实体型**:矩形 +- **属性**:椭圆 +- **联系**:菱形 + +![image 40.png](SQL Server题目/image40.png) + +面向对象数据模型中,对象包括一组 **属性**、**方法** 和 **消息** + +## SQL + +当关系有多个候选键时,则选定一个作为主键,但若主键为全键时应包含( ) + +- A单个属性 +- B多个属性 +- C两个属性 +- D全部属性 + +- 解释:候选键=候选码 + + 1. 全键(All-Key)是指关系中的所有属性组合起来构成候选键。 + 2. 当关系是全键关系时,主键必须包含全部属性,因为: + - 在全键关系中,所有属性都是必需的,缺少任何一个属性都不能唯一标识一个元组 + - 如果不包含全部属性,就无法保证关系的唯一性约束 + 3. 举例说明: 假设有一个关系R(A,B,C),如果(A,B,C)是全键,那么: + - 不能只选择A或B或C作为主键 + - 也不能选择(A,B)或(B,C)或(A,C)作为主键 + - 必须选择(A,B,C)全部属性作为主键 + 4. 这与普通的候选键选择不同: + - 在普通情况下,可以从多个候选键中选择任意一个作为主键 + - 但在全键的情况下,必须包含所有属性 + + 因此,当关系为全键时,主键必须包含全部属性,选择D是正确的。 + + +> [!important] **目**:是指关系(表)中属性的个数(列数) +> +> **域**:是一个属性所有可能取值的集合 + +### 关系代数 + +### 1. 选择(Selection) - σ + +- **操作符**:σ +- **功能**:选择操作从一个关系中选出符合特定条件的元组(行)。 +- **语法**:`σ_condition(R)` + - 其中 `R` 是一个关系(表),`condition` 是用来筛选元组的条件。 +- **示例**:`σ_age > 30 (Employee)` 表示从 "Employee" 表中选择所有年龄大于30的员工记录。 + +### 2. 投影(Projection) - π + +- **操作符**:π +- **功能**:投影操作用于从关系中选择特定的属性(列)。 +- **语法**:`π_attributes(R)` + - 其中 `attributes` 是需要选择的列名,`R` 是要进行投影的关系。 +- **示例**:`π_name, salary (Employee)` 表示从 "Employee" 表中选择 "name" 和 "salary" 两列。 + +### 3. 连接(Join) - ⨝ + +- **操作符**:⨝ +- **功能**:连接操作用于将两个关系基于某些共享的属性进行连接,形成一个新关系。 + - 最常见的是 **自然连接(Natural Join)**,它基于共享的属性自动连接。 +- **语法**:`R ⨝ S` + - 其中 `R` 和 `S` 是两个关系,连接基于它们的共享属性。 +- **示例**:`Employee ⨝ Department` 表示将 "Employee" 表和 "Department" 表连接,基于共同的属性(如 "Department_ID")。 + +### 4. 并集(Union) - ∪ + +- **操作符**:∪ +- **功能**:并集操作用于将两个关系合并为一个新关系,要求两个关系具有相同的属性集合。 +- **语法**:`R ∪ S` + - 其中 `R` 和 `S` 是两个具有相同属性的关系。 +- **示例**:`Employee ∪ Manager` 表示将 "Employee" 和 "Manager" 表的所有元组合并成一个新的关系。 + +### 5. 差集(Difference) - − + +- **操作符**:− +- **功能**:差集操作用于返回属于第一个关系但不属于第二个关系的元组。 +- **语法**:`R − S` + - 其中 `R` 和 `S` 是两个具有相同属性的关系。 +- **示例**:`Employee − Manager` 表示从 "Employee" 表中删除所有属于 "Manager" 表的员工记录。 + +### 6. 交集(Intersection) - ∩ + +- **操作符**:∩ +- **功能**:交集操作用于返回两个关系中共同存在的元组。 +- **语法**:`R ∩ S` + - 其中 `R` 和 `S` 是两个具有相同属性的关系。 +- **示例**:`Employee ∩ Manager` 表示找出既是 "Employee" 又是 "Manager" 的元组(员工和经理共有的记录)。 + +### 7. 笛卡尔积(Cartesian Product) - × + +- **操作符**:× +- **功能**:笛卡尔积操作用于将两个关系的每个元组与另一个关系的每个元组合并,生成一个新的关系。 +- **语法**:`R × S` + - 其中 `R` 和 `S` 是两个关系。 +- **示例**:`Employee × Department` 表示将 "Employee" 表的每一条记录与 "Department" 表的每一条记录进行组合,生成新的关系。 + +### 8. 重命名(Rename) - ρ + +- **操作符**:ρ +- **功能**:重命名操作用于给关系或关系中的属性重新命名,方便后续操作。 +- **语法**:`ρ_new_name(R)` 或 `ρ_new_name(attributes)(R)` + - 其中 `new_name` 是新名称,`R` 是原始关系,`attributes` 是关系的属性集合。 +- **示例**:`ρ(Emp, Employee)` 表示将 "Employee" 表重命名为 "Emp"。 + +### 9. 除法(Division) - ÷ + +- **操作符**:÷ +- **功能**:除法操作用于在两个关系之间进行查询,返回一个新的关系,它包含一个关系中的所有元组,同时满足另一个关系中的某些条件。 + - 除法操作一般用于解决 "对于所有" 类的查询。 +- **语法**:`R ÷ S` + - 其中 `R` 和 `S` 是两个关系,`R` 的每个元组都应该包含 `S` 中的所有元组。 +- **示例**:假设我们有两个关系:学生表 "Student" 和课程表 "Course"。如果我们想找出选修了所有课程的学生,可以使用除法操作。 + +### 10. 外连接(Outer Join)(扩展的连接类型) + +- **操作符**:有不同的外连接类型(LEFT OUTER JOIN, RIGHT OUTER JOIN, FULL OUTER JOIN)。 +- **功能**:外连接是对连接操作的扩展,不仅返回满足连接条件的元组,还返回那些没有匹配的元组,并用NULL填充缺失的部分。 + +> [!important] 自然连接则是在等值连接的基础上,自动消除重复的属性列 + +> [!important] 关系代数的5个基本操作是: +> +> - 并、差、选择、投影、笛卡尔积 +> +> 系代数的4个组合操作是 +> +> - 交、连接、自然连接、除 + +关系的完整性分为____、____、____3类 + +- 解释 + + 实体完整性、参考完整性、用户定义完整性 + + +关系操作有3种不同的描述方式,分别是____、____、____ + +- 解释 + + 关系代数、关系演算、结构化查询语言/SQL + +- 题目 + + σ、π、 + + 1、πsno(σjno=‘j1’(spj)) + + 2、πsno(σjno=’j1’^pno=’p1’(spj)) + + 3、πsno(πsno、pno(σjno=’j1’(spj))⨝πpno(σcolor=’红’(p))) + + 4、πjno(πjno(spj)-πjno(σcity=’天津’(j))⨝πpno(σcolor=’红’(p))) + + 5、πjno,pno(spj)÷πpno(σsno=’s1’(SPJ)) + + +## Transact-SQL编程 + +在 SQL Server 中,变量名需要以 `@` 开头 + +> [!important] @@为全局变量 + +为声明的局部变量赋值的命令是____或者____ + +- 解释 + - set + - select into + +sql server语法 + +- `CAST` 将`字符串`转换为 `DATE` 类型 +- function标量函数**基本语法** (必须包含return) + + ```SQL + create function 函数名(@参数名 数据类型) + returns 返回值数据类型 + as + begin + -- 函数体 + return 返回值 + end + ``` + +- 基本语句 + + ```SQL + -- 定义 + declare @a int = 1; + -- 赋值 + set @a = 1 ; + -- 条件 + if @a > 1 + begin + + end + else + begin + + end + -- 循环 + while @a > 1 + begin + + end + -- 游标(cursor)声明并获取游标 + declare c_name cursor for + sql语句 + -- open游标(执行sql) + open c_name; + -- 读数据 + fetch next from c_name into @变量,@变量,@变量 + -- 关闭、释放 + close、deallocate + ``` + + +在数据库系统中,==可串行性== 就是并发执行的正确性准则 + + + +- 题目 + + ```SQL + create function getDay(@year int,@month int) + returns int + as + begin + -- 将整形 --> 日期 + -- 获取该月最后一天 eomonth + -- 获取该月的天数 day() + return day(eomonth(datefromparts(@year,@month,1))); + end + + + declare @sum bigint = 1; + declare @i int = 1; + while @i<=100 + begin + set @sum = @sum * @i; + set @i = @i + 1; + end + return @sum; + ``` + + ```SQL + + 现有“学生管理"数据库,数据库中有3个关系如下: + + 学生(学号, 姓名, 性别, 出生日期, 备注) + + 课程(课程号, 课程名, 学分) + + 选课(学号, 课程号, 成绩) + + 使用Transer SQL语句,实现下列功能: + + (1)在学生关系中增加一个“所在系”属性,数据类型为CHAR(4)。 + after table 学生 add '所在系' char(4) + + (2)把“所在系”属性的数据类型修改为CHAR(16)。 + after table 学生 + after column '所在系' char(16) + + (3)删除学生关系中“备注”属性。 + alter table 学生 + drop '备注' + + (4)向学生关系中插入一条学生记录(‘104001’, ‘张丽’, ‘女’)。 + insert into 学生(学号, 姓名, 性别) values('104001', '张丽', '女') + + (5)把学号为”104001”的学生姓名改为李平。 + update 学生 set 名字 ='李平' where 学号 = '104001' + + (6)把学号为”104001”的学生的选课信息全部删除。 + delete from 选课 where 课程号 = '104001' + + (7)创建“学生_选课”视图,视图内容包括管理系学生的学号、姓名、课程号、成绩。 + create view 学生_选课 as + select 学生.学号,学生.姓名,选课.课程号,选课.成绩 from 学生,选课 + where 学生.学号 = 选课.学号 + + (8)在课程关系中创建一个名为“课程号_INDEX”的唯一非聚簇索引,索引关键字为课程号,升序排列。 + alter table 选课 + add constraint 课程号_INDEX unique (课程号); + ``` + + 表中字段属性修改 + + ```SQL + [sql_server] + alter table table_name + alter column column_name new_data_type + [mysql] + alter table table_name + modify column column_name new_data_type + ``` + + 添加索引(索引名不需要引号括起来),默认是非聚集 + + ```SQL + alter table table_name + add constraint 索引名 unique (字段) + ``` + + + + + + +top + +- `select top 10 字段,字段 from table` +- `select top 10 percent` 百分比 + + + +### ER图 + +- 实体矩形、属性椭圆形、联系是菱形 + +### 存储过程(procedure) + +```SQL +create procedure Name + @变量 类型 [out] +as +begin + +end; +-- 调用 +exec Name +``` + +### 函数(function) + +```SQL +create function f(@i int,@j int)( +returns int +as +begin + + +end; +-- 调用 +select f(1,2); +``` + +### 触发器(trigger) + +```SQL +DML(insert、update、delete) +create trigger T +on 表 +after 操作 +instead of 操作(代替) +begin + +end +-- 删除 +drop trigger T + +DDL带数据库 +``` + +### sql语句 + +```SQL +DDL +create table table_naem( + id int identity(1,1) primary key, + name varchar(10) default 0, + age varchar(10) unique, + name varchar(10) default 0, + pid int, + foreign key(pid) references tn(id) +) +修改字段 +-- 添加新列 +alter table Employees +add Salary DECIMAL(10, 2); + +-- 修改列的数据类型 +alter table Employees +alter column Salary DECIMAL(12, 2); + +-- 删除列 +alter table Employees +drop column Salary; +索引 +create index name on table_name(id,name) +create unique index index_name on +drop index 名字 table_name on +外键 + + +DML +select * from table_name where x = 1; +update table_name set name = 1 where id =1; +insert into table_name(id,name) values(1,'123'); +delete from table_name where id =1; + asc desc + count + having + group by + order by +``` + +### 范式 + +**第一范式(1NF)** + +- 简单来说,第一范式要求数据库表的**每一列都是不可分割的基本数据项**。也就是说,不能有一列中包含多个值,每个单元格只能存放一个值。 +- 比如,有一个学生信息表,如果 “联系方式” 这一列里既写了电话号码又写了电子邮箱,这就不符合第一范式。正确的做法是把 “联系方式” 拆分成 “电话号码” 和 “电子邮箱” 两列。 + +**第二范式(2NF)** + +- 第二范式是在满足第一范式的基础上,要求表中的每一个**非主属性完全依赖于主键**,而不能只依赖于主键的一部分。 +- 举个例子,有一个订单表,包含订单编号、商品编号、客户编号、商品名称、客户姓名这些字段。假设订单编号和商品编号是联合主键。如果 “商品名称” 只依赖于 “商品编号”,而不依赖于 “订单编号”,这就不符合第二范式。解决办法就是把 “商品编号” 和 “商品名称” 单独拿出来创建一个商品表。 + +### **第三范式(3NF)** + +- 第三范式是在满足第二范式的基础上,要求表中的每一个**非主属性不能传递依赖于主键**。 +- 例如,有一个员工表,包含员工编号、部门编号、部门名称、员工姓名等字段。如果 “部门名称” 是通过 “部门编号” 来确定的,而 “员工姓名” 依赖于 “员工编号”,“部门名称” 依赖于 “部门编号”,这就存在传递依赖。不符合第三范式。解决办法是把 “部门编号” 和 “部门名称” 单独创建一个部门表。 \ No newline at end of file diff --git a/posts/blog/编程技术/数据库/SQL Server/SQL Server题目/image40.png b/posts/blog/编程技术/数据库/SQL Server/SQL Server题目/image40.png new file mode 100644 index 0000000..112f838 Binary files /dev/null and b/posts/blog/编程技术/数据库/SQL Server/SQL Server题目/image40.png differ diff --git a/posts/blog/编程技术/消息队列/RabbitMQ/RabbitMQ笔记.md b/posts/blog/编程技术/消息队列/RabbitMQ/RabbitMQ笔记.md new file mode 100644 index 0000000..0bc8729 --- /dev/null +++ b/posts/blog/编程技术/消息队列/RabbitMQ/RabbitMQ笔记.md @@ -0,0 +1,36 @@ +--- +title: RabbitMQ笔记 +date: 2025-04-28 +tags: [RabbitMQ] +--- + + +## 特点 + +- 异步处理 +- 削峰平谷,解决高并发 + +## 安装 + + + +## 介绍 + +- 生产者(Publisher)和消费者(Consumer) +- Channel:通过Channel去访问MQ服务器,通信协议是**amqp协议** + - 连接个数问题 +- Exchange:交换机,将请求转发到对应消息队列,队列都绑定到exchange上 +- Queue:消息队列本体 +- virtual host:环境隔离,1个virtual host中有多个交换机、Queue,每个virtual host之间相互隔离 + +## 多种模式 + +![image 9.png](RabbitMQ笔记/image9.png) + +![image 1 6.png](RabbitMQ笔记/image16.png) + +### 生产者、消费者 + +(默认交换机)根据队列名称、根据路由key + +生产者:创建交换机、队列、将队列绑定到路由中,并设置路由key \ No newline at end of file diff --git a/posts/blog/编程技术/消息队列/RabbitMQ/RabbitMQ笔记/image16.png b/posts/blog/编程技术/消息队列/RabbitMQ/RabbitMQ笔记/image16.png new file mode 100644 index 0000000..f6c3163 Binary files /dev/null and b/posts/blog/编程技术/消息队列/RabbitMQ/RabbitMQ笔记/image16.png differ diff --git a/posts/blog/编程技术/消息队列/RabbitMQ/RabbitMQ笔记/image9.png b/posts/blog/编程技术/消息队列/RabbitMQ/RabbitMQ笔记/image9.png new file mode 100644 index 0000000..7618fa3 Binary files /dev/null and b/posts/blog/编程技术/消息队列/RabbitMQ/RabbitMQ笔记/image9.png differ diff --git a/posts/blog/编程技术/算法/算法笔记.md b/posts/blog/编程技术/算法/算法笔记.md new file mode 100644 index 0000000..f93d27a --- /dev/null +++ b/posts/blog/编程技术/算法/算法笔记.md @@ -0,0 +1,123 @@ +--- +title: 算法笔记 +date: 2025-02-23 +tags: [算法] +--- + + +### 1. 数组与字符串 + +- 两数之和(Two Sum) + - LeetCode 1: Two Sum +- 最长回文子串(Longest Palindromic Substring) + - LeetCode 5: Longest Palindromic Substring +- 滑动窗口问题 + - “无重复字符的最长子串” - LeetCode 3: Longest Substring Without Repeating Characters + - “最小覆盖子串” - LeetCode 76: Minimum Window Substring +- 数组排序与查找 + - “合并区间” - LeetCode 56: Merge Intervals + - “寻找第K大元素” - LeetCode 215: Kth Largest Element in an Array + +--- + +### 2. 链表 + +- 反转链表(Reverse Linked List) + - LeetCode 206: Reverse Linked List +- 环检测(Linked List Cycle) + - LeetCode 141: Linked List Cycle +- 合并两个有序链表 + - LeetCode 21: Merge Two Sorted Lists +- 删除链表的倒数第N个节点 + - LeetCode 19: Remove Nth Node From End of List + +--- + +### 3. 树与图 + +- 二叉树遍历 + - 前序 - LeetCode 144: Binary Tree Preorder Traversal + - 中序 - LeetCode 94: Binary Tree Inorder Traversal + - 后序 - LeetCode 145: Binary Tree Postorder Traversal +- 二叉搜索树(BST)验证 + - LeetCode 98: Validate Binary Search Tree +- 层序遍历 + - LeetCode 102: Binary Tree Level Order Traversal +- 图的DFS和BFS + - “岛屿数量” - LeetCode 200: Number of Islands + - “课程表”(拓扑排序) - LeetCode 207: Course Schedule +- 最短路径 + - Dijkstra - LeetCode 743: Network Delay Time(类似问题) + +--- + +### 4. 动态规划(DP) + +- 爬楼梯 + - LeetCode 70: Climbing Stairs +- 最长递增子序列(LIS) + - LeetCode 300: Longest Increasing Subsequence +- 背包问题 + - 01背包 - LeetCode 416: Partition Equal Subset Sum(类似问题) + - 完全背包 - LeetCode 518: Coin Change II(类似问题) +- 编辑距离(Edit Distance) + - LeetCode 72: Edit Distance +- 股票买卖系列 + - “买卖股票的最佳时机” - LeetCode 121: Best Time to Buy and Sell Stock + +--- + +### 5. 堆与优先队列 + +- 前K大/小元素 + - LeetCode 215: Kth Largest Element in an Array +- 合并K个有序链表 + - LeetCode 23: Merge k Sorted Lists +- 滑动窗口最大值 + - LeetCode 239: Sliding Window Maximum + +--- + +### 6. 搜索与回溯 + +- N皇后问题 + - LeetCode 51: N-Queens +- 子集(Subsets) + - LeetCode 78: Subsets +- 单词搜索(Word Search) + - LeetCode 79: Word Search +- 组合总和(Combination Sum) + - LeetCode 39: Combination Sum + +--- + +### 7. 排序与贪心 + +- 快速排序和归并排序 + - LeetCode 912: Sort an Array(手写快排或归并) +- 会议室问题 + - LeetCode 252: Meeting Rooms(简单版) + - LeetCode 253: Meeting Rooms II(进阶版) +- 跳跃游戏(Jump Game) + - LeetCode 55: Jump Game + +--- + +### 8. Java后端相关算法题 + +- LRU缓存(LRU Cache) + - LeetCode 146: LRU Cache +- 设计一个限流器 + - 无直接题目,类似 LeetCode 359: Logger Rate Limiter(简化版) +- Top K问题 + - LeetCode 347: Top K Frequent Elements + +--- + +### 经典例题推荐(额外补充) + +- LeetCode 1: Two Sum +- LeetCode 206: Reverse Linked List +- LeetCode 146: LRU Cache +- LeetCode 53: Maximum Subarray +- LeetCode 200: Number of Islands \ No newline at end of file diff --git a/posts/blog/编程技术/系统设计/系统设计笔记.md b/posts/blog/编程技术/系统设计/系统设计笔记.md new file mode 100644 index 0000000..f762ffd --- /dev/null +++ b/posts/blog/编程技术/系统设计/系统设计笔记.md @@ -0,0 +1,118 @@ +--- +title: 系统设计笔记 +date: 2025-06-11 +tags: [系统设计] +--- + + +## 项目结构 + +```Shell +根项目(管理依赖版本) +-通用模块(引入项目) +-其他子模块(导入通用模块依赖) +``` + +**👁注意** + +- 根项目打包方式:pom +- lombok可能无法依赖,可能每个模块都要自己导入 +- 子模块设置打包工具,指定主类 + +### 开发工具 + +- 设置热部署,重构快捷启动 +- 使用服务页面,debug运行,日志配置很关键 + +### 通用模块 + +- 数据库的po、vo、dto、query都在这里,以及mapper、service及其实现 +- 系统的一些枚举设置、接口返回状态码 + +### web模块 + +- 自定义通用异常,接口异常统一处理,并回复大致异常信息(防狗) + + + +## 业务 + +### 验证码 + +为什么要有验证码? + +- 登录如果没有验证码,他人恶意破解密码,如果简单粗暴的封禁用户,影响普通用户体验,引入验证码,**大大提供了破解者的成本**,验证码使用程序识别有一定难道和成本,所以提高了安全性 + +### 拓展业务设计 + +像**设置**这种可扩展的业务,设计如下 + +- 表设计:主键、对象json +- 使用一个整体封装,使用设置对象 +- 设置启动加载配置 +- 使用枚举+反射完成高拓展的加载方法 + +### 接口设计 + +针对可登录、未登录都可以操作的接口 + +- 登录用户可查看自己审核中的内容(or (userId = 1 and status = 0)) +- 登录用户可查看自己的操作记录 + +### 数据库表设计 + +适当冗余字段是必须的 + +- 可以当作是留痕迹,但id一定要保存 + +类似操作的表直接合并,通过type分辨 + + + +### 代码风格 + +减少硬编码,使用**枚举**,即清晰又解决硬编码问题 + +- 特别是排序方式 + + + + + +### 低并发代码线程安全性问题 + +当系统中并发较低(不引入其他锁的情况下),保证数据库数据的线程安全, + +场景1:增、改数据,保证数据一致性 + +- 查询字段唯一,基本判断(减少直接数据库操作压力) +- 修改(设置数据库字段唯一,做兜底) + +场景2:库存、积分扣除,保证不会超卖 + +- 查询是否到底(基本判断) +- 扣除(数据库数据自增 和 是否满足操作后) + +```Shell + + update point + set point = point + #{ i } + where user_id = #{ userId } + and point + #{ i } >= 0 + +``` + +原理 + +- **InnoDB**,update 基于主键或索引,默认**行锁** +- ⚠️ 没索引或全表扫描,InnoDB 也会退化成**表锁**! +- 并且不需要加事务和显示锁 + +> 如果是等级制,则还需要判断是否为新增,并记录新增数据 + +### 高并发线程安全 + +高并发下,中间加一层,不直接操作数据库 + +- Redis+Lua脚本进行库存扣除 +- 使用mq对数据进行写库操作 \ No newline at end of file diff --git a/posts/blog/编程技术/系统设计/默认地址唯一问题的解决方案.md b/posts/blog/编程技术/系统设计/默认地址唯一问题的解决方案.md new file mode 100644 index 0000000..ec199c2 --- /dev/null +++ b/posts/blog/编程技术/系统设计/默认地址唯一问题的解决方案.md @@ -0,0 +1,28 @@ +--- +title: 默认地址唯一问题的解决方案 +date: 2025-09-17 21:12:02 +tags: [并发] +--- + +## 前言 + +在很多场景都有设置默认数据的行为,比如`默认下单地址`、`默认联系人`等场景,如何优雅、高效的保证默认选项唯一是我想讨论的问题; + +## 分析 + +这个问题说白了就是并发修改问题,流程如下 + +* 查询默认地址/或者直接修改默认地址 + * 有,设置为null +* 插入默认地址 + +修改默认地址都好说,毕竟相当于天然一个乐观锁,即是否成功修改原默认地址相当于是版本号;容易出问题的地方就是**新增默认地址**,多个默认地址新增都以为没有默认地址,导致设置了多个默认地址;针对并发问题,解决方案莫过于各种锁:悲观锁、乐观锁.... + +## 解决 + +今天ai给了一个新思路,通过`数据库字段`唯一解决该问题,由于`是否默认`这个状态无法做为唯一索引的标准,可以新增一个`default_marker`这个字段专门用于标志字段唯一,然后通过数据库字段唯一来解决该问题,并发下使用非常优雅,此时我们的操作只需要保持原逻辑就能完成 + +* 查询默认地址/或者直接修改默认地址 + * 有,设置为null +* 插入默认地址 + diff --git a/posts/blog/编程技术/网络与通信/WebRTC/WebRTC笔记.md b/posts/blog/编程技术/网络与通信/WebRTC/WebRTC笔记.md new file mode 100644 index 0000000..0e6cd8b --- /dev/null +++ b/posts/blog/编程技术/网络与通信/WebRTC/WebRTC笔记.md @@ -0,0 +1,295 @@ +--- +title: WebRTC笔记 +date: 2024-12-28 +tags: [WebRTC] +--- + + +## 建立流程 + +- 本地设备生成SDP + - **SDP**(Session Description Protocol): + - 描述音视频流的格式、编解码器、比特率等。 + - 包括 `offer` 和 `answer`,双方通过这些描述进行协商。 + - 发起方:offer + - 接收方:answer + +大致过程: + +- 发起方(offer)—> 接收方 (接收offer — > answer) —> 接收方(接收 answer ) + + ```SQL + Peer A Peer B + | | + |-- createOffer() -------->| + |-- setLocalDescription()->| + |-- send offer ----------->| + | | + | |-- setRemoteDescription() + | |-- createAnswer() + | |-- setLocalDescription() + |<-------- send answer ----| + | | + |-- setRemoteDescription()->| + ``` + + +**ICE 信息**(Interactive Connectivity Establishment) + +- 描述网络路径和地址信息,是为了实现 NAT 穿透和找到最佳的通信路径所需的候选地址信息 +- 与stun服务器建立连接,获得公网IP +- ICE候选者的收集是在设置本地SDP之后 +- 网络变化,实时交换 + + +```SQL +onIcECandidate -----> addIceCandidate +``` + + + +```SQL +正在加入房间: 123 +webrtc.js:25 WebSocket 连接已建立 +webrtc.js:102 创建 PeerConnection +webrtc.js:34 发送加入房间消息: {type: 'join', room: '123'} +webrtc.js:172 创建 offer +webrtc.js:174 设置本地描述 +webrtc.js:166 信令状态变更: have-local-offer +webrtc.js:182 发送 offer: {type: 'offer', room: '123', data: {…}} +webrtc.js:162 ICE 收集状态变更: gathering +webrtc.js:142 生成新的 ICE candidate: RTCIceCandidate {candidate: 'candidate:1405354849 1 udp 2113937151 0624c9f0-4df…typ host generation 0 ufrag dZNi network-cost 999', sdpMid: '0', sdpMLineIndex: 0, foundation: '1405354849', component: 'rtp', …} +webrtc.js:43 收到消息: {type: 'answer', room: '123', data: {…}} +webrtc.js:46 收到 answer +webrtc.js:43 收到消息: {type: 'ice', room: '123', data: {…}} +webrtc.js:70 收到 ICE candidate +webrtc.js:81 远程描述未设置,将 ICE candidate 加入队列 +webrtc.js:43 收到消息: {type: 'ice', room: '123', data: {…}} +webrtc.js:70 收到 ICE candidate +webrtc.js:81 远程描述未设置,将 ICE candidate 加入队列 +webrtc.js:43 收到消息: {type: 'ice', room: '123', data: {…}} +webrtc.js:70 收到 ICE candidate +webrtc.js:81 远程描述未设置,将 ICE candidate 加入队列 +webrtc.js:166 信令状态变更: stable +webrtc.js:51 远程描述设置成功 +3webrtc.js:58 处理队列中的 ICE candidate 成功 +webrtc.js:142 生成新的 ICE candidate: RTCIceCandidate {candidate: 'candidate:539587617 1 udp 1677729535 111.183.62.21… rport 0 generation 0 ufrag dZNi network-cost 999', sdpMid: '0', sdpMLineIndex: 0, foundation: '539587617', component: 'rtp', …} +webrtc.js:162 ICE 收集状态变更: complete +webrtc.js:43 收到消息: {type: 'ice', room: '123', data: {…}} +webrtc.js:70 收到 ICE candidate +webrtc.js:75 ICE candidate 添加成功 +webrtc.js:43 收到消息: {type: 'ice', room: '123', data: {…}} +webrtc.js:70 收到 ICE candidate +webrtc.js:75 ICE candidate 添加成功 +webrtc.js:43 收到消息: {type: 'ice', room: '123', data: {…}} +webrtc.js:70 收到 ICE candidate +webrtc.js:75 ICE candidate 添加成功 +webrtc.js:43 收到消息: {type: 'ice', room: '123', data: {…}} +webrtc.js:70 收到 ICE candidate +webrtc.js:75 ICE candidate 添加成功 +``` + +```SQL +正在加入房间: 2 +webrtc.js:25 WebSocket 连接已建立 +webrtc.js:102 创建 PeerConnection +webrtc.js:34 发送加入房间消息: {type: 'join', room: '2'} +webrtc.js:172 创建 offer +webrtc.js:174 设置本地描述 +webrtc.js:166 信令状态变更: have-local-offer +webrtc.js:182 发送 offer: {type: 'offer', room: '2', data: {…}} +webrtc.js:162 ICE 收集状态变更: gathering +webrtc.js:142 生成新的 ICE candidate: RTCIceCandidate {candidate: 'candidate:548194722 1 udp 2113937151 2eb7bc86-5760…typ host generation 0 ufrag Wv5Z network-cost 999', sdpMid: '0', sdpMLineIndex: 0, foundation: '548194722', component: 'rtp', …} +webrtc.js:142 生成新的 ICE candidate: RTCIceCandidate {candidate: 'candidate:1671667679 1 udp 2113939711 f9e4fe21-d64…typ host generation 0 ufrag Wv5Z network-cost 999', sdpMid: '0', sdpMLineIndex: 0, foundation: '1671667679', component: 'rtp', …} +webrtc.js:43 收到消息: {type: 'answer', room: '2', data: {…}} +webrtc.js:46 收到 answer +webrtc.js:166 信令状态变更: stable +webrtc.js:51 远程描述设置成功 +webrtc.js:43 收到消息: {type: 'ice', room: '2', data: {…}} +webrtc.js:70 收到 ICE candidate +webrtc.js:75 ICE candidate 添加成功 +webrtc.js:43 收到消息: {type: 'ice', room: '2', data: {…}} +webrtc.js:70 收到 ICE candidate +webrtc.js:75 ICE candidate 添加成功 +webrtc.js:43 收到消息: {type: 'ice', room: '2', data: {…}} +webrtc.js:70 收到 ICE candidate +webrtc.js:75 ICE candidate 添加成功 +webrtc.js:142 生成新的 ICE candidate: RTCIceCandidate {candidate: 'candidate:2487067342 1 udp 1677732095 240e:45d:1a3… rport 0 generation 0 ufrag Wv5Z network-cost 999', sdpMid: '0', sdpMLineIndex: 0, foundation: '2487067342', component: 'rtp', …} +webrtc.js:158 ICE 连接状态变更: checking +webrtc.js:153 连接状态变更: connecting +webrtc.js:158 ICE 连接状态变更: connected +webrtc.js:162 ICE 收集状态变更: complete +webrtc.js:43 收到消息: {type: 'ice', room: '2', data: {…}} +webrtc.js:70 收到 ICE candidate +webrtc.js:75 ICE candidate 添加成功 +webrtc.js:43 收到消息: {type: 'ice', room: '2', data: {…}} +webrtc.js:70 收到 ICE candidate +webrtc.js:75 ICE candidate 添加成功 +webrtc.js:43 收到消息: {type: 'ice', room: '2', data: {…}} +webrtc.js:70 收到 ICE candidate +webrtc.js:75 ICE candidate 添加成功 +webrtc.js:153 连接状态变更: connected +webrtc.js:108 消息通道已打开 +webrtc.js:126 数据通道已打开 +webrtc.js:194 测试数据通道已打开 +webrtc.js:196 发送测试消息: hi +``` + + + + + +```SQL +请输入房间号: 123 +WebRTC 客户端已启动 +服务器地址: ws://localhost:8080/signal +房间号: 123 +按 Enter 键退出程序... +WebSocket 连接已建立 +初始化 PeerConnection +创建 PeerConnectionFactory +配置 RTCConfiguration +创建 PeerConnection +发送加入房间消息: {"type":"join","room":"123","data":null} +收到消息: {"type":"offer","room":"123","data":{"sdp":"v=0\r\no=- 7614051670451549014 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\nm=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\nc=IN IP4 0.0.0.0\r\na=ice-ufrag:dZNi\r\na=ice-pwd:A5U2G86Cnu9feC8YfZUr8Qzl\r\na=ice-options:trickle\r\na=fingerprint:sha-256 C2:38:B4:4A:87:B1:C4:93:EA:23:3D:24:79:96:7E:C0:6C:53:CD:2D:89:0D:0C:41:69:74:8C:E4:E3:AC:F1:11\r\na=setup:actpass\r\na=mid:0\r\na=sctp-port:5000\r\na=max-message-size:262144\r\n","type":"offer"}} +收到 offer,开始处理... +创建 RTCSessionDescription: type=offer, sdp=v=0 +o=- 7614051670451549014 2 IN IP4 127.0.0.1 +s=- +t=0 0 +a=group:BUNDLE 0 +a=extmap-allow-mixed +a=msid-semantic: WMS +m=application 9 UDP/DTLS/SCTP webrtc-datachannel +c=IN IP4 0.0.0.0 +a=ice-ufrag:dZNi +a=ice-pwd:A5U2G86Cnu9feC8YfZUr8Qzl +a=ice-options:trickle +a=fingerprint:sha-256 C2:38:B4:4A:87:B1:C4:93:EA:23:3D:24:79:96:7E:C0:6C:53:CD:2D:89:0D:0C:41:69:74:8C:E4:E3:AC:F1:11 +a=setup:actpass +a=mid:0 +a=sctp-port:5000 +a=max-message-size:262144 + +设置远程描述 +信令状态变更: HAVE_REMOTE_OFFER +创建 answer +收到消息: {"type":"ice","room":"123","data":{"candidate":"candidate:1405354849 1 udp 2113937151 0624c9f0-4dfd-4af6-a42c-6093adbfc7a3.local 49833 typ host generation 0 ufrag dZNi network-cost 999","sdpMid":"0","sdpMLineIndex":0,"usernameFragment":"dZNi"}} +收到 ICE candidate,开始处理... +信令状态变更: STABLE +创建 ICE candidate: sdpMid=0, sdpMLineIndex=0, candidate=candidate:1405354849 1 udp 2113937151 0624c9f0-4dfd-4af6-a42c-6093adbfc7a3.local 49833 typ host generation 0 ufrag dZNi network-cost 999 +添加远程 ICE candidate +发送 answer: {"type":"answer","room":"123","data":{"type":"answer","sdp":"v=0\r\no=- 1332817728761808997 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\nm=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\nc=IN IP4 0.0.0.0\r\na=ice-ufrag:R0Fo\r\na=ice-pwd:rXXIcEPAwbFLpXnyDrO8MaM9\r\na=ice-options:trickle\r\na=fingerprint:sha-256 DF:B7:34:B6:6C:1C:42:91:B1:6F:F9:42:E8:D6:7B:AD:BD:7C:34:00:E1:9B:A4:3E:C8:24:39:1B:33:36:36:9F\r\na=setup:active\r\na=mid:0\r\na=sctp-port:5000\r\na=max-message-size:262144\r\n"}} +ICE 收集状态变更: GATHERING +ICE candidate 添加成功 +生成新的 ICE candidate +发送 ICE candidate: {"type":"ice","room":"123","data":{"sdpMid":"0","sdpMLineIndex":0,"sdp":"candidate:4235741499 1 udp 2122262783 240e:45d:1a30:2bea:3fe6:96ae:8cf5:eda1 53760 typ host generation 0 ufrag R0Fo network-id 2 network-cost 10","serverUrl":null}} +生成新的 ICE candidate +发送 ICE candidate: {"type":"ice","room":"123","data":{"sdpMid":"0","sdpMLineIndex":0,"sdp":"candidate:2549219629 1 udp 2122197247 240e:45d:1a30:2bea:f1dc:13db:95e6:dfe6 53761 typ host generation 0 ufrag R0Fo network-id 3 network-cost 10","serverUrl":null}} +生成新的 ICE candidate +发送 ICE candidate: {"type":"ice","room":"123","data":{"sdpMid":"0","sdpMLineIndex":0,"sdp":"candidate:4208435381 1 udp 2122129151 192.168.137.133 53762 typ host generation 0 ufrag R0Fo network-id 1 network-cost 10","serverUrl":null}} +ICE 连接状态变更: CHECKING +生成新的 ICE candidate +发送 ICE candidate: {"type":"ice","room":"123","data":{"sdpMid":"0","sdpMLineIndex":0,"sdp":"candidate:3002840523 1 tcp 1518283007 240e:45d:1a30:2bea:3fe6:96ae:8cf5:eda1 63525 typ host tcptype passive generation 0 ufrag R0Fo network-id 2 network-cost 10","serverUrl":null}} +生成新的 ICE candidate +发送 ICE candidate: {"type":"ice","room":"123","data":{"sdpMid":"0","sdpMLineIndex":0,"sdp":"candidate:3648126429 1 tcp 1518217471 240e:45d:1a30:2bea:f1dc:13db:95e6:dfe6 63526 typ host tcptype passive generation 0 ufrag R0Fo network-id 3 network-cost 10","serverUrl":null}} +生成新的 ICE candidate +发送 ICE candidate: {"type":"ice","room":"123","data":{"sdpMid":"0","sdpMLineIndex":0,"sdp":"candidate:3025427525 1 tcp 1518149375 192.168.137.133 63527 typ host tcptype passive generation 0 ufrag R0Fo network-id 1 network-cost 10","serverUrl":null}} +收到消息: {"type":"ice","room":"123","data":{"candidate":"candidate:539587617 1 udp 1677729535 111.183.62.212 5243 typ srflx raddr 0.0.0.0 rport 0 generation 0 ufrag dZNi network-cost 999","sdpMid":"0","sdpMLineIndex":0,"usernameFragment":"dZNi"}} +收到 ICE candidate,开始处理... +创建 ICE candidate: sdpMid=0, sdpMLineIndex=0, candidate=candidate:539587617 1 udp 1677729535 111.183.62.212 5243 typ srflx raddr 0.0.0.0 rport 0 generation 0 ufrag dZNi network-cost 999 +添加远程 ICE candidate +ICE candidate 添加成功 +连接状态变更: CONNECTING +连接状态变更: 连接状态变更: CONNECTING +生成新的 ICE candidate +发送 ICE candidate: {"type":"ice","room":"123","data":{"sdpMid":"0","sdpMLineIndex":0,"sdp":"candidate:2468212596 1 udp 1685921535 111.183.62.212 5100 typ srflx raddr 192.168.137.133 rport 53762 generation 0 ufrag R0Fo network-id 1 network-cost 10","serverUrl":"stun:74.125.250.129:19302"}} +ICE 收集状态变更: COMPLETE +ICE 连接状态变更: FAILED +连接状态变更: FAILED +连接状态变更: 连接状态变更: FAILED +P2P 连接失败,请检查网络设置和 ICE 服务器 +``` + +```SQL +请输入房间号: 2 +WebRTC 客户端已启动 +服务器地址: ws://localhost:8080/signal +房间号: 2 +按 Enter 键退出程序... +WebSocket 连接已建立 +初始化 PeerConnection +创建 PeerConnectionFactory +配置 RTCConfiguration +创建 PeerConnection +发送加入房间消息: {"type":"join","room":"2","data":null} +收到消息: {"type":"offer","room":"2","data":{"sdp":"v=0\r\no=- 5238658533245731591 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\nm=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\nc=IN IP4 0.0.0.0\r\na=ice-ufrag:Wv5Z\r\na=ice-pwd:HMxttS8JWw+CLXqJ9AAp9r1y\r\na=ice-options:trickle\r\na=fingerprint:sha-256 01:F9:75:30:A7:19:9F:A1:6E:41:BE:BD:B2:A0:69:BF:5C:7E:1F:D9:4F:32:02:D7:54:96:07:2A:7E:A9:91:5C\r\na=setup:actpass\r\na=mid:0\r\na=sctp-port:5000\r\na=max-message-size:262144\r\n","type":"offer"}} +收到 offer,开始处理... +创建 RTCSessionDescription: type=offer, sdp=v=0 +o=- 5238658533245731591 2 IN IP4 127.0.0.1 +s=- +t=0 0 +a=group:BUNDLE 0 +a=extmap-allow-mixed +a=msid-semantic: WMS +m=application 9 UDP/DTLS/SCTP webrtc-datachannel +c=IN IP4 0.0.0.0 +a=ice-ufrag:Wv5Z +a=ice-pwd:HMxttS8JWw+CLXqJ9AAp9r1y +a=ice-options:trickle +a=fingerprint:sha-256 01:F9:75:30:A7:19:9F:A1:6E:41:BE:BD:B2:A0:69:BF:5C:7E:1F:D9:4F:32:02:D7:54:96:07:2A:7E:A9:91:5C +a=setup:actpass +a=mid:0 +a=sctp-port:5000 +a=max-message-size:262144 + +设置远程描述 +信令状态变更: HAVE_REMOTE_OFFER +创建 answer +收到消息: {"type":"ice","room":"2","data":{"candidate":"candidate:548194722 1 udp 2113937151 2eb7bc86-5760-49a9-8d7e-e7153c328c9b.local 63714 typ host generation 0 ufrag Wv5Z network-cost 999","sdpMid":"0","sdpMLineIndex":0,"usernameFragment":"Wv5Z"}} +收到 ICE candidate,开始处理... +信令状态变更: STABLE +创建 ICE candidate: sdpMid=0, sdpMLineIndex=0, candidate=candidate:548194722 1 udp 2113937151 2eb7bc86-5760-49a9-8d7e-e7153c328c9b.local 63714 typ host generation 0 ufrag Wv5Z network-cost 999 +添加远程 ICE candidate +发送 answer: {"type":"answer","room":"2","data":{"type":"answer","sdp":"v=0\r\no=- 4391799676724420358 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\nm=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\nc=IN IP4 0.0.0.0\r\na=ice-ufrag:daAl\r\na=ice-pwd:Dy7T2XbCCj81FORIkCq2tzVP\r\na=ice-options:trickle\r\na=fingerprint:sha-256 2F:56:8E:31:5C:24:B7:10:03:E8:AC:91:19:BD:23:E8:AA:C6:3A:B5:A9:96:6E:5F:D2:5C:04:5B:E5:FE:CA:21\r\na=setup:active\r\na=mid:0\r\na=sctp-port:5000\r\na=max-message-size:262144\r\n"}} +ICE 收集状态变更: GATHERING +生成新的 ICE candidate +发送 ICE candidate: {"type":"ice","room":"2","data":{"sdpMid":"0","sdpMLineIndex":0,"sdp":"candidate:4235741499 1 udp 2122262783 240e:45d:1a30:2bea:3fe6:96ae:8cf5:eda1 59030 typ host generation 0 ufrag daAl network-id 2 network-cost 10","serverUrl":null}} +生成新的 ICE candidate +ICE candidate 添加成功 +收到消息: {"type":"ice","room":"2","data":{"candidate":"candidate:1671667679 1 udp 2113939711 f9e4fe21-d648-456d-b156-a0dbd8d082de.local 63715 typ host generation 0 ufrag Wv5Z network-cost 999","sdpMid":"0","sdpMLineIndex":0,"usernameFragment":"Wv5Z"}} +发送 ICE candidate: {"type":"ice","room":"2","data":{"sdpMid":"0","sdpMLineIndex":0,"sdp":"candidate:2549219629 1 udp 2122197247 240e:45d:1a30:2bea:f1dc:13db:95e6:dfe6 59031 typ host generation 0 ufrag daAl network-id 3 network-cost 10","serverUrl":null}} +生成新的 ICE candidate +收到 ICE candidate,开始处理... +创建 ICE candidate: sdpMid=0, sdpMLineIndex=0, candidate=candidate:1671667679 1 udp 2113939711 f9e4fe21-d648-456d-b156-a0dbd8d082de.local 63715 typ host generation 0 ufrag Wv5Z network-cost 999 +添加远程 ICE candidate +发送 ICE candidate: {"type":"ice","room":"2","data":{"sdpMid":"0","sdpMLineIndex":0,"sdp":"candidate:4208435381 1 udp 2122129151 192.168.137.133 59032 typ host generation 0 ufrag daAl network-id 1 network-cost 10","serverUrl":null}} +ICE 连接状态变更: CHECKING +ICE candidate 添加成功 +收到消息: {"type":"ice","room":"2","data":{"candidate":"candidate:2487067342 1 udp 1677732095 240e:45d:1a30:2bea:f1dc:13db:95e6:dfe6 63715 typ srflx raddr :: rport 0 generation 0 ufrag Wv5Z network-cost 999","sdpMid":"0","sdpMLineIndex":0,"usernameFragment":"Wv5Z"}} +收到 ICE candidate,开始处理... +创建 ICE candidate: sdpMid=0, sdpMLineIndex=0, candidate=candidate:2487067342 1 udp 1677732095 240e:45d:1a30:2bea:f1dc:13db:95e6:dfe6 63715 typ srflx raddr :: rport 0 generation 0 ufrag Wv5Z network-cost 999 +添加远程 ICE candidate +ICE candidate 添加成功 +连接状态变更: CONNECTING +连接状态变更: 连接状态变更: CONNECTING +生成新的 ICE candidate +发送 ICE candidate: {"type":"ice","room":"2","data":{"sdpMid":"0","sdpMLineIndex":0,"sdp":"candidate:3002840523 1 tcp 1518283007 240e:45d:1a30:2bea:3fe6:96ae:8cf5:eda1 49227 typ host tcptype passive generation 0 ufrag daAl network-id 2 network-cost 10","serverUrl":null}} +生成新的 ICE candidate +发送 ICE candidate: {"type":"ice","room":"2","data":{"sdpMid":"0","sdpMLineIndex":0,"sdp":"candidate:3648126429 1 tcp 1518217471 240e:45d:1a30:2bea:f1dc:13db:95e6:dfe6 49228 typ host tcptype passive generation 0 ufrag daAl network-id 3 network-cost 10","serverUrl":null}} +生成新的 ICE candidate +发送 ICE candidate: {"type":"ice","room":"2","data":{"sdpMid":"0","sdpMLineIndex":0,"sdp":"candidate:3025427525 1 tcp 1518149375 192.168.137.133 49229 typ host tcptype passive generation 0 ufrag daAl network-id 1 network-cost 10","serverUrl":null}} +ICE 收集状态变更: COMPLETE +连接状态变更: CONNECTED +P2P 连接已建立,等待数据通道... +连接状态变更: 连接状态变更: CONNECTED +ICE 连接状态变更: CONNECTED +数据通道已创建: messageChannel +数据通道已创建: testChannel +消息通道状态变更: OPEN +测试通道状态变更: OPEN +数据通道已创建: testChannel +测试通道状态变更: OPEN +收到测试通道消息: hi +通过测试通道发送回复: 服务器回复测试: [hi] +``` \ No newline at end of file diff --git a/posts/blog/编程技术/网络与通信/WebSocket/WebSocket笔记.md b/posts/blog/编程技术/网络与通信/WebSocket/WebSocket笔记.md new file mode 100644 index 0000000..1a8fe68 --- /dev/null +++ b/posts/blog/编程技术/网络与通信/WebSocket/WebSocket笔记.md @@ -0,0 +1,382 @@ +--- +title: WebSocket笔记 +date: 2025-04-28 +tags: [WebSocket] +--- + + + + +2种方式:JavaEE 的方式 , Spring集成 + + + +--- + +🔹1. 使用 `@ServerEndpoint` 和 “方法1”的区别(原理、使用差异、哪种更常见) + +### ✅ 方法1:使用 `@ServerEndpoint` + +这是 **Java EE 标准(Jakarta EE)** 提供的方式。 + +```Java +@ServerEndpoint("/ws/{sid}") +public class MyWebSocket { + @OnOpen + public void onOpen(Session session, @PathParam("sid") String sid) { } + + @OnMessage + public void onMessage(String message, Session session) { } + + @OnClose + public void onClose(Session session) { } + + @OnError + public void onError(Session session, Throwable error) { } +} +``` + +**依赖:** + +- 需要 WebSocket 容器支持(如 Tomcat、Jetty、Undertow)。 +- 在 Spring Boot 中要启用 `ServerEndpointExporter` 来扫描 `@ServerEndpoint`。 + +**原理:** + +- 使用底层容器(Tomcat)对 WebSocket 的支持。 +- 它**不属于 Spring 容器管理的 Bean**,而是由 Web 容器托管。 +- 如果要使用 Spring Bean(如注入 `@Autowired`),需要额外配置(如自定义配置类注入)。 + +**优点:** + +- 简洁、符合 JSR 356 标准。 +- 跨平台(不仅限 Spring)。 + +**缺点:** + +- 不易和 Spring 的生态集成(如 Bean 注入)。 +- 配置扩展较麻烦。 + +--- + +### ✅ 方法2:使用 Spring 封装方式(推荐方式) + +Spring 提供了基于 `WebSocketHandler` 和 `WebSocketConfigurer` 的方式,如: + +```Java +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(new MyHandler(), "/ws").setAllowedOrigins("*"); + } +} +``` + +**依赖:** + +```XML + + org.springframework.boot + spring-boot-starter-websocket + +``` + +**原理:** + +- 使用 Spring 的抽象封装,完全由 Spring 管理。 +- `WebSocketHandler` 是 Spring 提供的处理器,支持注入 Bean、AOP 等。 + +**优点:** + +- 易于集成 Spring(注入服务层 Bean 非常方便)。 +- 配置灵活,扩展性好。 + +**缺点:** + +- 写起来稍微繁琐一些,没有注解那么直观。 + +--- + +✅ 使用情况比较 + +|项目类型|推荐方式| +|---|---| +|标准 Java EE 项目|`@ServerEndpoint`| +|Spring Boot 项目|`WebSocketHandler`| +|简单快速项目|`@ServerEndpoint`(可用但需要处理注入问题)| +|企业级项目(整合服务)|`WebSocketHandler` 更规范和推荐| + +--- + +🔹2. 使用 `@ServerEndpoint` 怎样设置跨域? + +**WebSocket 本质上是跨域的,但只在握手阶段受 CORS 影响。** + +对于 `@ServerEndpoint` 方式,**不能直接使用** `**@CrossOrigin**`**,也不能像 Spring 一样设置**。解决方式: + +✅ 方式一:配置 Tomcat 的握手过滤器(推荐) + +添加一个自定义的 `ServerEndpointConfig.Configurator`: + +```Java +@ServerEndpoint(value = "/ws/{sid}", configurator = MyConfigurator.class) +public class MyWebSocket { + ... +} +``` + +```Java +public class MyConfigurator extends ServerEndpointConfig.Configurator { + @Override + public void modifyHandshake(ServerEndpointConfig config, + HandshakeRequest request, + HandshakeResponse response) { + // 允许所有跨域 + response.getHeaders().put("Access-Control-Allow-Origin", Collections.singletonList("*")); + } +} +``` + +**⚠️注意:** 这是在 WebSocket 握手阶段设置响应头,浏览器依然可能拦截非安全连接(需 HTTPS + 认证策略) + +--- + +✅ 方式二:使用 nginx 代理(生产推荐) + +```Plain +location /ws/ { + proxy_pass http://localhost:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; +} +``` + +这种方式绕开浏览器拦截,**无需后端设置跨域**。 + +--- + +✅ 小结 + +|项目架构|选型建议| +|---|---| +|纯 Java EE|`@ServerEndpoint` + Configurator| +|Spring Boot|Spring WebSocketHandler(推荐)| +|要注入 Bean|不建议用 `@ServerEndpoint`,除非特殊配置| +|要跨域|用 nginx 代理或 `Configurator` 设置| + +如需我给你示范完整代码(比如 `WebSocketHandler` + 配置跨域 + 注入 Service)也可以继续告诉我。 + + + + + +## 携带Token问题 + +### 从url中携带 + + + +### 从协议中携带, + + + +### 浏览器不知道携带请求头(ws) + + + + + +你抓得很准,确实我刚才说的“握手阶段”容易让人误会成 TCP 的“三次握手”——那是传输层的事。我们来仔细拆解一下这个过程,理一理层次和概念。 + +**一、区分几个“握手”的概念** + +**1. TCP 三次握手(传输层)** + +- 用于建立 TCP 连接,跟 WebSocket 没有直接关系。 +- 发生在任何基于 TCP 的协议中(包括 HTTP 和 WebSocket)。 + +**2. HTTP 请求握手(应用层)** + +- 指的是浏览器和服务器建立 WebSocket 连接前,发起的 HTTP 请求。 +- WebSocket 是通过标准的 HTTP 请求发起,然后由服务端通过状态码 101 Switching Protocols 同意“协议升级”(upgrade)到 ws。 + +这就是我们说的 WebSocket 握手阶段,是在应用层,属于 WebSocket 协议本身的一部分。 + +**3. JWT 是应用层的认证机制** + +- Token 本身与底层连接(TCP)无关,它是放在 HTTP 请求头、URL 或消息体中的数据。 +- 所以它是完全在应用层处理的,和 WebSocket 的握手 HTTP 请求可以结合使用。 + +**二、WebSocket 握手流程(含验证)** + +1. 客户端发起 WebSocket 请求,本质是一个带特殊 Header 的 HTTP 请求: + +GET /chat HTTP/1.1 + +Host: example.com + +Upgrade: websocket + +Connection: Upgrade + +Sec-WebSocket-Key: ... + +Authorization: Bearer YOUR_JWT_TOKEN + +2. 服务器收到这个请求,在“握手处理器”中检查 Authorization Header。 + - 验证 JWT(应用层逻辑)。 + - 验证成功后,返回 101 Switching Protocols 响应,升级协议。 + - 否则直接拒绝连接。 + +3. 协议升级完成后,开始 WebSocket 全双工通信。 + +**总结一句话:** + +我说的“握手阶段携带 token”是指 WebSocket 的升级请求(HTTP 层)中带上认证信息,不是 TCP 的三次握手哈。 + +你这个问题问得很专业,如果你有网络协议这方面的背景,我们可以更深入聊聊 WebSocket 跟 HTTP/2 的关系、或者 WebSocket over TLS(wss)的一些坑。想听吗? + + + + + + + + + +你问得特别好,这正是 WebSocket 的“黑箱感”来源之一—— + +**为什么你以前用 WebSocket 没管过协议升级?** + +因为大多数 WebSocket 库(尤其是 Spring Boot、Socket.IO、ws 等)封装了协议升级过程,对开发者屏蔽了细节。你用的可能是前端 new WebSocket(...) 和后端框架自动处理,握手、升级都悄悄完成了。 + +但实际上,在 握手阶段(HTTP -> WS 升级) 里,你是可以拦截并验证请求的,比如做 JWT 校验。 + +**核心代码:Spring Boot + WebSocket + JWT 验证(握手拦截器)** + +下面是精简的核心逻辑,重点是 HandshakeInterceptor 中验证 token: + +**1. 添加 WebSocket 配置类** + +```java +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + +@Autowired + +private JwtHandshakeInterceptor jwtHandshakeInterceptor; + +@Autowired + +private ChatWebSocketHandler chatWebSocketHandler; + +@Override + +public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + +registry.addHandler(chatWebSocketHandler, "/chat") + +.addInterceptors(jwtHandshakeInterceptor) + +.setAllowedOrigins("*"); // 可根据需要设置 CORS + +} + +} +``` + +**2. JWT 验证拦截器(握手前拦截 HTTP 请求)** + +```java +@Component +public class JwtHandshakeInterceptor implements HandshakeInterceptor { +@Override +public boolean beforeHandshake(ServerHttpRequest request, +ServerHttpResponse response, +WebSocketHandler wsHandler, +Map attributes) throws Exception { + +String token = null; + +if (request instanceof ServletServerHttpRequest servletRequest) { + +HttpServletRequest httpRequest = servletRequest.getServletRequest(); + +token = httpRequest.getHeader("Authorization"); + +if (token != null && token.startsWith("Bearer ")) { + +token = token.substring(7); // 去掉 "Bearer " 前缀 + +// TODO: 验证 JWT 的合法性 + +String userId = JwtUtils.verify(token); // 自定义工具类 + +attributes.put("userId", userId); // 传给 WebSocketHandler + +return true; + +} + +} + +response.setStatusCode(HttpStatus.UNAUTHORIZED); + +return false; + +} + +@Override + +public void afterHandshake(ServerHttpRequest request, + +ServerHttpResponse response, + +WebSocketHandler wsHandler, + +Exception exception) { + +} + +} +``` + +**3. WebSocket 消息处理器中获取用户信息** + +```java +@Component +public class ChatWebSocketHandler extends TextWebSocketHandler { + +@Override + +public void afterConnectionEstablished(WebSocketSession session) { + +String userId = (String) session.getAttributes().get("userId"); + +System.out.println("连接建立,用户ID:" + userId); + +} + +@Override + +public void handleTextMessage(WebSocketSession session, TextMessage message) { + +// 处理聊天逻辑 + +} + +} +``` + +**总结:你可以干预协议升级过程** + +- Spring Boot 提供了 HandshakeInterceptor 来做认证,是官方推荐方式。 +- 通过 Header 传 JWT 是安全且标准的方式。 +- 验证通过后,可以将用户信息保存在 session.getAttributes() 里,供后续使用。 + +你要不要我也贴一下 JWT 验证的 JwtUtils.verify() 的代码?还是你那边已有实现? \ No newline at end of file diff --git a/posts/blog/编程技术/计算机相关/快捷操作.md b/posts/blog/编程技术/计算机相关/快捷操作.md new file mode 100644 index 0000000..6fa9fb2 --- /dev/null +++ b/posts/blog/编程技术/计算机相关/快捷操作.md @@ -0,0 +1,78 @@ +--- +title: 快捷操作 +date: 2024-06-02 +tags: [快捷操作] +--- + +**小操作** + +| **操作** | **效果** | +| -------- | ------------------------ | +| win+D | 回桌面(Desk) | +| win+E | 打开我的电脑(资源管理) | +| win+R | 打开运行(Run)窗口 | +| win+L | 锁(Lock)电脑 | +| alt+tab | (Alternative)切应用 | + + + +**常用DOS命令** + +- 选中,右键复制(旧) 新:右键进行操作 + +| **操作** | **效果** | **例子** | +| ---------------------- | ---------------------------- | ---------------------------------------------- | +| dir | 列出当前目录(directory) | dir | +| ipconfig或ipconfig/all | 查看IP地址 | ipconfig/all | +| cls | 清屏(clear screen) | cls | +| ping ip -t | -t是一直ping | ping [www.baidu.com](http://www.baidu.com/) -t | +| exit | 退出窗口 | exits | +| 盘符: | 切盘 | c: | +| del | 删除文件或文件夹的所有文件 | del xxx.txt | +| ren | (rename)重命名目录或文件 | ren Q G | +| mkdir | (make directory)创建目录 | mkdir Q | +| rd | (remake directory)删除空目录 | rd Q | +| cd | (change directory)切换目录 | cd ..(上一级) | +| shutdown | 关机 | | + + + +**批处理** + +dos命令组成的文件(bat) 注意切盘 + + + +**文本编辑器快捷操作** + +| **按键** | **作用** | +| --------- | -------- | +| home | 句首 | +| ctrl+home | 头部 | +| end | 句尾 | +| ctrl+end | 底部 | + + + +**常用的IDEA快捷操作** + +- 单行注释:ctrl + / +- 多行注释:ctrl + shift + / +- 查看源码:按ctrl别松手,鼠标移动到对应的类名下方,出现下划线,点击过去,可以查看类源码。 +- 多行编辑:按alt别松手,鼠标拖动多行,完成多行编辑。 +- 怎么快速生成创建对象语句:类名.new.var +- 怎么快速生成if语句:布尔类型值.if +- 怎么快速生成setter和getter方法:alt + insert,然后选择setter and getter生成。 +- 怎么快速生成构造方法:alt + insert,然后选择Constructor。 +- 在IDEA当中如何自动纠错:将光标移动到错误的位置,按atl + enter。会有提示,根据提示进行纠错。 +- 移动代码到上一行:alt + shift + 上/下方向 +- 怎么快速重写方法?alt + insert,选择Override.... +- 怎么快速重写方法?ctrl + o +- 快速向下转型,并同时生成变量名:变量名.castvar +- 快速查看方法的参数:ctrl + p +- 返回上一步:ctrl + alt + 左方向键。 下一步:ctrl + alt + 右方向键。 +- 代码格式化:ctrl + alt + L。 +- 查看继承结构:ctrl + H +- 自动代码包裹:ctrl + alt + t + + diff --git a/posts/blog/编程技术/计算机相关/编码.md b/posts/blog/编程技术/计算机相关/编码.md new file mode 100644 index 0000000..a0aa336 --- /dev/null +++ b/posts/blog/编程技术/计算机相关/编码.md @@ -0,0 +1,41 @@ +--- +title: 编码 +date: 2024-08-14 +tags: [编码] +--- + +### Unicode码 + +- 为世界上的每个字符都分配了一个对应的值(码点) +- 是一种全世界通用的码表,是一种规范 +- 操作系统中有很好的应用 + + + +### UTF-8编码 + +- 是基于Unicode码的一种可变长度的编码方式,它用于将 Unicode 码点编码为字节序列(用于计算机存储或传输),使用 1 到 4 个字节来表示一个 Unicode 字符 + + + + + +### UTF-16编码 + +- UTF-16 是一种可变长度的编码方式,它可以使用2或4个字节来表示一个 Unicode 字符 +- Char默认使用这种编码方式,对常用字符进行实现,使其更加高效 + + + +### 小结: + +- 一个“中”字,对应的码点是 U+4E2D + - 在UTF-8中对应的是3个字节(E4 B8 AD) + - 在UTF-16中对应的是2个字节(4E 2D) +- 文字复制、黏贴的原理 + - 复制的是Unicode码 ![GetImage](编码/GetImage.png) + +文字的基本显示原理 + +- 所以无论什么文字,只要已知编码格式,得到正确的Unicode码,就可以正确显示(操作系统存储文字时的编码方式可能与文本文件不同,进行了编码转换) +- 操作系统(理解为C程序)从硬盘中读取2进制文件,使用UTF-8进行解码为一个Unicode值(暂时存在内存中),将这个值传给渲染程序经行渲染 diff --git a/posts/blog/编程技术/计算机相关/编码/GetImage.png b/posts/blog/编程技术/计算机相关/编码/GetImage.png new file mode 100644 index 0000000..cd7b60c Binary files /dev/null and b/posts/blog/编程技术/计算机相关/编码/GetImage.png differ diff --git a/posts/blog/编程技术/计算机相关/编程基础.md b/posts/blog/编程技术/计算机相关/编程基础.md new file mode 100644 index 0000000..62af2b3 --- /dev/null +++ b/posts/blog/编程技术/计算机相关/编程基础.md @@ -0,0 +1,88 @@ +--- +title: 编程基础 +date: 2024-07-01 +tags: [编程基础] +--- + +### 进制转换:6(十进制)+4(二进制)+2(八、十六) + +X进制转**十进制**:每一位和权值**相乘**,结果**相加** + +- 二进制:110=1*2^2+1*2^1+0*2^0=4+2+0=6 +- 八进制:210=2*8^2+1*8^1+0*8^0=128+8+0=136 +- 十六进制:A10=10*16^2+1*16^1+0*16^0=2560+16+0=2576 + + + +**十进制**转X进制:除X**取余**,直到商为0,将余数**逆序**输出 + + 二进制 八进制 十六进制 + +6/2=3….0 136/8=17….0 2576/16=161….0 + +3/2=1….1 17/8=2….1 161/16=10….1 + +1/2=0….1 2/8=0….2 10/16=0….10 + + 110 210 A10 + + + +八、十六进制 <===> 二进制 + +- 八进制<---->111:210=010 001 000 +- 十六进制<---->1111:A10=1010 0001 0000 + + + +八进制 <--- 二进制 ---> 十六进制 + +- 210= 010 001 000= 1000 1000= 88 + + + + + +### 原码、反码、补码 + +- 二进制的三种表示形态 +- 计算机底层是以**补码**的形式存储 +- 最高位为符号位,1负,0正 + + + +正数:三码合一(原码、反码、补码相同) + +负数 + +- 原码:绝对值的二进制,最高位改为1 +- 反码:最高位不变,其他位1变0,0变1 +- 补码:最高位不变,最后一位加1 +- 补码->原码:补码取反+1 + + + +为什么计算机使用补码存储数据? + +- 方便计算,正负数补码可以直接计算 +- 解决了正负零问题 +- 简化硬件设计 + + + +一个字节(8位)表示范围:-128 --- 127 + +- 正数:最大值 0111 1111 -> 127 +- 负数:最小值 1000 0000 -> -128 + + + +例如: + +-128 原码:128=1000 0000(-0 被定义为-128) + +​ 反码: 1111 1111 + +​ 补码: 1000 000 原码、补码相同 + + \ No newline at end of file diff --git a/posts/blog/编程技术/计算机相关/计算机概念.md b/posts/blog/编程技术/计算机相关/计算机概念.md new file mode 100644 index 0000000..6c41701 --- /dev/null +++ b/posts/blog/编程技术/计算机相关/计算机概念.md @@ -0,0 +1,22 @@ +--- +title: 计算机概念 +date: 2024-06-08 +tags: [计算机概念] +--- + +硬件 + +- CPU(中央处理器):算力 +- 主板:连接各个部件 +- 内存条:临时存储器 +- 硬盘:永久存储器 +- 显卡(集成显卡or独立显卡):图像处理器 + +GPU是显卡的核心部分 + + + +软件 + +- 系统软件:操作系统 +- 应用软件:QQ、微信 diff --git a/posts/blog/编程技术/计算机相关/软件开发7大原则.md b/posts/blog/编程技术/计算机相关/软件开发7大原则.md new file mode 100644 index 0000000..a3e5bae --- /dev/null +++ b/posts/blog/编程技术/计算机相关/软件开发7大原则.md @@ -0,0 +1,9 @@ +--- +title: 软件开发7大原则 +date: 2025-06-11 +tags: [软件开发7大原则] +--- + +- 开闭原则(OCP):对拓展开发,对修改关闭。即在不修改原代码的基础上,通过添加新的代码来扩展功能。最基本的原则,**多态**是其中的核心概念 +- 单一职责原则 +- ……. diff --git a/posts/blog/计算基础/操作系统/操作系统的理解.md b/posts/blog/计算基础/操作系统/操作系统的理解.md new file mode 100644 index 0000000..2c80768 --- /dev/null +++ b/posts/blog/计算基础/操作系统/操作系统的理解.md @@ -0,0 +1,49 @@ +--- +title: 操作系统的理解 +date: 2024-08-16 +tags: [操作系统] +--- + + +# 操作系统的理解 + +操作系统确实是一个复杂的程序,但要理解为什么只认识0和1的计算机能够“认识”操作系统,我们需要从计算机的工作原理和操作系统的启动过程来探讨。 + +### 1. **计算机硬件只理解0和1**: + +- 计算机硬件的最底层确实只能处理二进制的0和1。这些0和1通过逻辑电路的开关状态(高电平和低电平)来表示,所有的计算和存储最终都归结为一系列二进制操作。 + +### 2. **指令集架构(ISA)**: + +- 计算机的中央处理器(CPU)能够执行一组特定的指令,这些指令是用二进制编码的。这些指令集是CPU硬件理解和执行的基础。 +- 每个指令对应一系列的二进制代码,CPU能够识别并执行这些指令来完成基本的操作,如加法、跳转、加载数据等。 + +### 3. **机器代码与操作系统**: + +- 操作系统实际上是由一系列用机器码(或汇编语言)编写的程序构成的。机器码是CPU能够直接执行的二进制指令。 +- 当计算机启动时,BIOS(基本输入输出系统)或UEFI(统一可扩展固件接口)会从预定义的位置(通常是硬盘、SSD或光盘)加载操作系统的引导程序(Bootloader)。 +- 引导程序是一个简单的二进制程序,它的任务是加载操作系统内核到内存中,并将控制权交给内核。 + +### 4. **操作系统的加载过程**: + +- **启动过程**: + 1. **上电自检(POST)**:计算机启动时,首先进行自检,确认各硬件部件正常工作。 + 2. **加载引导程序**:BIOS/UEFI寻找可启动设备,并加载引导程序到内存中。 + 3. **引导操作系统内核**:引导程序将操作系统内核加载到内存,并将执行控制权交给内核。 + 4. **初始化操作系统**:内核开始运行,初始化硬件、启动系统服务,并最终将控制权交给操作系统的用户空间,启动图形界面或命令行界面。 +- **运行机制**: + - 一旦操作系统内核启动,CPU就开始执行内核中的机器指令。这些指令告诉CPU如何管理内存、调度进程、处理输入输出等。 + - 操作系统本质上是一系列的程序,这些程序都是用机器码编写的,因此CPU可以直接理解和执行它们。 + +### 5. **为什么计算机能识别操作系统**: + +- **硬件与软件的契合**: + - 计算机硬件设计与操作系统是紧密相关的。CPU执行的指令集决定了操作系统的构建方式,因此操作系统的核心部分是直接用机器码编写的。 +- **层层封装和抽象**: + - 尽管操作系统是高层次的软件,但其内核部分非常接近底层硬件,通过系统调用、设备驱动等直接与硬件通信。 + - 操作系统封装了硬件的复杂性,对外提供了更易用的接口和服务,同时也向上层应用程序提供了抽象,使得开发者无需直接与硬件打交道。 + +### 6. **总结**: + +- 虽然计算机硬件只识别0和1,但通过一系列的编码规则(如指令集架构),这些0和1可以表示复杂的操作和逻辑。 +- 操作系统作为一个用机器码编写的程序,其指令被设计成CPU能够直接理解和执行的形式。因此,计算机能够“识别”操作系统,并在硬件上正确运行。通过这个过程,操作系统可以管理硬件资源,并为应用程序提供运行环境。 \ No newline at end of file diff --git a/posts/blog/计算基础/操作系统/操作系统知识.md b/posts/blog/计算基础/操作系统/操作系统知识.md new file mode 100644 index 0000000..f6fd1d5 --- /dev/null +++ b/posts/blog/计算基础/操作系统/操作系统知识.md @@ -0,0 +1,488 @@ +--- +title: 操作系统知识 +date: 2025-01-06 +tags: [操作系统] +--- + + +## 基本概念 + +### **操作系统特点** + +- 并发 + - 一段时间内多个程序运行 +- 并行 + - 一个时间点有多个程序运行(必须多个cpu) +- 共享 + - 互斥共享:读占资源 + - 同时共享:磁盘设备 +- 虚拟 + - **时分复用**(按时间来复用) + - 单核cpu并发 + - **空分复用**(按空间来复用) + - 电脑磁盘分区(D、C盘) +- 异步 + - 程序之间不用互相等待 + +> [!important] 最基本的特征:**并发**和**共享**,两者互为存在条件 + +### **操作系统的历史** + +- 手工操作阶段 + - 人太慢了,处理机**利用率低** +- 单道批处理 + - 引入**脱机输入/输出技术**,缺点只能有一道程序运行 +- 多道批处理(**正式的操作系统**) + - 多道多个程序运行,引用了中断技术,**由操作系统负责管理这些程序的运行**,缺点,**用户响应时间长** +- 分时操作系统 + - 以**时间片为单位**为程序提供服务,**通过终端与计算机进行交互**,缺点**无法优先处理紧急任务** +- 实时操作系统 + - 弥补以上缺陷 + - 硬实时系统 + - 乱实时任务 + +~~**操作系统的结构设计**~~ + +**中断** + +- 内中断 +- 外中断:人为干涉 + +**原语** + +- 若干条指令组成,**不允许中断** + +**处理机状态** + +- 内核态(目态):有限的指令 +- 用户态(管态):计算机中的多种指令 +- 状态切换,系统调用 +- 从内核态到用户态只有唯一的途径:**特权指令** +- 当系统运行遇到**访管指令**时,一定会发生从用户态跳转到内核态 + +## 进程管理 + +进程(PCB:进程控制块)的**调度、同步、控制、安全** + +### 进程与线程 + +- 进程是**系统分配资源的最小的单位** +- 线程是**cpu调度的最小单位** + +### **处理器调度** + +调度三个层次 + +- **高级调度**:外存 —> 内存(面向作业) +- **中级调度**:外存 —> 内存(面向进程) +- **低级调度**:内存 —> CPU(发起频率高) + +方式 + +- 非抢占式 +- 抢占式 + +### **调度准则** + +- **系统的吞吐量** = 总完成作业数 / 总时间 +- **cpu的利用率** = CPU有效工作时间 / 总时间 +- 响应时间:提交到首次响应时间 (第一次执行时间 - 到达时间) +- **周转时间**:提交到完成的时间(完成时间 - 到达时间) + - 等待时间:等待服务的时间(周转时间 - 运行时间) + - 平均周转时间 = 周转时间之和 / 作业数 + - 带权周转时间 = 周转时间 / 运行时间 + - 平均带权周转时间 = 带权周转时间之和 / 作业数 + +### **调度算法** + +- 先来先服务(FCFS):作业不被抢占 +- 短作业优先(SJF):作业不被抢占 + - **导致饥饿现象**(等太久了) +- 高响应比优先调度算法:作业不被抢占 + - 第一个正常算,执行完毕 + - 决定下一个作业,越大则先:(等待时间+执行时间)/ 执行时间 +- 最短剩余时间优先(SRTF) +- 优先级调度算法 +- 多级反馈队列调度法 +- 时间片轮状调度算法 + +### **进程状态转换** + +- 创建 —> 就绪 —> 运行 —> 阻塞(IO操作) —> 终止 + + ![image 54.png](操作系统知识/image54.png) + + +### **进程同步** + +- 同步 +- 互斥 + +- 信号量 + - 大于0表示可用,其他表示不可以用(锁了) + - pv操作 + - p操作表示加锁,执行p操作,并将信号量-1(线程安全操作) + - v表示释放锁,执行v操作,将信号量加1(线程安全操作) + +**临界区** + +- 互斥锁 +- 信号量 +- 临界区资源 + - 空闲让出、有限等待、让权等待、忙时等待 + +- **生产者消费者问题** + + ```Plain + 缓存区空时,消费者不能消费 + 缓冲区满时,生产者不能添加数据 + + 确定互斥资源 + + int n; //缓冲区大小 + semaphore mutex = 1; //信号量 + semaphore empty = n; //空闲个数 + semaphore full = 0; //使用个数 + project + + n=2; + semaphore m = 1; + semaphore e = 2; + semaphore f_a = 0; + semaphore f_o = 0; + + while(1){ + p(e); + p(m); + 添加苹果 + v(m); + v(f_a); + } + + while(1){ + p(f_a); + p(m); + 取出苹果 + v(m); + v(e); + 吃苹果 + } + ``` + + + + +管程 + +**死锁** + +- 银行家算法 + - 题目,资源数10 10 10 + + |进程|最大需求|已分配资源| + |---|---|---| + |p1|9 8 5|2 1 1| + |….|….|….| + + - 第一步:计算可用资源 + - 第二步:计算还需要的资源 + - 第三步:进行资源匹配 + +## 内存管理 + +- 对内存的划分和动态分配 + +**功能** + +- 空间分配与回收 +- 地址转换:**将逻辑地址转成物理地址** +- 内存空间的扩充:**虚拟存储技术**,从逻辑上扩充主存 +- 存储保护 + +源程序 —>可执行程序 + +- 编译 +- 链接 + - 静态链接 + - 装入时动态链接 + - 运行时动态链接 +- 装入 + - **绝对装入**:真实地址 + - **静态可重定向装入**:逻辑与真实地址不同 + - 动态运行时装入:可以不是连续区域 + +**覆盖** + +- 将程序分段,常用的段常驻内存(解决了程序大小大于物理内存大小的问题) + +**交换** + +- 空间紧张时将某些进程暂时换出外存 + +**连续分配管理方式** + +连续内存空间,包括**单一连续分配、固定分区分配和动态分区分配** + +- 内部碎片 +- 外部碎片 + + + + + +### 地址转换 + +给页号、块号,求当页面大小为X,逻辑地址P对应的物理地址 + +- 逻辑地址(16进制) ---> 二进制(1-> 0001) +- 页面大小:2的n次方 +- 将2进制分割,从小到大的n位,前:页号,后:不变 +- 替换页号(二进制),生成新16进制 + + ![image 1 32.png](操作系统知识/image132.png) + + + + + + +### 可变分区管理 + +首次适应(first fit) + +- 从左到右,看分区大小是否满足 +- 修改起始地址、分区大小 + +下次适应:首次之后以当前位置 + +最佳适应(best fit) + +- 排序,选择第一个能装的分区 +- 修改起始地址、分区大小 + +最坏适应:逆序 + + + +```Plain +10008, +1 0000 0000 0000 1000 +其对应段起始地址为0X6000 +0110 0000 0000 0000 0000 0000 1000 +6 + +60 120 40 40 +``` + + + +字符设备:打印机、键盘 + +块设备:磁带、硬盘 + +## 大题 + +### 处理器调度 + +- 先来先服务 +- 短作业优先 + +### pv操作 + +- 生产者消费者问题 + +### 银行家算法 + +- 安全序列 + +### 重定向分页、分段 + +- 虚拟地址转换为物理地址的过程 + +页面置换算法 + +- 缺页次数 = 总列 - 空列;缺页率 = 缺页次数 / 总列 +- 先进先出(FIFD)页面置换算法 + - 不同则添加,把滞留最久的去掉 + - 同则空出来 +- 最近最久未使用(LRU) + - 不同则将最前面划掉 + - 同则将相同划掉空出来 + +### 驱动调度 + +磁盘移动臂调度算法 + +- 计算磁道位置 +- SCFS 先来先服务 + - 按顺序算 +- SSTF 最短寻找时间优先算法 + - 列出位置,近的先算 +- SCAN算法(电梯调度算法) + - 向那个方向移动、到不到边缘 +- C-SCAN算法 + - 向那个方向移动、到不到边缘 + +### 设备独立性 + +**设备独立性**是操作系统中的一个重要概念,指的是操作系统能够将硬件设备的细节隐藏起来,使得应用程序和用户不需要关心具体的硬件设备细节,就能够通过统一的接口与各种设备进行交互。这种特性使得应用程序能够在不同的硬件环境中运行,而无需修改代码,从而提高了软件的可移植性和硬件的可扩展性。 + +在操作系统设计中,设备独立性通常包括以下几个方面的考察: + +1. **抽象设备接口**: + +操作系统通过提供统一的抽象设备接口,隐藏了硬件设备的具体实现细节。用户和应用程序通过这个接口与设备交互,而不需要关注设备的物理特性(如硬盘、打印机、显示器等)。 + +- 例如,磁盘 I/O 操作的接口(如 `read()`、`write()`)是统一的,不同的磁盘设备(不同品牌、不同型号)可以通过不同的驱动程序实现,但操作系统提供给应用程序的接口是相同的。 + +2. **设备驱动程序**: + +设备独立性的重要实现之一是通过设备驱动程序。每种设备(如打印机、硬盘、键盘)都有一个特定的驱动程序来管理硬件的具体操作。操作系统通过设备驱动与硬件进行通信,而应用程序与操作系统之间的交互是设备无关的。 + +- **驱动程序**的作用是将操作系统对设备的请求转化为硬件可以理解的命令,同时也将硬件的状态反馈给操作系统。应用程序通过标准化的接口与操作系统进行交互,而不需要直接处理硬件细节。 + +3. **硬件抽象层(HAL, Hardware Abstraction Layer)**: + +硬件抽象层是操作系统设计中的一层,它通过封装硬件设备的特性,提供一个统一的硬件接口,使得上层的软件不需要关注硬件的差异。 + +- 通过硬件抽象层,操作系统可以支持多种硬件设备,而应用程序和操作系统的其他部分无需修改,即可在不同硬件上运行。 + +4. **设备无关性(Device Independence)与文件系统**: + +在文件系统中,设备独立性还体现在用户与文件系统的交互上。用户通过文件系统进行读写操作时,文件的存储位置、文件存储的设备类型(如硬盘、SSD、网络存储等)对用户来说是透明的。操作系统通过不同的设备驱动来处理这些细节,但用户并不需要了解它们。 + +- 例如,用户可以通过标准的文件接口(如 `open()`、`read()`、`write()`)来操作文件,而文件存储在不同的设备上,操作系统会根据设备类型来选择适当的驱动程序执行操作。 + +5. **设备独立性的优点**: + +- **可移植性**:应用程序不需要关心底层硬件细节,程序能够在不同的硬件平台上运行,只需依赖操作系统的设备接口。 +- **硬件扩展性**:当新的硬件设备被加入到系统时,操作系统只需开发相应的驱动程序,而不需要修改已有的应用程序或上层系统。 +- **简化用户操作**:用户不需要了解或操作具体硬件设备的细节,可以通过统一的接口进行设备管理。 + +6. **考察内容**: + +在操作系统相关的考试或面试中,**设备独立性**通常考察以下几个方面: + +- **概念理解**:理解设备独立性的含义、目的和优势。 +- **设备驱动程序的作用**:理解设备驱动程序如何帮助操作系统与硬件设备交互,屏蔽硬件差异。 +- **硬件抽象层的作用**:理解硬件抽象层在实现设备独立性中的角色。 +- **文件系统的设备独立性**:理解操作系统如何通过文件系统实现对硬件设备的透明访问。 + +7. **设备独立性的例子**: + +- **磁盘设备**:应用程序可以通过文件系统接口进行文件操作,而无需关心文件到底存储在硬盘、SSD还是网络存储上。 +- **打印机**:操作系统为打印任务提供统一的打印接口,用户无需关心使用的是激光打印机、喷墨打印机,还是虚拟打印机。 +- **输入设备**:无论是键盘、鼠标、触摸屏,操作系统都提供统一的输入接口,应用程序通过标准的输入接口接收用户输入,而不关心硬件的差异。 + +**总结:** + +**设备独立性**主要考察操作系统如何通过抽象设备接口、设备驱动程序和硬件抽象层,使得应用程序和用户可以忽略硬件设备的差异,通过统一的接口与各种设备进行交互,从而提高系统的可移植性和可扩展性。在考试中,可能会考察这些技术如何实现、它们的优点,以及如何通过这些技术实现硬件与软件的解耦。 + +### 结果不唯一 + +- 讨论结果 + +### 七态模型(处理器调度层次) + +- 新建 —> 就绪 —> 运行 —> 阻塞 —> 终止 + +在操作系统中,**处理器调度层次**(Processor Scheduling Levels)通常是指调度策略在系统中的不同层次和作用范围。不同层次的调度负责不同粒度的调度任务,涉及到从进程、线程到硬件层面的调度。在操作系统的考试中,关于处理器调度层次的考察,通常会聚焦于以下几个方面: + +### 1. **调度层次的基本概念**: + +处理器调度通常分为多个层次,每个层次负责不同类型的调度任务: + +### 1.1 **长程调度(Long-term Scheduling)** + +- **定义**:长程调度是操作系统决定哪些进程应当进入 **就绪队列**,即从 **新建状态** 到 **就绪状态** 的调度过程。长程调度负责系统中**进程的引入**,通常决定的是进程的**数量**。 +- **作用**: + - 控制系统中的 **进程数量**,避免系统被过多的进程占据资源而导致性能下降。 + - 决定进程是否可以进入就绪队列,并在适当的时候 **创建新进程**。 + - 常见于**批处理系统**,通常是由操作系统根据系统负载来调整。 + +### 1.2 **中程调度(Medium-term Scheduling)** + +- **定义**:中程调度是在长程调度和短程调度之间进行的调度,它涉及进程的 **交换**。中程调度决定了哪些进程从 **就绪队列** 移动到 **阻塞队列** 或 **从阻塞队列恢复到就绪队列**。 +- **作用**: + - **交换进程**:操作系统根据系统的资源利用情况,通过进程的**交换**,让进程在内存和外存之间移动。这样可以优化系统的**响应性**和**资源利用**。 + - 通过 **进程切换**,操作系统可以实现对 **内存使用** 的更好管理,避免内存碎片化。 + - 也可以用于控制进程的 **暂停与恢复**,例如在负载较高时将一些进程暂时从内存移出(称为交换出),减轻内存压力。 + +### 1.3 **短程调度(Short-term Scheduling)** + +- **定义**:短程调度,又称为 **CPU调度**,负责决定在 **就绪队列** 中的多个进程中,哪个进程会被分配 CPU 时间,进入 **运行状态**。 +- **作用**: + - 在所有就绪进程中选择一个来运行,通常根据调度算法(如 **先来先服务 FCFS**、**最短作业优先 SJF**、**时间片轮转 RR** 等)来决定执行顺序。 + - 这是最常见的调度,涉及到 CPU 时间的分配,它直接影响系统的 **响应时间**、**吞吐量** 和 **公平性**。 + +### 1.4 **进程调度粒度与上下文切换**: + +- **进程调度粒度**:调度的粒度指的是调度行为的**频繁程度**,即操作系统多长时间执行一次调度。粒度越小,调度越频繁,系统的响应性越强,但同时开销也较大。 +- **上下文切换(Context Switching)**:短程调度时,操作系统需要将当前进程的状态保存起来,然后恢复下一个进程的状态。这是调度的一部分,也是一个较为昂贵的操作。上下文切换的效率直接影响系统的调度效率。 + +### 2. **不同层次调度的考察内容**: + +在操作系统的考试或面试中,处理器调度层次可能会考察以下几个方面: + +### 2.1 **调度层次的定义与区别**: + +考察学生对 **长程调度**、**中程调度**、**短程调度** 等不同层次的定义和作用的理解。学生需要清楚不同层次的调度各自负责的任务和它们之间的区别。 + +### 2.2 **各层次调度的作用**: + +- 如何通过长程调度控制系统中的进程数量,以避免资源的过度占用。 +- 中程调度如何通过进程的交换来优化系统的内存使用。 +- 短程调度如何通过合适的调度算法优化 CPU 使用效率。 + +### 2.3 **调度算法的影响**: + +- 每个调度层次可能会应用不同的调度算法。短程调度常常使用不同的算法来优化性能,如 **FCFS**、**SJF**、**RR**、**优先级调度** 等。 +- 考察学生如何根据不同层次的调度需求选择合适的调度算法。 + +### 2.4 **进程调度的实现与优化**: + +- 在操作系统中,如何通过优化调度算法(如 **多级反馈队列**)来平衡系统负载,避免某些进程的饥饿。 +- 讨论如何通过调度优化系统性能,特别是在 **多任务** 系统中如何保持系统的响应性。 + +### 2.5 **调度开销和性能**: + +- 每个调度层次都伴随着一定的开销。长程调度的开销较低,但它的任务是为了优化整个系统的负载;中程调度则需要考虑内存的交换问题;短程调度的开销可能较高,尤其是在频繁进行上下文切换的情况下。 + +### 2.6 **死锁与调度**: + +- 不同层次的调度对死锁的影响也是常见的考点。例如,在长程调度中,操作系统可以通过控制进程数目来避免系统过载并减少死锁的风险。 +- 在短程调度时,系统可能需要避免进程之间相互等待的情况,以防止死锁发生。 + +### 2.7 **调度与多核处理器**: + +- 在多核处理器的系统中,每个处理器可能有自己的调度策略。考察如何在多个核心上分配任务,如何实现进程的负载均衡,如何设计适应多核系统的调度策略。 + +### 3. **考试中可能出现的问题**: + +- **简答题**:问你各个调度层次的定义、作用及区别。 +- **算法题**:给定一些进程,基于不同的调度层次和调度算法,计算系统的**平均等待时间**、**平均周转时间**,并分析其性能。 +- **案例分析**:给出具体的进程调度场景,要求考生根据不同调度层次来优化系统性能,或者讨论如何避免死锁、饥饿等问题。 + +### 4. **实例分析**: + +假设有一个简单的多进程系统: + +- **长程调度**:操作系统根据负载情况决定是否引入新的进程。 +- **中程调度**:系统内存不足时,操作系统将一些进程从内存中交换到磁盘。 +- **短程调度**:操作系统通过 **RR** 或 **SJF** 算法来决定哪些进程获得 CPU 执行时间。 + +每个层次的调度都有其不同的目标: + +- 长程调度的目标是避免资源过载; +- 中程调度的目标是优化内存使用; +- 短程调度的目标是尽量公平高效地分配 CPU 时间。 + +### 总结: + +**处理器调度层次**的考察主要是对操作系统如何在不同粒度上调度进程进行深入了解,包括长程调度、中程调度和短程调度的作用和实现方式。理解不同层次的调度可以帮助设计和优化系统的资源管理,解决死锁、饥饿等问题,并通过合理的调度算法提高系统的性能。 + + + + + +![QQ_1736258399599.png](操作系统知识/QQ_1736258399599.png) \ No newline at end of file diff --git a/posts/blog/计算基础/操作系统/操作系统知识/QQ_1736258399599.png b/posts/blog/计算基础/操作系统/操作系统知识/QQ_1736258399599.png new file mode 100644 index 0000000..feb4eff Binary files /dev/null and b/posts/blog/计算基础/操作系统/操作系统知识/QQ_1736258399599.png differ diff --git a/posts/blog/计算基础/操作系统/操作系统知识/image132.png b/posts/blog/计算基础/操作系统/操作系统知识/image132.png new file mode 100644 index 0000000..a748215 Binary files /dev/null and b/posts/blog/计算基础/操作系统/操作系统知识/image132.png differ diff --git a/posts/blog/计算基础/操作系统/操作系统知识/image54.png b/posts/blog/计算基础/操作系统/操作系统知识/image54.png new file mode 100644 index 0000000..f5b1f69 Binary files /dev/null and b/posts/blog/计算基础/操作系统/操作系统知识/image54.png differ diff --git a/posts/blog/计算基础/操作系统/操作系统题目.md b/posts/blog/计算基础/操作系统/操作系统题目.md new file mode 100644 index 0000000..1c244d8 --- /dev/null +++ b/posts/blog/计算基础/操作系统/操作系统题目.md @@ -0,0 +1,30 @@ +--- +title: 操作系统题目 +date: 2024-09-04 +tags: [操作系统] +--- + + + + +计算机系统的层次结构 + +- 应用软件层 +- 支撑软件层 +- 硬件层 + + + +资源管理 + +题目描述:当采用可变分区分配方案对512KB的内存进行管理时,要求输入多个进程已经占用分区信息、多个进程内存回收信息,输出显示空闲分区的个数。 +输入格式:程序要求输入3行,以回车符号作为分隔,第一行是一个整数n(10>n>0),表示n个进程已经占用分区;第二行是2n个整数,以空格作为分隔,依次按地址递增对应第一行n个进程(进程编号依次为p1、p2、p3….pn,p为小写字母)已经占用分区的起始地址和存储容量(单位为K8)。第三行是一个字符串,用进程编号表示所回收的进程,编号之间用空格作为分隔符。 +输出格式:输出一个整数表示回收后空闲分区的个数,若有回收的进程不存在,则输出“false”(不含双引号,所有字母皆为小写) +输入样例 +2 +0 60 100 220 +P1 +输出样例 +2 + +解释一下这个题目,并解释一下样例,因为我根本不懂可变分区分配方案,请不要提供答案给我 \ No newline at end of file diff --git a/posts/blog/计算基础/架构/B-S和C-S架构.md b/posts/blog/计算基础/架构/B-S和C-S架构.md new file mode 100644 index 0000000..5b46457 --- /dev/null +++ b/posts/blog/计算基础/架构/B-S和C-S架构.md @@ -0,0 +1,47 @@ +--- +title: B-S和C-S架构 +date: 2024-08-15 +tags: [架构] +--- + + +BS(Browser-Server)架构和CS(Client-Server)架构是两种常见的软件架构模式,它们在客户端与服务器的交互方式、部署和管理上有显著的区别。 + +### 1. **架构模式** + +- **BS架构**:在BS架构中,客户端使用浏览器访问服务器。应用程序的主要逻辑和处理都在服务器端完成,客户端只负责展示和交互。典型的BS应用包括网页应用程序。 +- **CS架构**:CS架构则需要客户端安装专用的软件,这些客户端软件和服务器进行交互。应用程序的部分逻辑可以分布在客户端和服务器端。常见的CS应用包括桌面应用程序和一些企业内部系统。 + +### 2. **部署与维护** + +- **BS架构**:客户端只需浏览器即可使用,无需额外的软件安装和更新,更新维护集中在服务器端,用户只需刷新页面即可获得最新版本。 +- **CS架构**:客户端软件需要安装、配置和更新。对于多用户环境下的升级,可能需要对每个客户端进行更新,这增加了维护成本。 + +### 3. **性能与交互** + +- **BS架构**:由于客户端仅承担展示和基本交互,较多的计算和逻辑处理都在服务器端完成。因此,对于高交互性或需要大量计算的应用,BS架构可能表现不如CS架构。 +- **CS架构**:客户端可以承担更多的计算任务,这使得它在高交互性和复杂计算场景下有更好的性能表现。同时,本地应用程序可以更好地利用硬件资源,比如GPU加速。 + +### 4. **适用场景** + +- **BS架构**:适合跨平台的应用、对部署和维护要求较高的系统、互联网产品等。例如:电子商务网站、社交媒体平台、在线办公软件。 +- **CS架构**:适合企业内部系统、需要高性能或本地资源调用的应用。例如:ERP系统、CAD软件、视频游戏。 + +### 5. **网络依赖性** + +- **BS架构**:高度依赖网络连接,通常需要持续的网络连接才能正常工作。 +- **CS架构**:虽然通常也需要网络连接,但某些应用可以在离线状态下部分或完全工作,视具体实现而定。 + +总结来说,BS架构适用于广泛用户、跨平台、易部署的场景,而CS架构则在高性能、复杂交互、本地资源调用等场景中更为适合。 + + + +BS架构: + +- 网络依赖(基于tcp协议)http/https协议 + +CS架构: + +- 网络依赖基于tcp协议实现的协议 ,可自定义 + +![image 38.png](B-S和C-S架构/image38.png) \ No newline at end of file diff --git a/posts/blog/计算基础/架构/B-S和C-S架构/image38.png b/posts/blog/计算基础/架构/B-S和C-S架构/image38.png new file mode 100644 index 0000000..9926a24 Binary files /dev/null and b/posts/blog/计算基础/架构/B-S和C-S架构/image38.png differ diff --git a/posts/blog/计算基础/缓存/缓存笔记.md b/posts/blog/计算基础/缓存/缓存笔记.md new file mode 100644 index 0000000..c780cd7 --- /dev/null +++ b/posts/blog/计算基础/缓存/缓存笔记.md @@ -0,0 +1,21 @@ +--- +title: 缓存笔记 +date: 2024-09-11 +tags: [缓存] +--- + + +- 简单理解,就是将使用过的数据进行存储,下次访问直接获取,提高效率 +- 应用: + + **CPU高速缓存** + + - 内存处理速度慢,cpu进行缓存 + + **内存缓存** + + - 磁盘读写慢,内存进行缓存 + - Redis数据库 +- 优点:访问速度更快 +- 缺点: + - 缓存数据和主内存数据(正常存储数据)不一致 \ No newline at end of file diff --git a/posts/blog/计算基础/编码/编码问题 2.md b/posts/blog/计算基础/编码/编码问题 2.md new file mode 100644 index 0000000..eae0955 --- /dev/null +++ b/posts/blog/计算基础/编码/编码问题 2.md @@ -0,0 +1,25 @@ +--- +title: 编码问题 2 +date: 2024-08-14 +tags: [编码] +--- + + + + +## Tip + +将byte转为int型,保留数据的二进制 + +```Java +byte a=-1;//二进制:1111 1111 +int b=a;//二进制变为:4个1111 1111 +int b=b>>24;//继续补1,恢复失败! + +//使用&0xFF +int c=a & 0xff; +//oxff表示:0000 .... 0000 1111 1111 +// &右边第1、3全是0,第4个字节全为1,则全看左边的数,是1留1,是0得0,完美得保存了1个字节数据 +``` + +## Java转码的底层细节 \ No newline at end of file diff --git a/posts/blog/计算基础/编码/编码问题.md b/posts/blog/计算基础/编码/编码问题.md new file mode 100644 index 0000000..6db62e9 --- /dev/null +++ b/posts/blog/计算基础/编码/编码问题.md @@ -0,0 +1,116 @@ +--- +title: 编码问题 +date: 2024-08-16 +tags: [编码] +--- + + +在一台默认编码为UTF-8的计算机上打开一个UTF-8编码的文件时,会涉及到一系列的二进制数据传输和处理。整个过程可以分为以下几个阶段,每个阶段都涉及到不同类型的二进制数据的产生和使用。 + +### 1. **文件系统层面的二进制数据**: + +- **文件存储**: + - 文件以二进制形式存储在硬盘或其他存储设备上。对于UTF-8编码的文件,其内容已经以UTF-8编码的形式存储为二进制数据。 + - 例如,文件中包含字符串“你好”(在UTF-8中,`你`的二进制编码为`11100110 10100010 10101100`,`好`为`11100110 10110001 10001101`),存储在硬盘上时,就是对应的这串二进制数据。 +- **文件元数据**: + - 文件系统还会存储关于文件的元数据,如文件名、大小、修改时间等。这些元数据也是以二进制形式存储和处理的。 + - 文件名可能也是UTF-8编码的字符串,在存储时会产生对应的二进制数据。 + +### 2. **操作系统层面的二进制数据**: + +- **文件读取请求**: + - 当用户或程序请求打开文件时,操作系统通过系统调用向文件系统发出读取请求。这些请求包括文件路径、文件名等信息,都会以二进制形式传递。 + - 操作系统需要解析文件路径和文件名,将其转换为磁盘上的具体位置,并发出读取指令。 +- **文件数据的读取**: + - 操作系统通过驱动程序与硬盘等存储设备通信,读取文件的数据块。这些数据块是从磁盘上读取的二进制数据,操作系统将它们加载到内存中。 + - 文件内容在内存中也保持UTF-8编码的二进制形式。 + +### 3. **应用层面的二进制数据**: + +- **文件内容解析**: + - 应用程序(如文本编辑器)打开文件后,会读取内存中的二进制数据,并按照UTF-8编码对这些数据进行解析和解码。 + - 解码的过程中,应用程序将UTF-8的二进制数据转换为对应的Unicode码点。例如,`11100110 10100010 10101100`(即`你`的UTF-8编码)解码为Unicode码点`U+4F60`。 +- **字符渲染**: + - 一旦字符被解码成Unicode码点,应用程序可能会通过操作系统的图形接口或库(如字体库)来渲染字符。 + - 字符渲染过程涉及将Unicode码点映射为字形(glyph),这些字形通常以矢量或位图的形式表示,也会涉及大量的二进制数据。 +- **图形输出**: + - 最终,应用程序将渲染后的图像数据(通常以像素的RGB值或其他颜色模型表示)发送给图形处理单元(GPU)或显示控制器。图像数据也是以二进制形式传输的。 + +### 4. **硬件层面的二进制数据**: + +- **磁盘到内存的数据传输**: + - 文件数据以二进制形式从硬盘传输到内存。这个传输过程涉及到总线(如PCIe或SATA)的二进制信号传输。 +- **内存到CPU的数据传输**: + - 当数据被处理时,会在内存和CPU之间通过系统总线进行二进制传输。 + - CPU需要执行的一些指令也会涉及到与文件内容相关的二进制数据处理,如解码、渲染等。 +- **内存到显示器的数据传输**: + - 最终,渲染后的图像数据会被送到显卡,然后通过显卡传输到显示器上,这些数据依然是二进制形式。 + +### 5. **总结:整个过程中产生的二进制数据**: + +- **文件内容的二进制**:UTF-8编码的文件内容在硬盘和内存中都以二进制形式存在。 +- **系统调用和文件读取的二进制**:操作系统发出和接收的文件读取请求、路径解析等都是二进制数据。 +- **文件内容解码**:应用程序解码UTF-8数据时产生的Unicode码点也是以二进制形式存在。 +- **字符渲染与图形数据**:字符被渲染成图像时,生成的图像数据也以二进制形式传输和处理。 + +每个阶段都会涉及到不同形式的二进制数据,这些数据在计算机系统中不断流转,最终实现从存储到显示的全过程。 + + + +你的问题切中要点。让我们理清概念并回答这个问题: + +1. 解码的过程与结果: + - UTF-8、UTF-16、UTF-32 是三种不同的编码方式,用于将Unicode码点转换为二进制数据以便存储和传输。 + • Unicode码点 是一种抽象的表示形式,例如,字符“你”的Unicode码点是U+4F60。这个码点本身并没有规定具体的存储形式。 +2. UTF-8 是一种编码,而非编码后的结果: + - 当你说“解码”,指的是将存储在文件中的UTF-8编码数据(即一串二进制数据)转换为可以处理和显示的Unicode码点。 + • 当应用程序解码时,通常会将这些数据转化为一种内存中更易操作的形式。这时,内存中的数据往往以UTF-16或UTF-32形式存储,因为这些格式使用固定或较少变动的字节数表示每个字符,处理起来更高效。 + • 如果仍然是UTF-8编码,那就意味着数据没有被解码,而是保持在原本的传输和存储格式中。 +3. 为什么不直接用UTF-8作为内存表示: + - 可变长度问题:UTF-8是可变长度编码,每个字符可以是1到4个字节不等。在处理字符串时,特别是随机访问某个字符、截取字符串、或计算字符串长度时,可变长度会带来额外的复杂性。相比之下,UTF-16和UTF-32更适合作为内存表示,因为它们提供了更直接的字符访问方式。 + • 性能考虑:内存中直接使用UTF-8会使很多常见操作变得复杂且低效,比如随机访问某个字符时需要遍历字符串计算偏移量,而UTF-16和UTF-32则可以直接根据索引进行访问。 +4. UTF-8作为存储格式的优势: + - 存储效率:UTF-8对ASCII字符(以及很多常见字符)的编码非常高效,能够节省存储空间。这是为什么在存储和传输数据时,UTF-8常被使用的原因。 + • 兼容性:UTF-8的设计使得它与传统的ASCII编码兼容,并且能够支持所有Unicode字符,这使得它在文件传输和跨平台操作中非常有优势。 +5. 解码的必要性: + - 当文件内容以UTF-8形式存储时,它可以方便地跨平台、跨应用传输和存储。然而,为了高效处理和操作这些数据,通常需要将其解码为更适合在内存中操作的格式(如UTF-16或UTF-32)。 + • 如果你保持UTF-8不解码,那么在实际操作数据时,仍然需要处理它的可变长度特性,这将会增加操作复杂性和计算成本。 +6. 总结: + - 作为一种编码方式,UTF-8非常适合用于数据存储和传输,因为它高效且兼容性强。然而,在程序实际处理数据时,通常会将其解码为一种更易于操作的内存格式,如UTF-16或UTF-32,这样可以提高操作效率和简化实现。 + • 因此,即使计算机默认编码为UTF-8,文件中的二进制数据仍然需要解码成更适合处理的形式,以便进行更复杂的操作。保持UTF-8编码不解码虽然理论上可以减少步骤,但会带来处理上的复杂性,不利于程序的高效运行。 + + + +1. Unicode编码格式: + - Unicode字符可以通过多种编码格式进行存储和传输,常见的包括: + • UTF-8:一种可变长度编码方式,每个字符使用1到4个字节表示。ASCII字符用1个字节,其他字符用2到4个字节。 + • UTF-16:也是一种可变长度编码,每个字符使用2或4个字节表示。基本多文种平面(BMP)字符使用2个字节,增补字符使用4个字节。 + • UTF-32:一种固定长度编码,每个字符使用4个字节表示,无论是什么字符。 + + + +编辑器打开文件时使用的编码通常取决于以下几个因素: + +1. **文件的 BOM (Byte Order Mark)**: 一些文件在开头包含一个 BOM,它是一种特殊的字节序列,用于标识文件的编码格式。如果文件有 BOM,编辑器通常会根据它来识别并使用相应的编码格式(如 UTF-8、UTF-16)。 +2. **文件的内容**: 某些编辑器会根据文件内容尝试自动检测编码。例如,如果文件的内容包含常见的 ASCII 字符且没有特殊的非 ASCII 字符,编辑器可能会认为这是一个使用 UTF-8 或 ISO-8859-1 等常见编码的文件。 +3. **编辑器的默认设置**: 如果文件没有 BOM,且编辑器无法通过内容明确识别编码,则编辑器会使用默认的编码设置来打开文件。这个默认设置通常可以在编辑器的配置或设置选项中进行修改。例如,许多编辑器的默认编码是 UTF-8。 +4. **文件扩展名或类型**: 一些编辑器可能会根据文件的扩展名或类型来决定使用哪种编码。例如,某些特定类型的文件(如 HTML、XML)可能默认使用 UTF-8 编码。 +5. **用户指定**: 在某些情况下,用户可以在打开文件时手动指定编码,或者在编辑器的设置中为特定类型的文件指定特定的编码。 + +综上所述,当点开一个文件时,编辑器通常会按以下顺序决定使用哪种编码:BOM > 内容检测 > 默认设置 > 文件扩展名 > 用户指定的编码。 + + + +要查看文件是否有 BOM (Byte Order Mark) 标识,可以通过以下几种方法: + +### 1. **使用十六进制编辑器** + +- 打开文件时,可以使用十六进制编辑器(如 HxD、Hex Fiend、UltraEdit 等)查看文件的开头字节。 +- 不同编码的 BOM 标识如下: + - **UTF-8**: `EF BB BF` + - **UTF-16 (LE)**: `FF FE` + - **UTF-16 (BE)**: `FE FF` + - **UTF-32 (LE)**: `FF FE 00 00` + - **UTF-32 (BE)**: `00 00 FE FF` + +如果文件以这些字节序列开头,则表示文件有相应的 BOM 标识。 \ No newline at end of file diff --git a/posts/blog/计算基础/计算机网络/计算机网络笔记.md b/posts/blog/计算基础/计算机网络/计算机网络笔记.md new file mode 100644 index 0000000..8fb41ce --- /dev/null +++ b/posts/blog/计算基础/计算机网络/计算机网络笔记.md @@ -0,0 +1,130 @@ +--- +title: 计算机网络笔记 +date: 2024-09-26 +tags: [计算机网络] +--- + + +## 层次结构 + +理论上7层(OSI模型)、实际4次(tcp/ip模型),方便学习5层(网络模型) + +- 应用层:处理应用层协议 + + 各种应用协议:http、ftp、TLS/SSL + + 调用系统的传输层接口 + + - **打开和关闭TCP连接** + - **读取和写入TCP流数据** + - **设置TCP参数**(如超时时间、Nagle算法开关、缓冲区大小等,通过Socket选项) + - **实现应用层协议**(HTTP、WebSocket等)在TCP上的封装和解析 +- 传输层:决定通信方式(可靠、还是不可靠) + + 传输协议:tcp、udp + + > [!important] 传输层的2位可以理解为2家物流公司 + + tcp:协议会有握手过程、数据拆分重组、可靠传输;将完整数据才会交给上层处理 + + udp:则简单粗暴,直接传输完整信息(所以长度有限) + + - 应用层决定传输协议、目标端口 + - 源端口:随机端口 +- 网络层:决定下一个网络 + + IP地址,相关协议:ICMP,测试IP连通性 + + - 目的ip + - 源ip:本地ip +- 数据链路层:决定下一跳设备 + + Mac地址,相关协议:ARP协议(获取本地ip的Mac地址),它会发送一个广播帧(目的MAC地址是 `FF:FF:FF:FF:FF:FF`,即所有主机都能收到),这样局域网的设备就能交接Mac地址了 + + - 同一网段则通过ARP协议获取ip(或缓存),非同一网段(外网请求)则填写网关Mac地址 + - 源mac:本地Mac +- 物理层 + + 将数据包转为电信号、光信号 + + + + +主机(操作系统)通过网络模型构建一个数据包,再进行物理传播,可以是2层的ARP协议请求、3层的ICMP协议请求、4层的TCP握手请求、5层的应用协议 + +常见设备可以处理的层级 + +交换机(连接设备):2层 + +路由器(隔离网络):3层 + +计算机:5层 + + + +## TCP协议 + +- 使用TCP协议,须**先建立TCP连接**,形成传输数据通道,似于拨打电话 +- 传输前,采用“**三次握手**”方式,属于==点对点通信==,是面向连接的,效率低 +- 仅支持单播传输,每条TCP传输连接只能有两个端点(客户端、服务端) +- 两个端点的数据传输,采用的是“字节流”来传输,属于可靠的数据传输 +- 传输完毕,需释放已建立的连接,开销大,速度慢,适用于文件传输、邮件等 + +### TCP协议的三次握手(通道建立) + +TCP(传输控制协议)是一种面向连接的、可靠的传输层协议。它使用三次握手来建立连接,以确保数据在两个设备之间可靠地传输。 + +**三次握手的过程如下:** + +1. 客户端发送 SYN(同步)数据包。这个数据包包含客户端的初始序列号(ISN)。 +2. 服务器收到 SYN 数据包后,发送 SYN-ACK(同步确认)数据包。这个数据包包含服务器的初始序列号(ISN)和对客户端 ISN 的确认号(ACK)。 +3. 客户端收到 SYN-ACK 数据包后,发送 ACK(确认)数据包。这个数据包包含对服务器 ISN 的确认号(ACK)。 + +三次握手完成后,客户端和服务器就可以开始交换数据了。 + +**三次握手的意义:** + +三次握手可以确保数据在两个设备之间可靠地传输。它可以防止以下情况的发生: + +不会丢失:如果没有三次握手,客户端和服务器可能会同时发送数据,导致数据丢失。 + +不会重复:如果没有三次握手,客户端和服务器可能会重复发送数据,导致数据重复。 + +不会乱序:如果没有三次握手,客户端和服务器可能会乱序发送数据,导致数据乱序。 + +![image 5.png](计算机网络笔记/image5.png) + +### TCP协议的四次挥手(通道关闭) + +使用四次挥手来关闭连接,以确保数据在两个设备之间可靠地传输。 + +**四次挥手的过程如下:** + +1. 客户端发送 FIN(结束)数据包。这个数据包表示客户端已经完成数据传输,并希望关闭连接。 +2. 服务器收到 FIN 数据包后,发送 ACK(确认)数据包。这个数据包表示服务器已经收到客户端的 FIN 数据包,并同意关闭连接。 +3. 服务器发送 FIN 数据包。这个数据包表示服务器已经完成数据传输,并希望关闭连接。 +4. 客户端收到 FIN 数据包后,发送 ACK(确认)数据包。这个数据包表示客户端已经收到服务器的 FIN 数据包,并同意关闭连接。四次挥手完成后,客户端和服务器之间的连接就关闭了。 + +**四次挥手的意义** + +四次挥手可以确保数据在两个设备之间可靠地传输。它可以防止以下情况的发生: + +如果没有四次挥手,客户端和服务器可能会同时关闭连接,导致数据丢失。 + +如果没有四次挥手,客户端和服务器可能会重复发送数据,导致数据重复。 + +如果没有四次挥手,客户端和服务器可能会乱序发送数据,导致数据乱序。 + +![image 1 2.png](计算机网络笔记/image12.png) + +## UDP协议 + +- 采用**数据报**(数据、源、目的)的方式来传输,**无需建立连接**,类似于发短信 +- ==每个数据报的大小限制在64K内,超出64k可以分为多个数据报来发送== +- 发送不管对方是否准备好,接收方即使收到也不确认,因此属于**不可靠的** +- 可以**广播发送**,也就是属于一对一、一对多和多对一连接的通信协议 +- 发送数据结束时无需释放资源,开销小,速度快,适用于视频会议、直播等 + +> 对比 + +![image 2 2.png](计算机网络笔记/image22.png) \ No newline at end of file diff --git a/posts/blog/计算基础/计算机网络/计算机网络笔记/image12.png b/posts/blog/计算基础/计算机网络/计算机网络笔记/image12.png new file mode 100644 index 0000000..a8a9cf9 Binary files /dev/null and b/posts/blog/计算基础/计算机网络/计算机网络笔记/image12.png differ diff --git a/posts/blog/计算基础/计算机网络/计算机网络笔记/image22.png b/posts/blog/计算基础/计算机网络/计算机网络笔记/image22.png new file mode 100644 index 0000000..dba94a0 Binary files /dev/null and b/posts/blog/计算基础/计算机网络/计算机网络笔记/image22.png differ diff --git a/posts/blog/计算基础/计算机网络/计算机网络笔记/image5.png b/posts/blog/计算基础/计算机网络/计算机网络笔记/image5.png new file mode 100644 index 0000000..da47536 Binary files /dev/null and b/posts/blog/计算基础/计算机网络/计算机网络笔记/image5.png differ diff --git a/posts/blog/运维与基础设施/NAS/NAS笔记.md b/posts/blog/运维与基础设施/NAS/NAS笔记.md new file mode 100644 index 0000000..544e33e --- /dev/null +++ b/posts/blog/运维与基础设施/NAS/NAS笔记.md @@ -0,0 +1,54 @@ +--- +title: NAS笔记 +date: 2025-07-04 +tags: [NAS] +--- + + +## 群晖NAS + +### iSCSI 是什么? + +**iSCSI 全称:Internet Small Computer System Interface** + +👉 本质上是一个**通过 TCP/IP 网络,把远程硬盘映射成本地硬盘的存储协议** + +用于**挂载硬盘** + +![image 11.png](NAS笔记/image11.png) + +IQN表示唯一标识 + +### LUN 是什么? + +**LUN,全称 Logical Unit Number(逻辑单元号)** + +👉 它是存储系统里,用来标识一块逻辑设备(块设备)的编号 + +说白了: + +- 在 iSCSI / FC / SAN 环境里,一块磁盘、一个虚拟盘、一个 RAID 逻辑卷 + + 👉 都是一个 LUN + + +> [!important] 简单来说,通过iscsi连接,挂载lun磁盘内容 +> +> iSCSI是设备一对一 +> +> iSCSI绑定的LUN(盘)是多对多 + + + +## 📦 📖 群晖/NAS 常见存储池类型简介 + +|类型|数据安全|容量利用率|特点| +|---|---|---|---| +|**Basic**|❌ 无|100%|单盘直通,单盘坏数据丢| +|**JBOD**|❌ 无|100%|多盘拼接成一个大池,坏一块盘丢对应那块数据| +|**RAID0**|❌ 无|100%|多盘并联提速,坏一块盘全毁| +|**RAID1**|✅ 镜像|50%|2块盘互为备份,读性能提升| +|**RAID5**|✅ 容错1块|(N-1)/N|最常用企业级方案,容错1块| +|**SHR**|✅ 容错1或多块|灵活|群晖独家,支持不同容量硬盘,自动优化空间| + +RAID5最多坏一块硬盘 \ No newline at end of file diff --git a/posts/blog/运维与基础设施/NAS/NAS笔记/image11.png b/posts/blog/运维与基础设施/NAS/NAS笔记/image11.png new file mode 100644 index 0000000..9ef87dc Binary files /dev/null and b/posts/blog/运维与基础设施/NAS/NAS笔记/image11.png differ diff --git a/posts/blog/运维与基础设施/nginx/nginx笔记.md b/posts/blog/运维与基础设施/nginx/nginx笔记.md new file mode 100644 index 0000000..7699395 --- /dev/null +++ b/posts/blog/运维与基础设施/nginx/nginx笔记.md @@ -0,0 +1,55 @@ +--- +title: nginx笔记 +date: 2025-07-12 +tags: [nginx] +--- + + +nginx是一个web服务器,专门处理http请求,性能强、功能多 + +## 配置文件 + +大致分为 + +- events{} +- http{} + - server{} + - location{} + +### events + +配置 nginx 事件驱动相关参数(很少改) + +### http + +全部的http配置都在这里,server在里面 + +- 配置日志 +- 文件大小 + +### server + +监听端口,处理请求, + +- 监听端口、域名 +- 配置ssl连接(https) +- 使用location处理请求 + +### location + +按请求url处理请求 + +- 设置访问资源(网页) +- 反向代理,将请求发给Java + + + +有些配置文件,只写了server是因这个配置文件是**包含在主配置中**的,比如: + +```Shell +http { + include /etc/nginx/conf.d/*.conf; +} +``` + +那这个 server 块就自动被放入 http 块下了 \ No newline at end of file diff --git a/posts/blog/运维与基础设施/服务器/服务器笔记.md b/posts/blog/运维与基础设施/服务器/服务器笔记.md new file mode 100644 index 0000000..f6bace6 --- /dev/null +++ b/posts/blog/运维与基础设施/服务器/服务器笔记.md @@ -0,0 +1,884 @@ +--- +title: 服务器笔记 +date: 2024-11-08 +tags: [服务器] +--- + +## 安装Java环境 + +- 登录服务器,通过命令`uname -m`判断服务器架构 + - `x86_64`:表示 x86-64 架构(即 64 位 x86 架构)。 + - `aarch64`:表示 ARM 64 位架构(即 AArch64)。 + - `armv7l`:表示 ARM 32 位架构(即 ARMv7)。 +- 下载对应架构和操作系统的jdk: [Java Archive | Oracle 中国](https://www.oracle.com/cn/java/technologies/downloads/archive/) + - `Linux ARM 64 Compressed Archive` :对应`aarch64` + - `Linux x64 Compressed Archive` :对应`x86_64` +- 上传到服务器、解压 + + ```Shell + # 本地文件上传到服务器 + scp -P 8970 "jdk-17.0.10_linux-aarch64_bin.tar.gz" hbnu@110.40.59.154:/home/hbnu + # 解压 + [sudo] tar -xvf 压缩文件名 + ``` + + - 命令详解 + - `**sudo**`:以超级用户权限运行后续的命令。 + - `**tar**`:调用归档工具 `tar`。 + - `**x**`:解压归档文件。 + - `**v**`:以详细模式显示解压过程中的文件信息。 + - `**f**`:指定要解压的归档文件的名称。 + - `**jdk-8uXXX-linux-x64.tar.gz**`:要解压的归档文件的名称。 +- 修改配置文件,编辑 `~/.bashrc` 或 `/etc/profile` + + ```Shell + export JAVA_HOME=/home/hbnu/app/java/jdk/jdk-17.0.10 + export PATH=${JAVA_HOME}/bin:$PATH + ``` + + - 命令详解 + - 打开文件:`vim 文件名` + - 修改文件:`i` + - 保存退出:esc `: wq!` +- 重新加载环境配置文件,`source /etc/profile` +- 检查是否配置成功Java,`java -version` + + ![image 6.png](服务器笔记/image6.png) + + + + +## 结束Java进程 + +- 查看Java进程 + + ```Shell + ps -ef | grep java + ``` + + - `ps -ef`:列出所有进程的详细信息 + - `grep java`:过滤出包含 java 的行 +- 结果分析,第一个数字表示pid(进程id) + + ```Shell + root 2544 12536 0 11:58 pts/0 00:00:00 java -jar ServerNio.jar + ``` + + - 数据详解 + - **USER**:运行该进程的用户。在这个例子中是 `root`。 + - **PID**:进程 ID(Process ID)。在这个例子中是 `2544`。 + - **PPID**:父进程 ID(Parent Process ID)。在这个例子中是 `12536`。 + - **%CPU**:进程占用的 CPU 百分比。在这个例子中是 `0`。 + - **%MEM**:进程占用的内存百分比。在这个例子中是 `0`。 + - **VSZ**:虚拟内存大小(Virtual Memory Size)。这一列在这个输出中没有显示。 + - **RSS**:常驻内存大小(Resident Set Size)。这一列在这个输出中没有显示。 + - **TTY**:终端设备。在这个例子中是 `pts/0`。 + - **STAT**:进程状态。这一列在这个输出中没有显示。 + - **START**:进程启动时间。在这个例子中是 `11:58`。 + - **TIME**:进程运行的总时间。在这个例子中是 `00:00:00`。 + - **COMMAND**:启动进程的命令。在这个例子中是 `java -jar ServerNio.jar`。 +- 结束进程 + + ```Shell + kill -9 pid + ``` + + + + + + +## 域名 + +购买域名 + +配置dns即可 + + + + + +## https + +不分离 + +- 后端配置ssl证书就行了 + +前端 + +在nginx中配置ssl证书,这样前端就可以使用https了 + +- 自签证书可以用,但依然会包不安全 +- 专业的ssl需要绑定域名 + +后端 + +- 前端依然可以使用http与后端联系 +- 配置nginx将http代理成https请求,后端配置ssl文件即可接收 + + + +简单总结就是:只要 web 服务器配置了 + +SSL 证书,就可以接受 HTTPS 请求。 + +- SSL 证书可以通过证书颁发机构(如 Let'sEncrypt、商业证书提供商等)获得,配置到Web 服务器上。 +- 配置好 SSL 证书后,Web 服务器会监听 443 + 端口,接受 HTTPS 请求,确保数据传输加密并安全。 + + + + + +代理和vpn差不多 + + + + + + + +当一个 Web 服务器配置了 SSL 证书后,它能够接受 HTTPS 请求的原因在于 SSL/TLS 协议的工作机制以及证书在其中扮演的角色。让我一步步解释: + +**1. HTTPS 的本质** + +HTTPS(HyperText Transfer Protocol Secure)是基于 HTTP 的安全版本,它通过 SSL/TLS 协议对数据进行加密,确保客户端(比如浏览器)和服务器之间的通信是安全的。SSL(Secure Sockets Layer)及其继任者 TLS(Transport Layer Security)提供了加密、数据完整性和身份验证的功能。 + +**2. SSL 证书的作用** + +SSL 证书是一个数字证书,由受信任的证书颁发机构(CA,Certificate Authority)签发,包含了服务器的公钥和身份信息(如域名)。它的主要作用是: + +- **身份验证**:证明服务器是合法的,防止中间人冒充。 +- **加密通信**:提供公钥,用于协商加密密钥。 + +当服务器安装了 SSL 证书后,它就具备了参与 SSL/TLS 握手的条件。 + +**3. SSL/TLS 握手过程** + +客户端发起 HTTPS 请求时,会触发 SSL/TLS 握手,过程大致如下: + +1. **客户端问候(Client Hello)**:客户端发送支持的加密协议版本和加密算法。 +2. **服务器响应(Server Hello)**:服务器返回选定的协议版本、加密算法,并附上 SSL 证书。 +3. **证书验证**:客户端验证证书的有效性(是否由可信 CA 签发、是否过期、域名是否匹配)。 +4. **密钥交换**:客户端和服务器利用证书中的公钥协商一个对称加密密钥(通常通过 Diffie-Hellman 或 RSA 完成)。 +5. **加密通信建立**:握手完成后,双方使用协商出的密钥加密后续的数据传输。 + +服务器配置了 SSL 证书后,就能完成上述握手过程,从而接受并处理 HTTPS 请求。 + +**4. 为什么需要 SSL 证书** + +- **没有证书无法加密**:如果服务器没有 SSL 证书,就无法提供公钥,也无法完成密钥交换,加密通信无从谈起。 +- **客户端信任问题**:现代浏览器要求服务器提供有效证书,否则会阻止连接并显示安全警告(如“连接不安全”)。 +- **协议要求**:HTTPS 的定义就是 HTTP + SSL/TLS,没有证书就无法实现。 + +**5. 配置后的效果** + +一旦服务器安装了 SSL 证书并正确配置(如绑定到端口 443,HTTPS 的默认端口),它就具备了以下能力: + +- 响应 HTTPS 请求(即 https:// 开头的 URL)。 +- 与客户端建立安全的加密通道。 +- 通过证书证明自己的身份。 + +**总结** + +Web 服务器配置 SSL 证书后可以接受 HTTPS 请求,是因为证书提供了身份验证和密钥交换的基础,使服务器能够与客户端完成 SSL/TLS 握手,建立加密连接。没有证书,HTTPS 的安全机制无法实现,通信只能退回到不安全的 HTTP。 + +如果你有更具体的技术问题(比如证书安装步骤或配置细节),可以进一步告诉我,我会详细解答! + + + +建立连接 + +- ssl/tls握手 + +- 验证证书 + - 通过,安全 + - 不通过,不安全 + + + +发起请求 + +响应 + + + +## 服务器架构 + +- amd 和 arm + + 让我用简单的方式来解释这几个术语的区别: + + AMD64: + + - 也叫x86_64,是由AMD公司开发的64位架构 + - 目前大多数台式机和笔记本电脑都使用这种架构 + - Intel和AMD生产的处理器都支持这种架构 + - 可以看作是个人电脑最常见的CPU类型 + + ARM64: + + - 一种低功耗的64位处理器架构 + - 主要用在手机、平板等移动设备上 + - 苹果M系列芯片、高通骁龙都是ARM架构 + - 特点是省电,但性能也在不断提升 + - 近年来也开始用于笔记本电脑,如苹果MacBook + + windowsamd64: + + - 这不是架构名称,而是指Windows操作系统的AMD64版本 + - 简单说就是运行在AMD64(x86_64)架构上的Windows系统 + - 是目前最常见的Windows版本 + + 简单总结: + + - AMD64主要用于传统PC + - ARM64主要用于移动设备 + - windowsamd64是指AMD64架构上运行的Windows系统 + + 需要我对某个方面详细解释吗? + +- 64 和 32 + + 让我用简单的例子来解释64位和32位的区别: + + 内存寻址能力: + + - 32位就像一个邮递员只能记住32个数字的地址 + - 最多能访问4GB内存(2³²字节) + - 超过4GB的内存空间就访问不到了 + - 64位就像邮递员能记住64个数字的地址 + - 理论上可以支持高达16EB(16万TB)的内存 + - 目前实际应用远没达到这个上限 + + 处理数据能力: + + - 32位像是一条单车道 + - 一次只能处理32位(4字节)的数据 + - 64位像是一条双车道 + - 一次可以处理64位(8字节)的数据 + - 处理大量数据时效率更高 + + 软件兼容性: + + - 64位系统通常可以运行32位程序 + - 向下兼容,就像宽马路也能跑小汽车 + - 32位系统不能运行64位程序 + - 无法向上兼容,就像小路跑不了大卡车 + + 实际应用建议: + + - 如果内存超过4GB,必须用64位系统 + - 现代电脑建议使用64位系统和软件 + - 部分老旧软件可能只有32位版本 + + 需要我进一步解释某个方面吗? + + + + + + + + +## Ubuntu服务器使用 + +### 用户和用户组 + +- 切换root用户 + + ```Shell + sudo -s + ``` + +- 创建用户 + + ```Shell + sudo useradd 用户名 + ``` + +- 设置密码 + + ```Shell + sudo passwd 用户名 + ``` + + + +`sudo usermod -aG sudo qiu` + +每个用户组相当于缺陷,用户没有权限,加入对应用户组即可;比如没有sudo权限,需要有sudo权限的用户将你拉入该用户组 + + + + + +### ssh端口修改 + +- 修改配置文件 + + ```Shell + sudo nano /etc/ssh/sshd_config + ``` + +- 防火墙放行端口 + + ```Shell + -- 下载ufw + sudo apt update + sudo apt install ufw -y + sudo ufw enable + -- 放行 + sudo ufw allow 8964/tcp + ``` + +- 重启服务ssh服务 + + ```Shell + sudo systemctl restart ssh + ``` + + + + +## docker使用 + +### 安装Docker + +> [!info] Ubuntu +> Jumpstart your client-side server applications with Docker Engine on Ubuntu. +> [https://docs.docker.com/engine/install/ubuntu/](https://docs.docker.com/engine/install/ubuntu/) + +> [!important] 安装完,给用户添加docker组(退出重新进入一下) `sudo usermod -aG docker ubuntu` + +### 镜像操作 + +- 查找镜像:`docker search nginx` + - 指定版本,到docker hub + + > [!info] Docker Hub Container Image Library | App Containerization + > Welcome to the world's largest container registry built for developers and open source contributors to find, use, and share their container images. + > [https://hub.docker.com/](https://hub.docker.com/) + +- 下载镜像:`docker pull nginx` +- 查看镜像:`docker images` +- 删除镜像:`docker rmi nginx` + +### 容器操作 + +- **运行容器:**`**docker run**` + - 后台启动:docker run -d nginx + - **端口映射 -p 外部端口:内部端口:docker run -d -p 80:80 nginx** + - 通过挂载启动:-v /path/to/nginx.conf:/etc/nginx/nginx.conf + +- **进入容器:**`**docker exec**` + - 进入容器进行基本配置、程序部署:docker exec -it 名称 bash/sh +- 查看容器:docker ps +- 停止容器:docker stop +- 启动容器:docker start +- 重启容器:docker restart +- 状态容器:docker stats +- 容器日志:docker logs +- 删除容器:docker rm + +### 保存镜像、分享社区 + +- 提交:`docker commit` -m “消息” nginx myngix:v1.0 +- 保存:`docker save` +- 加载:`docker load`;作用是解压别人的镜像 +- 登录:`docker login` +- 命名:`docker tag` +- 推送:`docker push` + + + +### 目录挂载 + +- 进入容器修改配置不方便,并且数据存储在容器中不安全(**容器销毁、数据丢失**),将配置弄在本地,并挂载到容器中 +- 通过挂载启动:-v /path/to/nginx.conf:/etc/nginx/nginx.conf +- 文件夹挂载文件夹也可以 + +### 卷映射 + +直接将内部配置文件,映射到外部,默认地址为:/var/lib/docker/volumes,这样方便修改 + +![image 1 3.png](服务器笔记/image13.png) + +这样方便读个容器使用一个卷 + +### 网络问题 + +创建网络,容器名为域名,方便容器之间访问 + +![image 2 3.png](服务器笔记/image23.png) + +创建网络 + +![image 3 2.png](服务器笔记/image32.png) + +容器加入网络 + +![image 4 2.png](服务器笔记/image42.png) + + + +-netword 网络 + +-p 端口 + +-v 存储 + +-e 环境 + +![image 5 2.png](服务器笔记/image52.png) + + + +### compose + +批量启动容器 + + + +### 部署项目 + +创建网络 + +```Shell +docker network create myNet +``` + +后端 + +```Shell +docker run -d \ + --name java \ + --network myNet \ + -p 8080:8080 \ + -v /app/java/app.jar:/app/app.jar \ + -v /app/java/application.yml:/app/application.yml \ + -v /app/file:/app/file \ + openjdk:17 \ + java -jar /app/app.jar --spring.config.location=/app/application.yml +``` + +前端 + +```Shell +docker run -d \ + --name vue \ + --network myNet \ + -p 80:80 \ + -v /app/vue/dist:/usr/share/nginx/html \ + -v /app/nginx/nginx.conf:/etc/nginx/conf.d/default.conf \ + nginx +``` + +nginx + +```Shell +server { + listen 80; + server_name qodes.top; + + client_max_body_size 100M; + + root /usr/share/nginx/html; + index index.html; + + # 访问前端页面 + location / { + try_files $uri $uri/ /index.html; + } + + # 反向代理 /api 开头的请求给后端容器(容器名 backend) + location /api/ { + proxy_pass http://java:8080/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +``` + + + +- 测试部署 + + 创建网络 + + ```Shell + docker network create smtNet + ``` + + 后端 + + ```Shell + docker run -d \ + --name smtj \ + --network smtNet \ + -p 8880:8888 \ + -v /aaa/java/Official.jar:/app/Official.jar \ + -v /aaa/java/yaml:/yaml \ + openjdk:17 \ + java -jar /app/Official.jar --spring.config.location=/yaml/application.yml + ``` + + 前端 + + ```Shell + docker run -d \ + --name smtv \ + --network smtNet \ + -p 8888:80 \ + -v /aaa/vue/dist:/usr/share/nginx/html \ + -v /aaa/nginx/nginx.conf:/etc/nginx/conf.d/default.conf \ + nginx + ``` + + nginx + + ```Shell + server { + listen 80; + client_max_body_size 100M; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://smtj:8080/api/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + } + ``` + + + + +### 配置免费的https + +**获取免费证书** + +方法一:问题是续签需要重启服务 + +```Shell +docker run --rm -it \ + -p 80:80 \ + -v /app/ssl:/acme.sh \ + neilpang/acme.sh \ + --issue -d qodes.top --standalone --keylength ec-256 --server letsencrypt +``` + +- `v /app/ssl:/acme.sh`:证书文件会保存到宿主机的 `/app/ssl` +- `-standalone`:自动开临时 web 服务验证域名(注意 80 端口不能被占用) +- `d qodes.top`:你的域名 + +**配置nginx** + +```Shell +events {} + +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + + server { + listen 80; + server_name qodes.top; + + # 自动跳转到 HTTPS + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl; + server_name qodes.top; + + ssl_certificate /etc/nginx/cert/fullchain.cer; + ssl_certificate_key /etc/nginx/cert/qodes.top.key; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://java:8080/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + } +} +``` + +**启动nginx容器** + +```Shell +docker run -d \ + --name vue \ + --network myNet \ + -p 80:80 \ + -p 443:443 \ + -v /app/vue/dist:/usr/share/nginx/html \ + -v /app/nginx/nginx.conf:/etc/nginx/nginx.conf \ + -v /app/ssl/qodes.top_ecc:/etc/nginx/cert \ + nginx +``` + +**定期更新证书** + +手动: + +```Shell +docker run --rm \ + -v /app/ssl:/acme.sh \ + neilpang/acme.sh \ + --renew -d qodes.top --ecc --server letsencrypt +``` + +自动(**此方法无效,主要原因要临时关闭80端口**): + +打开系统的定时任务配置:`crontab -e` + +选1,使用nano编辑器,底部添加命令,ctrl+O,回车,退出 + +```Shell +0 3 1 */2 * docker run --rm -v /app/ssl:/acme.sh neilpang/acme.sh --renew -d qodes.top --ecc --server letsencrypt && docker restart vue +``` + +续签待测试 + +📌 ✅ 解决方案:**方法 1:续签前停止 nginx 再续签** + +```Shell +0 3 1 */2 * docker stop vue && \ +docker run --rm -p 80:80 -v /app/ssl:/acme.sh neilpang/acme.sh --renew -d qodes.top --ecc --server letsencrypt && \ +docker start vue +``` + +方法二:自带续签,不停机 + +- 将验证资料配置到nginx中 + +```Shell +docker run --rm + -v /app/ssl:/acme.sh + neilpang/acme.sh \ + --issue -d test.qodes.top --webroot /acme.sh/test-webroot + --keylength ec-256 --server letsencrypt +``` + + + +### 通过代理,免备案 + +- **核心检查:host:域名(是否备案)** + + 所以直接将域名解析到服务器是一定行不通的,即使使用CDN服务,也无法改变host为域名的事实 + + 解决方案: + + 将域名解析到其他服务器(已备案、国外服务器),其他服务器代替你访问,并将host设置为ip,这样国内服务器会以为是ip访问,所以不会有影响 + + - host: 不要暴露未备案的域名,所以使用ip + + ```Plain + events { } + http { + server { + listen 8080; + location /{ + proxy_pass http://117.72.127.170:80; + proxy_set_header Host 117.72.127.170; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + } + } + ``` + + + > [!important] 代理服务器随便使用什么协议,http、https都可以 + + + + + + +### 日志(时区)配置 + +查看控制台日志:`docker logs java` + +nginx配置日志 + +```Shell +events {} + +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + + # 日志格式:含请求耗时、后端响应耗时 + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$request_time" "$upstream_response_time"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log warn; + + # 上传限制:最大 500MB,缓存 10MB + client_max_body_size 500m; + client_body_buffer_size 10m; # 这里你漏了分号 + + server { + listen 80; + server_name qodes.top; + + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl; + server_name qodes.top; + + ssl_certificate /etc/nginx/cert/fullchain.cer; + ssl_certificate_key /etc/nginx/cert/qodes.top.key; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://java:8080/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + } +} +``` + +docker + +```Shell +docker run -d --name vue \ + --network myNet \ + -p 80:80 -p 443:443 \ + -v /app/vue/dist:/usr/share/nginx/html \ + -v /app/nginx/nginx.conf:/etc/nginx/nginx.conf \ + -v /app/ssl/qodes.top_ecc:/etc/nginx/cert \ + -v /app/nginx/log:/var/log/nginx \ + nginx + + docker run -d --name vue \ + --network myNet \ + -p 80:80 -p 443:443 \ + -v /app/vue/dist:/usr/share/nginx/html \ + -v /app/nginx/nginx.conf:/etc/nginx/nginx.conf \ + -v /app/ssl/qodes.top_ecc:/etc/nginx/cert \ + -v /app/nginx/log:/var/log/nginx \ + -v /etc/localtime:/etc/localtime:ro \ + nginx +``` + +| | | +|---|---| +|上传需求|推荐设置| +|偶尔大文件上传|`client_body_buffer_size 10M`| +|高频大文件上传|保持低 buffer,默认 `256k~512k`,让它写临时文件| +|禁止上传超大文件|设置 `client_max_body_size 100M`,限制大小| + +java多环境配置docker部署、时区 + +```Shell +docker run -d \ + -p 8080:8080 \ + --name java \ + --network myNet \ + -v /etc/localtime:/etc/localtime:ro \ + -v /app/file:/app/file \ + -v /app/java/yaml:/yaml \ + -v /app/java/log:/app/java/log \ + -v /app/java/app.jar:/app/java/app.jar \ + openjdk:17 \ + java -Duser.timezone=Asia/Shanghai -Dspring.profiles.active=prod -Dspring.config.location=/yaml/ -jar /app/java/app.jar +``` + + + +1个tcp连接 可能是多个请求 + +```Shell +events {} + +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + + # 日志格式:含请求耗时、后端响应耗时 + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$request_time" "$upstream_response_time"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log warn; + + # 上传限制:最大 500MB,缓存 10MB + client_max_body_size 500m; + client_body_buffer_size 10m; # 这里你漏了分号 + + server { + listen 80; + server_name qodes.top; + + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl; + server_name qodes.top; + + ssl_certificate /etc/nginx/cert/fullchain.cer; + ssl_certificate_key /etc/nginx/cert/qodes.top.key; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://java:8080/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + } +} +``` \ No newline at end of file diff --git a/posts/blog/运维与基础设施/服务器/服务器笔记/image13.png b/posts/blog/运维与基础设施/服务器/服务器笔记/image13.png new file mode 100644 index 0000000..a36728b Binary files /dev/null and b/posts/blog/运维与基础设施/服务器/服务器笔记/image13.png differ diff --git a/posts/blog/运维与基础设施/服务器/服务器笔记/image23.png b/posts/blog/运维与基础设施/服务器/服务器笔记/image23.png new file mode 100644 index 0000000..30a08fe Binary files /dev/null and b/posts/blog/运维与基础设施/服务器/服务器笔记/image23.png differ diff --git a/posts/blog/运维与基础设施/服务器/服务器笔记/image32.png b/posts/blog/运维与基础设施/服务器/服务器笔记/image32.png new file mode 100644 index 0000000..c0ec1ce Binary files /dev/null and b/posts/blog/运维与基础设施/服务器/服务器笔记/image32.png differ diff --git a/posts/blog/运维与基础设施/服务器/服务器笔记/image42.png b/posts/blog/运维与基础设施/服务器/服务器笔记/image42.png new file mode 100644 index 0000000..20f6155 Binary files /dev/null and b/posts/blog/运维与基础设施/服务器/服务器笔记/image42.png differ diff --git a/posts/blog/运维与基础设施/服务器/服务器笔记/image52.png b/posts/blog/运维与基础设施/服务器/服务器笔记/image52.png new file mode 100644 index 0000000..d4256d0 Binary files /dev/null and b/posts/blog/运维与基础设施/服务器/服务器笔记/image52.png differ diff --git a/posts/blog/运维与基础设施/服务器/服务器笔记/image6.png b/posts/blog/运维与基础设施/服务器/服务器笔记/image6.png new file mode 100644 index 0000000..59018f2 Binary files /dev/null and b/posts/blog/运维与基础设施/服务器/服务器笔记/image6.png differ diff --git a/posts/主页.md b/posts/主页.md new file mode 100644 index 0000000..98ca2ce --- /dev/null +++ b/posts/主页.md @@ -0,0 +1,32 @@ +--- +title: "主页" +date: "2026-05-24" +--- + + + +My Blog; + +# 你好 + +## 你好 + +My Blog; + +![image-20260528142227157](主页/image-20260528142227157.png) + +的法大师傅 + + + +打发士大夫 + + + +幅度萨芬 + + + + + +幅度萨芬 diff --git a/posts/主页/image-20260528142227157.png b/posts/主页/image-20260528142227157.png new file mode 100644 index 0000000..350ef1b Binary files /dev/null and b/posts/主页/image-20260528142227157.png differ diff --git a/posts/前端开发/react18-concurrent.md b/posts/前端开发/react18-concurrent.md new file mode 100644 index 0000000..58c91cf --- /dev/null +++ b/posts/前端开发/react18-concurrent.md @@ -0,0 +1,93 @@ +--- +title: "React 18 并发特性深度解析" +date: "2026-05-22" +category: "前端开发" +tags: ["React", "JavaScript", "并发"] +excerpt: "Suspense、Transitions、Server Components 全解读,理解 React 18 的核心变革。" +--- + +## 一、并发渲染基础 + +### 1.1 什么是并发渲染 + +React 18 最核心的变化就是并发渲染。简单来说,React 可以"暂停"正在进行的渲染工作,处理更高优先级的更新,然后再回来继续之前的工作。 + +这跟传统的同步渲染完全不同: + +- **同步渲染**:一旦开始就必须完成,无法中断 +- **并发渲染**:可以暂停、恢复、甚至丢弃正在进行的渲染 + +### 1.2 createRoot 与 StrictMode + +```jsx +import { createRoot } from 'react-dom/client'; + +const root = createRoot(document.getElementById('root')); +root.render(); +``` + +## 二、Transitions + +### 2.1 useTransition + +将某些状态更新标记为"非紧急": + +```jsx +function SearchPage() { + const [query, setQuery] = useState(''); + const [isPending, startTransition] = useTransition(); + + function handleChange(e) { + // 紧急更新:输入框立即响应 + setQuery(e.target.value); + + // 非紧急更新:搜索结果可以延迟 + startTransition(() => { + setSearchResults(filterResults(e.target.value)); + }); + } + + return ( + <> + + {isPending ? : } + + ); +} +``` + +### 2.2 useDeferredValue + +```jsx +const deferredQuery = useDeferredValue(query); +const results = useMemo(() => filterResults(deferredQuery), [deferredQuery]); +``` + +## 三、Suspense 改进 + +### 3.1 数据获取 + +```jsx +function ProfilePage() { + return ( + }> + + }> + + + + ); +} +``` + +## 四、Server Components + +React Server Components 让组件在服务端渲染,零 JS 体积发送到客户端: + +- 可以直接访问数据库和文件系统 +- 不会增加客户端 JS 打包体积 +- 与 Client Components 无缝集成 + +## 五、总结 + +React 18 的并发特性让 UI 响应更加流畅。核心思路是"区分优先级,可中断渲染"。 diff --git a/posts/后端开发/JUC/atomic-classes.md b/posts/后端开发/JUC/atomic-classes.md new file mode 100644 index 0000000..9c81677 --- /dev/null +++ b/posts/后端开发/JUC/atomic-classes.md @@ -0,0 +1,28 @@ +--- +title: "JUC 原子类详解" +date: "2026-05-28" +category: "后端开发" +tags: ["Java", "JUC", "Atomic"] +excerpt: "深入理解 Java 原子类的实现原理与使用场景。" +--- + +## Atomic 原子类概述 + +Java 的 `java.util.concurrent.atomic` 包提供了一系列原子类,基于 CAS 实现无锁并发。 + +### 基本类型 + +- `AtomicInteger` — 原子整数 +- `AtomicLong` — 原子长整数 +- `AtomicBoolean` — 原子布尔值 + +```java +AtomicInteger count = new AtomicInteger(0); +count.incrementAndGet(); // 原子自增 +``` + +### 数组类型 + +- `AtomicIntegerArray` +- `AtomicLongArray` +- `AtomicReferenceArray` diff --git a/posts/后端开发/java-concurrency-guide.md b/posts/后端开发/java-concurrency-guide.md new file mode 100644 index 0000000..3533587 --- /dev/null +++ b/posts/后端开发/java-concurrency-guide.md @@ -0,0 +1,271 @@ +--- +title: "Java 并发编程完整指南:从线程基础到 JUC 进阶" +date: "2026-05-26" +category: "后端开发" +tags: ["Java", "并发编程", "JUC"] +excerpt: "全面梳理 Java 并发编程知识体系,从线程基础、synchronized 原理到 JUC 并发工具类。" +--- + +全面梳理 Java 并发编程知识体系,从线程基础、`synchronized` 原理到 JUC 并发工具类,再到线程池最佳实践和虚拟线程。本文配合大量图解和代码示例,帮助你构建完整的并发编程知识框架。 + +**💡 阅读提示:** 本文适合有一定 Java 基础的开发者。如果你是初学者,建议先阅读 Java 基础语法部分。 + +## 一、线程基础知识 + +### 1.1 线程的生命周期 + +Java 线程在它的生命周期中会经历六种状态,理解这些状态的流转是掌握并发编程的第一步: + +- **NEW** — 线程刚创建,尚未调用 `start()` +- **RUNNABLE** — 线程在 JVM 中运行(包括等待 CPU 时间片) +- **BLOCKED** — 线程被阻塞,等待获取锁 +- **WAITING** — 无限期等待,需要其他线程显式唤醒 +- **TIMED_WAITING** — 超时等待,指定时间后自动返回 +- **TERMINATED** — 线程已执行完毕 + +> 重要区别:BLOCKED 是在等待进入 synchronized 块时发生的,而 WAITING 是通过 Object.wait()、Thread.join() 等方法进入的。 + +### 1.2 线程的创建方式 + +Java 中创建线程主要有四种方式,每种都有其适用场景: + +#### 继承 Thread 类 + +```java +class MyThread extends Thread { + @Override + public void run() { + System.out.println("线程运行中: " + Thread.currentThread().getName()); + } +} + +MyThread thread = new MyThread(); +thread.start(); // 启动线程 +``` + +简单直接,但 Java 不支持多继承,灵活性受限。 + +#### 实现 Runnable 接口 + +```java +class MyRunnable implements Runnable { + @Override + public void run() { + System.out.println("线程运行中: " + Thread.currentThread().getName()); + } +} + +Thread thread = new Thread(new MyRunnable()); +thread.start(); +``` + +更推荐的方式,任务与线程分离,便于复用。 + +#### Callable 与 Future + +```java +class MyCallable implements Callable { + @Override + public String call() throws Exception { + Thread.sleep(1000); + return "任务完成"; + } +} + +ExecutorService executor = Executors.newSingleThreadExecutor(); +Future future = executor.submit(new MyCallable()); +String result = future.get(); // 阻塞获取结果 +executor.shutdown(); +``` + +区别于 Runnable,Callable 可以有返回值并且可以抛出异常。配合 Future 可以获取异步执行结果。 + +## 二、synchronized 原理 + +### 2.1 对象头与 Monitor + +Java 中的每个对象都与一个 Monitor 关联。`synchronized` 关键字的底层实现依赖于对象头中的 Mark Word 和 Monitor 机制: + +- **Mark Word**:存储对象的 HashCode、GC 分代年龄、锁状态标志等信息 +- **Monitor**:一种同步机制,包含 EntryList、WaitSet、Owner 三个关键部分 + +```java +// synchronized 用法示例 +public class Counter { + private int count = 0; + + // 1. 同步方法 + public synchronized void increment() { + count++; + } + + // 2. 同步代码块(更细粒度) + public void incrementWithBlock() { + synchronized (this) { + count++; + } + } + + // 3. 静态同步方法(锁的是 Class 对象) + public static synchronized void staticMethod() { + // ... + } +} +``` + +### 2.2 锁升级过程 + +JDK 1.6 之后,synchronized 经历了重大优化。锁不再直接膨胀为重量级锁,而是有一个逐步升级的过程: + +| 锁状态 | 适用场景 | 实现原理 | +|--------|----------|----------| +| 偏向锁 | 只有一个线程访问同步块 | Mark Word 记录线程 ID,下次同一线程进入无需 CAS | +| 轻量级锁 | 多线程交替执行 | CAS 操作 + 自旋,避免线程阻塞 | +| 重量级锁 | 多线程竞争激烈 | 操作系统 Mutex Lock,线程阻塞 | + +#### 偏向锁 + +偏向锁的"偏"字表示偏向于第一个获取它的线程。当锁处于偏向状态时,Mark Word 会记录当前线程 ID。该线程再次进入同步块时,无需任何同步操作。 + +```java +// 查看偏向锁延迟(默认 4 秒后开启) +// JVM 参数:-XX:BiasedLockingStartupDelay=0 +``` + +#### 轻量级锁 + +当偏向锁被其他线程访问时,升级为轻量级锁。线程在栈帧中创建 Lock Record,通过 CAS 操作将 Mark Word 替换为指向 Lock Record 的指针。 + +#### 重量级锁 + +当自旋超过一定次数(默认 10 次),或自旋线程数超过 CPU 核数一半时,轻量级锁膨胀为重量级锁。此时线程阻塞,进入 Monitor 的 EntryList 等待。 + +
⚠️ 注意:锁只能升级不能降级。一旦变为重量级锁,即使竞争消失也不会回退到轻量级锁。
+ +## 三、JUC 并发工具 + +`java.util.concurrent`(JUC)包提供了丰富的并发工具,远超 `synchronized` 的能力范围。 + +### 3.1 ReentrantLock + +比 `synchronized` 更灵活的锁实现,支持公平锁、可中断、超时获取等特性: + +```java +ReentrantLock lock = new ReentrantLock(true); // 公平锁 + +lock.lock(); +try { + // 临界区代码 +} finally { + lock.unlock(); // 必须在 finally 中释放 +} + +// 尝试获取锁,等待 1 秒 +if (lock.tryLock(1, TimeUnit.SECONDS)) { + try { + // 获取成功 + } finally { + lock.unlock(); + } +} +``` + +### 3.2 CountDownLatch + +允许一个或多个线程等待其他线程完成操作。典型场景:主线程等待多个子任务完成。 + +```java +CountDownLatch latch = new CountDownLatch(3); + +// 创建三个子线程 +for (int i = 0; i < 3; i++) { + new Thread(() -> { + // 执行任务... + latch.countDown(); // 计数减 1 + }).start(); +} + +latch.await(); // 主线程等待,直到计数为 0 +System.out.println("所有子任务完成"); +``` + +### 3.3 CyclicBarrier + +让一组线程到达一个屏障时被阻塞,直到所有线程都到达后屏障才会打开。与 CountDownLatch 的区别在于它可以重复使用。 + +### 3.4 Semaphore + +信号量,用于控制同时访问资源的线程数量。常用于限流场景: + +```java +Semaphore semaphore = new Semaphore(5); // 最多 5 个线程同时访问 + +for (int i = 0; i < 10; i++) { + new Thread(() -> { + try { + semaphore.acquire(); + // 执行任务(最多 5 个线程同时执行) + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + semaphore.release(); + } + }).start(); +} +``` + +## 四、线程池原理 + +### 4.1 核心参数 + +`ThreadPoolExecutor` 是 Java 线程池的核心实现,其构造器包含七个参数: + +```java +public ThreadPoolExecutor( + int corePoolSize, // 核心线程数 + int maximumPoolSize, // 最大线程数 + long keepAliveTime, // 空闲线程存活时间 + TimeUnit unit, // 时间单位 + BlockingQueue workQueue, // 任务队列 + ThreadFactory threadFactory, // 线程工厂 + RejectedExecutionHandler handler // 拒绝策略 +) +``` + +线程池的工作流程:核心线程 → 任务队列 → 最大线程 → 拒绝策略 + +### 4.2 拒绝策略 + +当线程池和任务队列都满了,新提交的任务会被拒绝策略处理: + +- **AbortPolicy**(默认)— 抛出 RejectedExecutionException +- **CallerRunsPolicy** — 由提交任务的线程自己执行 +- **DiscardPolicy** — 静默丢弃,不抛异常 +- **DiscardOldestPolicy** — 丢弃队列中最早的未处理任务 + +> 生产环境最佳实践:不要使用 Executors 的快捷方法创建线程池,而应该手动使用 ThreadPoolExecutor 构造器,这样可以更清楚地了解线程池的配置参数。 + +## 五、虚拟线程 + +Java 21 正式引入了虚拟线程(Virtual Threads),这是 Project Loom 的核心成果。虚拟线程是 JVM 管理的轻量级线程,由平台线程(OS 线程)承载执行。 + +```java +// 创建虚拟线程 +Thread vThread = Thread.ofVirtual() + .name("virtual-thread-1") + .start(() -> { + System.out.println("虚拟线程运行中"); + }); + +// 使用 ExecutorService +try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { + executor.submit(() -> { + // 处理请求 + }); +} +``` + +虚拟线程的优势在于:当你遇到 IO 阻塞时,虚拟线程会被自动"卸载"(unmount),其承载的平台线程可以切换去执行其他虚拟线程。这意味着你可以用极低的成本创建上百万个虚拟线程。 + +
📌 总结:传统的"一个请求一个线程"模式在虚拟线程诞生后变得不再昂贵。对于 IO 密集型应用,虚拟线程可以显著提升吞吐量,同时保持代码简单、可读。
+ diff --git a/posts/后端开发/java-concurrency.md b/posts/后端开发/java-concurrency.md new file mode 100644 index 0000000..ae4b6ad --- /dev/null +++ b/posts/后端开发/java-concurrency.md @@ -0,0 +1,187 @@ +--- +title: "Java 并发编程完整指南:从线程基础到 JUC 进阶" +date: "2026-05-26" +category: "后端开发" +tags: ["Java", "并发", "JUC"] +excerpt: "全面梳理 Java 并发编程知识体系,从线程基础、synchronized 原理到 JUC 并发工具类。" +--- + +## 一、线程基础知识 + +### 1.1 线程的生命周期 + +Java 线程在它的生命周期中会经历六种状态: + +- **NEW** — 线程刚创建,尚未调用 `start()` +- **RUNNABLE** — 线程在 JVM 中运行 +- **BLOCKED** — 线程被阻塞,等待获取锁 +- **WAITING** — 无限期等待 +- **TIMED_WAITING** — 超时等待 +- **TERMINATED** — 线程已执行完毕 + +> 重要区别:BLOCKED 是在等待进入 synchronized 块时发生的,而 WAITING 是通过 Object.wait()、Thread.join() 等方法进入的。 + +### 1.2 线程的创建方式 + +Java 中创建线程主要有四种方式: + +**继承 Thread 类** + +```java +class MyThread extends Thread { + @Override + public void run() { + System.out.println("线程运行中: " + Thread.currentThread().getName()); + } +} +MyThread thread = new MyThread(); +thread.start(); +``` + +**实现 Runnable 接口** + +```java +class MyRunnable implements Runnable { + @Override + public void run() { + System.out.println("线程运行中"); + } +} +Thread thread = new Thread(new MyRunnable()); +thread.start(); +``` + +**Callable 与 Future** + +```java +class MyCallable implements Callable { + @Override + public String call() throws Exception { + Thread.sleep(1000); + return "任务完成"; + } +} +ExecutorService executor = Executors.newSingleThreadExecutor(); +Future future = executor.submit(new MyCallable()); +String result = future.get(); +``` + +## 二、synchronized 原理 + +### 2.1 对象头与 Monitor + +Java 中的每个对象都与一个 Monitor 关联。`synchronized` 关键字的底层实现依赖于对象头中的 Mark Word 和 Monitor 机制。 + +- **Mark Word**:存储对象的 HashCode、GC 分代年龄、锁状态标志 +- **Monitor**:包含 EntryList、WaitSet、Owner 三个关键部分 + +```java +public class Counter { + private int count = 0; + + public synchronized void increment() { + count++; + } + + public void incrementWithBlock() { + synchronized (this) { + count++; + } + } +} +``` + +### 2.2 锁升级过程 + +JDK 1.6 之后,锁不再直接膨胀为重量级锁,而是逐步升级: + +| 锁状态 | 适用场景 | 实现原理 | +|--------|----------|----------| +| 偏向锁 | 只有一个线程访问 | Mark Word 记录线程 ID | +| 轻量级锁 | 多线程交替执行 | CAS + 自旋 | +| 重量级锁 | 竞争激烈 | OS Mutex Lock | + +## 三、JUC 并发工具 + +### 3.1 ReentrantLock + +比 synchronized 更灵活的锁实现: + +```java +ReentrantLock lock = new ReentrantLock(true); +lock.lock(); +try { + // 临界区代码 +} finally { + lock.unlock(); +} +``` + +### 3.2 CountDownLatch + +等待其他线程完成操作: + +```java +CountDownLatch latch = new CountDownLatch(3); +for (int i = 0; i < 3; i++) { + new Thread(() -> { + // 执行任务... + latch.countDown(); + }).start(); +} +latch.await(); +``` + +### 3.3 Semaphore + +控制同时访问资源的线程数量: + +```java +Semaphore semaphore = new Semaphore(5); +semaphore.acquire(); +try { + // 最多5个线程同时执行 +} finally { + semaphore.release(); +} +``` + +## 四、线程池原理 + +### 4.1 核心参数 + +```java +public ThreadPoolExecutor( + int corePoolSize, // 核心线程数 + int maximumPoolSize, // 最大线程数 + long keepAliveTime, // 空闲线程存活时间 + TimeUnit unit, + BlockingQueue workQueue, + ThreadFactory threadFactory, + RejectedExecutionHandler handler +) +``` + +工作流程:核心线程 → 任务队列 → 最大线程 → 拒绝策略 + +### 4.2 拒绝策略 + +- **AbortPolicy** — 抛出异常 +- **CallerRunsPolicy** — 由提交线程自己执行 +- **DiscardPolicy** — 静默丢弃 +- **DiscardOldestPolicy** — 丢弃最早的未处理任务 + +## 五、虚拟线程 + +Java 21 正式引入了虚拟线程: + +```java +Thread vThread = Thread.ofVirtual() + .name("virtual-thread-1") + .start(() -> { + System.out.println("虚拟线程运行中"); + }); +``` + +> 传统"一个请求一个线程"模式在虚拟线程诞生后变得不再昂贵。对于 IO 密集型应用,虚拟线程可以显著提升吞吐量。 + diff --git a/posts/快速入门/markdown-test.md b/posts/快速入门/markdown-test.md new file mode 100644 index 0000000..b9b0a32 --- /dev/null +++ b/posts/快速入门/markdown-test.md @@ -0,0 +1,152 @@ +--- +title: "Markdown 语法全量测试" +date: "2026-05-28" +category: "快速入门" +tags: ["Markdown", "测试"] +excerpt: "涵盖所有常用 Markdown 语法的测试文章,用于验证前端渲染效果。" +--- + +## 一、标题层级 + +### 三级标题 + +#### 四级标题 + +## 二、文本样式 + +这是**粗体**文本,这是*斜体*文本,这是~~删除线~~文本,这是`行内代码`。 + +> 这是一段引用文字。引用可以包含多个段落。 +> +> 第二个段落仍在同一引用内。 +> +> > 这是嵌套引用,用于展示层级关系。 + +## 三、列表 + +### 无序列表 + +- 第一项 +- 第二项 + - 嵌套子项 A + - 嵌套子项 B +- 第三项 + +### 有序列表 + +1. 步骤一 +2. 步骤二 + 1. 子步骤 2.1 + 2. 子步骤 2.2 +3. 步骤三 + +### 任务列表 + +- [x] 已完成任务 +- [ ] 待办任务 +- [ ] 另一项待办 + +## 四、代码块 + +```java +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, World!"); + } +} +``` + +```python +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n - 1) + fibonacci(n - 2) + +print(fibonacci(10)) +``` + +```sql +SELECT u.name, COUNT(o.id) AS order_count +FROM users u +LEFT JOIN orders o ON u.id = o.user_id +GROUP BY u.id +HAVING order_count > 5 +ORDER BY order_count DESC; +``` + +## 五、表格 + +| 名称 | 版本 | 描述 | +|------|------|------| +| Spring Boot | 3.2.5 | Java 后端框架 | +| Vue | 3.4 | 前端渐进式框架 | +| Redis | 7.2 | 内存数据结构存储 | +| MySQL | 8.0 | 关系型数据库 | + +左对齐 | 居中对齐 | 右对齐 +:---|:---:|---: +左 | 中 | 右 +A | B | C + +## 六、链接与图片 + +这是一个[外部链接](https://www.baidu.com),这是[内部锚点](#一标题层级)。 + +![占位图](https://placehold.co/600x200/0366d6/ffffff?text=Hello+World) + +[环境搭建](./环境搭建.md) + +## 七、分隔线 + +--- + +上面的分隔线将内容分成了两个部分。 + +## 八、特殊块 + +> **💡 提示:** 这是一个自定义 HTML 提示框,用于展示提示信息。 + +> **⚠️ 警告:** 这是一个自定义 HTML 警告框,用于展示警告信息。 + +## 九、Emoji 与符号 + +:rocket: 快速启动   :memo: 文档说明   :white_check_mark: 已完成 + +数学公式示例:E = mc² + +上标注1 和 下标 H2O + +## 十、转义字符 + +星号 \* 不会被解析为斜体,下划线 \_ 同理,反引号 \` 也是。 + +HTML 实体:<div> 会被原样显示,© 版权符号。 + +## 十、转义字符aaaaaaaaaaaaaaaaaaaa + +## 十、转义字符aaaaaaaaaaaaaaaaaaaa + +## 十、转义字符aaaaaaaaaaaaaaaaaaaa + +## 十、转义字符aaaaaaaaaaaaaaaaaaaa + +## 十、转义字符aaaaaaaaaaaaaaaaaaaa + +## 十、转义字符aaaaaaaaaaaaaaaaaaaa + +## 十、转义字符aaaaaaaaaaaaaaaaaaaa + +## 十、转义字符aaaaaaaaaaaaaaaaaaaa + +### 十、转义字符aaaaaaaaaaaaaaaaaaaa + +#### 1 + +##### 1 + +###### 4 + + + + + diff --git a/posts/快速入门/环境搭建.md b/posts/快速入门/环境搭建.md new file mode 100644 index 0000000..ba5166d --- /dev/null +++ b/posts/快速入门/环境搭建.md @@ -0,0 +1,80 @@ +--- +title: "环境搭建:Java 开发工具链" +date: "2026-05-18" +category: "快速入门" +tags: ["Java", "工具", "环境"] +excerpt: "JDK 安装、Maven 配置、IDEA 使用技巧,快速搭建 Java 开发环境。" +--- + +## 一、JDK 安装 + +### 1.1 版本选择 + +推荐使用 JDK 21 LTS,长期支持版本,包含了虚拟线程等新特性。 + +下载地址:https://adoptium.net/ + +### 1.2 环境变量配置 + +```bash +# Windows +JAVA_HOME=C:\Program Files\Java\jdk-21 +PATH=%JAVA_HOME%\bin + +# Linux/Mac +export JAVA_HOME=/usr/lib/jvm/jdk-21 +export PATH=$JAVA_HOME/bin:$PATH + +# 验证安装 +java --version +``` + +## 二、Maven 配置 + +### 2.1 安装 + +下载解压后配置环境变量,验证: + +```bash +mvn --version +``` + +### 2.2 配置国内镜像 + +```xml + + + + aliyun + central + https://maven.aliyun.com/repository/public + + +``` + +## 三、IDEA 设置 + +### 3.1 推荐插件 + +- Lombok — 简化 Java 代码 +- Maven Helper — 依赖冲突分析 +- SonarLint — 代码质量检查 + +### 3.2 常用快捷键 + +| 操作 | Windows | Mac | +|------|---------|-----| +| 格式化代码 | Ctrl+Alt+L | Cmd+Opt+L | +| 查找文件 | Ctrl+Shift+N | Cmd+Shift+O | +| 生成代码 | Alt+Insert | Cmd+N | + +## 四、第一个项目 + +```bash +mvn archetype:generate -DgroupId=com.example -DartifactId=hello-world +cd hello-world +mvn clean package +java -jar target/hello-world.jar +``` + +环境搭建完成后,就可以开始 Java 开发之旅了。 diff --git a/posts/数据库/mysql-index.md b/posts/数据库/mysql-index.md new file mode 100644 index 0000000..afd43ce --- /dev/null +++ b/posts/数据库/mysql-index.md @@ -0,0 +1,89 @@ +--- +title: "MySQL 索引优化实战:从 EXPLAIN 到索引设计" +date: "2026-05-24" +category: "数据库" +tags: ["MySQL", "索引", "性能优化"] +excerpt: "深入讲解 B+ 树索引结构、覆盖索引、索引下推、联合索引最左前缀原则。" +--- + +## 一、B+ 树索引结构 + +### 1.1 为什么选择 B+ 树 + +MySQL InnoDB 存储引擎选择 B+ 树作为索引结构,原因如下: + +- 所有数据存储在叶子节点,非叶子节点只存键值 +- 叶子节点通过双向链表连接,支持范围查询 +- 树的高度通常在 2-4 层,IO 次数可控 + +### 1.2 聚簇索引 vs 二级索引 + +**聚簇索引**:叶子节点存储完整行数据,一个表只能有一个。 + +**二级索引**:叶子节点存储主键值,查询需要回表。 + +```sql +-- 查看索引使用情况 +EXPLAIN SELECT * FROM users WHERE email = 'test@example.com'; +``` + +## 二、EXPLAIN 分析 + +### 2.1 关键字段解读 + +| 字段 | 含义 | 期望值 | +|------|------|--------| +| type | 访问类型 | const > ref > range > index > ALL | +| key | 实际使用的索引 | 不应为 NULL | +| rows | 扫描行数估计 | 越小越好 | +| Extra | 额外信息 | Using index 表示覆盖索引 | + +### 2.2 常见优化案例 + +```sql +-- 案例1:避免索引失效 +-- ❌ 函数导致索引失效 +SELECT * FROM orders WHERE DATE(created_at) = '2026-01-01'; + +-- ✅ 范围查询 +SELECT * FROM orders +WHERE created_at >= '2026-01-01' AND created_at < '2026-01-02'; +``` + +## 三、索引设计原则 + +### 3.1 最左前缀原则 + +联合索引 `(a, b, c)` 相当于创建了三个索引: +- `(a)` +- `(a, b)` +- `(a, b, c)` + +```sql +CREATE INDEX idx_name_age_city ON users(name, age, city); + +-- 可以走索引 +SELECT * FROM users WHERE name = '张三'; +SELECT * FROM users WHERE name = '张三' AND age = 25; + +-- 不能走索引(跳过了 name) +SELECT * FROM users WHERE age = 25; +``` + +### 3.2 覆盖索引 + +查询的所有列都包含在索引中,避免回表: + +```sql +CREATE INDEX idx_email_name ON users(email, name); + +-- Using index(覆盖索引) +SELECT email, name FROM users WHERE email = 'test@example.com'; +``` + +## 四、总结 + +- 索引不是越多越好,每个索引都会增加写操作成本 +- 定期分析慢查询日志 +- 使用 `EXPLAIN` 验证索引是否被正确使用 +- 联合索引遵循最左前缀原则 diff --git a/posts/数据库/redis-advanced.md b/posts/数据库/redis-advanced.md new file mode 100644 index 0000000..deed4ac --- /dev/null +++ b/posts/数据库/redis-advanced.md @@ -0,0 +1,79 @@ +--- +title: "Redis 进阶:数据结构底层实现与集群方案" +date: "2026-05-20" +category: "数据库" +tags: ["Redis", "缓存", "数据结构"] +excerpt: "从 SDS、ziplist、skiplist 等底层数据结构讲起,到 RDB/AOF 持久化、哨兵模式、Cluster 集群。" +--- + +## 一、底层数据结构 + +### 1.1 简单动态字符串(SDS) + +Redis 没有使用 C 语言的字符串,而是自己实现了 SDS: + +- 二进制安全 +- 杜绝缓冲区溢出 +- 获取字符串长度 O(1) +- 空间预分配和惰性释放 + +### 1.2 压缩列表(ziplist) + +连续内存块组成的顺序存储结构,节约内存: + +``` +[zlbytes][zltail][zllen][entry...][zlend] +``` + +当列表元素较少或元素较小时使用。Redis 7.0 后被 listpack 替代。 + +### 1.3 跳跃表(skiplist) + +有序集合的底层实现之一,平均 O(log n) 的查找效率: + +```c +typedef struct zskiplistNode { + sds ele; // 成员对象 + double score; // 分值 + zskiplistNode *backward; // 后退指针 + zskiplistLevel { + zskiplistNode *forward; + unsigned long span; + } level[]; +} zskiplistNode; +``` + +## 二、持久化机制 + +### 2.1 RDB + +快照方式,将某一时刻的内存数据写入磁盘: + +``` +save 900 1 # 900秒内至少1个key变化 +save 300 10 # 300秒内至少10个key变化 +``` + +### 2.2 AOF + +记录每次写操作命令,重启时重放: + +- `appendfsync always` — 每条命令都同步 +- `appendfsync everysec` — 每秒同步(推荐) +- `appendfsync no` — 由操作系统决定 + +> 生产环境建议 RDB + AOF 同时开启,取长补短。 + +## 三、集群方案 + +### 3.1 哨兵模式 + +监控主节点,自动故障转移。最少需要 3 个哨兵节点。 + +### 3.2 Cluster 集群 + +数据分片,16384 个哈希槽分布在多个节点。支持水平扩展。 + +## 四、总结 + +理解底层数据结构有助于合理选择数据类型和预估内存占用。生产环境要做好持久化和高可用方案。 diff --git a/posts/😊😊😎/2111111111111111111/ds/sa/新建文件夹/123.md b/posts/😊😊😎/2111111111111111111/ds/sa/新建文件夹/123.md new file mode 100644 index 0000000..e69de29 diff --git a/posts/😊😊😎/2111111111111111111/ds/sa/新建文件夹/新建 文本文档.txt b/posts/😊😊😎/2111111111111111111/ds/sa/新建文件夹/新建 文本文档.txt new file mode 100644 index 0000000..e69de29 diff --git a/public/pwa-192x192.png b/public/pwa-192x192.png new file mode 100644 index 0000000..4e14ca3 Binary files /dev/null and b/public/pwa-192x192.png differ diff --git a/public/pwa-512x512.png b/public/pwa-512x512.png new file mode 100644 index 0000000..785733c Binary files /dev/null and b/public/pwa-512x512.png differ diff --git a/public/pwa-icon.svg b/public/pwa-icon.svg new file mode 100644 index 0000000..2dbc8bb --- /dev/null +++ b/public/pwa-icon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/scripts/copy-images.js b/scripts/copy-images.js new file mode 100644 index 0000000..ae69576 --- /dev/null +++ b/scripts/copy-images.js @@ -0,0 +1,100 @@ +// 构建前将 posts/ 目录处理为静态数据 +// 1. 复制图片等静态文件到 public/posts/ +// 2. 生成 posts-data.json 供前端直接导入 + +import { cpSync, existsSync, rmSync, readdirSync, statSync, readFileSync, mkdirSync, writeFileSync } from 'fs' +import { join, dirname, relative } from 'path' +import { fileURLToPath } from 'url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const root = join(__dirname, '..') +const src = join(root, 'posts') +const dst = join(root, 'public', 'posts') + +// 1. 复制静态文件到 public/posts/ +if (existsSync(dst)) { + rmSync(dst, { recursive: true, force: true }) +} + +cpSync(src, dst, { + recursive: true, + filter: (srcPath) => !srcPath.endsWith('.md') +}) + +console.log('已同步 posts 静态文件到 public/posts/') + +// 2. 扫描 posts/ 生成数据清单 +function scanDir(dir) { + const entries = readdirSync(dir) + const result = { dirs: [], files: [] } + + for (const name of entries) { + if (name.startsWith('.')) continue + const full = join(dir, name) + const st = statSync(full) + + if (st.isDirectory()) { + const children = scanDir(full) + // 跳过空目录 + if (children.dirs.length === 0 && children.files.length === 0) continue + result.dirs.push({ name, children }) + } else if (name.endsWith('.md')) { + const relPath = relative(src, full).replace(/\\/g, '/').replace(/\.md$/, '') + const content = readFileSync(full, 'utf-8') + result.files.push({ name: name.replace('.md', ''), path: relPath, content }) + } + } + + // 排序:文件夹在前,文件在后,各自字母排序 + result.dirs.sort((a, b) => a.name.localeCompare(b.name)) + result.files.sort((a, b) => a.name.localeCompare(b.name)) + + return result +} + +function buildTree(scanResult) { + const items = [] + + for (const dir of scanResult.dirs) { + items.push({ type: 'category', name: dir.name, children: buildTree(dir.children) }) + } + + for (const file of scanResult.files) { + items.push({ type: 'post', name: file.name, path: file.path }) + } + + return items +} + +function buildContentMap(scanResult, map = {}) { + for (const file of scanResult.files) { + map[file.path] = file.content + } + for (const dir of scanResult.dirs) { + buildContentMap(dir.children, map) + } + return map +} + +function getAllPaths(scanResult, paths = []) { + for (const file of scanResult.files) { + paths.push(file.path) + } + for (const dir of scanResult.dirs) { + getAllPaths(dir.children, paths) + } + return paths +} + +const scanResult = scanDir(src) +const data = { + tree: buildTree(scanResult), + contentMap: buildContentMap(scanResult), + paths: getAllPaths(scanResult) +} + +const outDir = join(root, 'src', 'generated') +mkdirSync(outDir, { recursive: true }) +writeFileSync(join(outDir, 'posts-data.json'), JSON.stringify(data)) + +console.log(`已生成 posts-data.json (${data.paths.length} 篇文章)`) diff --git a/scripts/generate-icons.js b/scripts/generate-icons.js new file mode 100644 index 0000000..1f3c72c --- /dev/null +++ b/scripts/generate-icons.js @@ -0,0 +1,20 @@ +import sharp from 'sharp' +import { join, dirname } from 'path' +import { fileURLToPath } from 'url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const rootDir = join(__dirname, '..') +const svgPath = join(rootDir, 'public', 'pwa-icon.svg') + +async function generate() { + const sizes = [192, 512] + for (const size of sizes) { + await sharp(svgPath) + .resize(size, size) + .png() + .toFile(join(rootDir, 'public', `pwa-${size}x${size}.png`)) + console.log(`Generated pwa-${size}x${size}.png`) + } +} + +generate().catch(console.error) diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..e181897 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/src/api/index.js b/src/api/index.js new file mode 100644 index 0000000..57f31ab --- /dev/null +++ b/src/api/index.js @@ -0,0 +1,11 @@ +import { getPostTree, getPostContent } from '../data' + +export function fetchPosts() { + return getPostTree() +} + +export function fetchPostContent(path) { + const result = getPostContent(path) + if (!result) throw new Error('文章不存在') + return result +} diff --git a/src/components/FooterBar.vue b/src/components/FooterBar.vue new file mode 100644 index 0000000..a0c368d --- /dev/null +++ b/src/components/FooterBar.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue new file mode 100644 index 0000000..0937437 --- /dev/null +++ b/src/components/Sidebar.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/src/components/TocSidebar.vue b/src/components/TocSidebar.vue new file mode 100644 index 0000000..3427e35 --- /dev/null +++ b/src/components/TocSidebar.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/src/components/Topbar.vue b/src/components/Topbar.vue new file mode 100644 index 0000000..7f4f45a --- /dev/null +++ b/src/components/Topbar.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/src/components/TreeCategory.vue b/src/components/TreeCategory.vue new file mode 100644 index 0000000..874b0a3 --- /dev/null +++ b/src/components/TreeCategory.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/src/data.js b/src/data.js new file mode 100644 index 0000000..d050782 --- /dev/null +++ b/src/data.js @@ -0,0 +1,42 @@ +// 静态数据层 —— 从构建时生成的 JSON 导入文章数据,替代后端 API + +import postsData from './generated/posts-data.json' + +const contentMap = new Map() +for (const [path, content] of Object.entries(postsData.contentMap)) { + contentMap.set(path, content) +} + +let _tree = null + +export function getPostTree() { + if (_tree) return _tree + _tree = postsData.tree + return _tree +} + +export function getPostContent(path) { + const content = contentMap.get(path) + if (!content) return null + return { path, content } +} + +export function getAllPostPaths() { + return postsData.paths +} + +/** 根据文章路径 + 相对图片引用,解析出图片 URL */ +export function resolveImage(postPath, relHref) { + const dir = postPath.substring(0, postPath.lastIndexOf('/') + 1) + const raw = dir + relHref.replace(/^\.\//, '') + const parts = raw.split('/') + const resolved = [] + for (const p of parts) { + if (p === '..') { + resolved.pop() + } else if (p !== '.') { + resolved.push(p) + } + } + return '/posts/' + resolved.join('/') +} diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..293106d --- /dev/null +++ b/src/main.js @@ -0,0 +1,6 @@ +import { ViteSSG } from 'vite-ssg' +import App from './App.vue' +import { routes } from './router' +import './style.css' + +export const createApp = ViteSSG(App, { routes }, ({ app }) => {}) diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 0000000..26353de --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,6 @@ +import HomeView from '../views/HomeView.vue' + +export const routes = [ + { path: '/', name: 'home', component: HomeView }, + { path: '/post/:pathMatch(.*)*', name: 'post', component: HomeView, props: true } +] diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..2523bb6 --- /dev/null +++ b/src/style.css @@ -0,0 +1,215 @@ +:root { + --bg-primary: #fff; + --bg-secondary: #f6f8fa; + --bg-highlight: #f1f8ff; + --border-primary: #e1e4e8; + --border-secondary: #f0f0f0; + --text-primary: #24292e; + --text-secondary: #586069; + --text-tertiary: #8b949e; + --accent: #0366d6; + --scrollbar-thumb: #d1d5da; + --dot-color: #d1d5da; +} + +[data-theme="dark"] { + --bg-primary: #0d1117; + --bg-secondary: #161b22; + --bg-highlight: #1c2838; + --border-primary: #30363d; + --border-secondary: #21262d; + --text-primary: #e6edf3; + --text-secondary: #8b949e; + --text-tertiary: #6e7681; + --accent: #58a6ff; + --scrollbar-thumb: #484f58; + --dot-color: #484f58; +} + +* { margin: 0; padding: 0; box-sizing: border-box; } + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', Helvetica, Arial, sans-serif; + background: var(--bg-secondary); + color: var(--text-primary); + line-height: 1.6; +} + +html { + scroll-behavior: smooth; + scroll-padding-top: 80px; +} + +a { text-decoration: none; } + +.article-content a.internal-link { + color: var(--accent); + cursor: pointer; +} + +.article-content a.internal-link:hover { + text-decoration: underline; +} +/* 滚动条 */ +::-webkit-scrollbar { width: 4px; } +::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 2px; } + +/* 代码块 */ +pre { + background: var(--bg-secondary); + border: 1px solid var(--border-primary); + border-radius: 8px; + padding: 16px; + overflow-x: auto; + font-size: 13px; + line-height: 1.5; + margin: 16px 0; +} + +code { + font-family: 'Cascadia Code', Consolas, monospace; + font-size: 13px; +} + +:not(pre) > code { + background: var(--bg-secondary); + padding: 2px 6px; + border-radius: 4px; + border: 1px solid var(--border-primary); +} + +/* 代码块容器(带复制按钮和语言标签) */ +.code-block-wrapper { + position: relative; + margin: 16px 0; + border-radius: 8px; + overflow: hidden; + border: 1px solid var(--border-primary); + background: var(--bg-secondary); +} + +.code-block-wrapper pre { + margin: 0; + border: none; + border-radius: 0; + padding-top: 36px; + background: var(--bg-secondary); +} + +.code-block-wrapper code.hljs { + background: var(--bg-secondary); +} + +.code-lang { + position: absolute; + top: 8px; + left: 16px; + font-size: 11px; + color: var(--text-tertiary); + pointer-events: none; +} + +.copy-btn { + position: absolute; + top: 6px; + right: 8px; + font-size: 12px; + padding: 2px 10px; + background: var(--bg-secondary); + border: 1px solid var(--scrollbar-thumb); + border-radius: 4px; + color: var(--text-secondary); + cursor: pointer; + transition: all .15s; +} + +.copy-btn:hover { + background: var(--border-primary); + color: var(--text-primary); +} + +blockquote { + border-left: 4px solid var(--accent); + padding: 8px 18px; + margin: 20px 0; + color: var(--text-secondary); + background: var(--bg-highlight); + border-radius: 0 6px 6px 0; +} + +blockquote > *:first-child { margin-top: 0; } +blockquote > *:last-child { margin-bottom: 0; } + +.article-content blockquote > *:first-child { margin-top: 0; } +.article-content blockquote > *:last-child { margin-bottom: 0; } + +table { + width: 100%; + border-collapse: collapse; + margin: 16px 0; + font-size: 14px; +} + +th, td { + border: 1px solid var(--border-primary); + padding: 10px 14px; + text-align: left; +} + +th { + background: var(--bg-secondary); + font-weight: 600; +} + +/* markdown 渲染内容样式 */ +.article-content > :first-child { margin-top: 0; } +.article-content > :first-child > :first-child { margin-top: 0; } + +.article-content h2 { + font-size: 22px; + font-weight: 600; + margin: 12px 0 8px; + padding-bottom: 8px; + border-bottom: 1px solid var(--border-primary); +} + +.article-content h3 { + font-size: 18px; + font-weight: 600; + margin: 12px 0 8px; +} + +.article-content h4 { + font-size: 16px; + font-weight: 600; + margin: 0 0 6px; +} + +.article-content p { + margin-bottom: 5px; + font-size: 15px; + line-height: 1.9; +} + +.article-content ul, .article-content ol { + margin-bottom: 16px; + padding-left: 24px; +} + +.article-content li { + margin-bottom: 8px; + font-size: 15px; +} + +.article-content img { + display: block; + margin: 0 auto; + max-width: 100%; + border-radius: 8px; + cursor: zoom-in; + transition: opacity .15s; +} + +.article-content img:hover { + opacity: .85; +} diff --git a/src/utils/slug.js b/src/utils/slug.js new file mode 100644 index 0000000..34b7395 --- /dev/null +++ b/src/utils/slug.js @@ -0,0 +1,9 @@ +/** + * 将标题文本转为合法的 HTML ID + * 保留中文字符、字母、数字,空格转 -,其余符号去除 + */ +export function slugify(text) { + return text + .replace(/\s+/g, '-') + .replace(/[^一-鿿\w-]/g, '') +} diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue new file mode 100644 index 0000000..e730fd0 --- /dev/null +++ b/src/views/HomeView.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..ecbabd4 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,63 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { VitePWA } from 'vite-plugin-pwa' +import { readFileSync } from 'fs' + +function getPostPaths() { + try { + const data = JSON.parse(readFileSync('./src/generated/posts-data.json', 'utf-8')) + return data.paths + } catch { + return [] + } +} + +export default defineConfig({ + plugins: [ + vue(), + VitePWA({ + registerType: 'autoUpdate', + includeAssets: ['pwa-192x192.png', 'pwa-512x512.png'], + manifest: { + name: '知识指南', + short_name: '知识指南', + description: '个人知识库与技术博客', + theme_color: '#24292e', + background_color: '#ffffff', + display: 'standalone', + start_url: '/', + icons: [ + { + src: 'pwa-192x192.png', + sizes: '192x192', + type: 'image/png', + purpose: 'any' + }, + { + src: 'pwa-512x512.png', + sizes: '512x512', + type: 'image/png', + purpose: 'any maskable' + } + ] + }, + workbox: { + maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, + globPatterns: ['**/*.{js,css,html,svg,png,ico,woff,woff2}'], + runtimeCaching: [ + { + urlPattern: /^https:\/\/cdnjs\.cloudflare\.com\/.*/i, + handler: 'CacheFirst', + options: { + cacheName: 'cdn-cache', + expiration: { maxEntries: 10, maxAgeSeconds: 60 * 60 * 24 * 30 } + } + } + ] + } + }) + ], + ssgOptions: { + includedRoutes: () => getPostPaths().map(p => `/post/${p}`) + } +})