diff --git a/backend/migrate-passwords.ts b/backend/migrate-passwords.ts new file mode 100644 index 0000000..f27040d --- /dev/null +++ b/backend/migrate-passwords.ts @@ -0,0 +1,67 @@ +import bcrypt from 'bcrypt'; +import pool from './src/config/database'; + +/** + * 密码迁移脚本 + * 将数据库中所有明文密码更新为bcrypt哈希密码 + * 仅更新密码不是bcrypt哈希格式(以$2b$开头)的用户 + */ +async function migratePasswords() { + try { + console.log('开始密码迁移...'); + + // 获取所有用户 + const [users] = await pool.query( + 'SELECT user_id, username, password FROM user' + ); + + console.log(`共找到 ${users.length} 个用户`); + + let migratedCount = 0; + let skippedCount = 0; + let errorCount = 0; + + for (const user of users) { + const { user_id, username, password } = user; + + // 检查密码是否已经是bcrypt哈希格式 + if (password.startsWith('$2b$') || password.startsWith('$2a$') || password.startsWith('$2y$')) { + console.log(`✓ 用户 ${username} (ID: ${user_id}) 密码已经是哈希格式,跳过`); + skippedCount++; + continue; + } + + try { + // 生成bcrypt哈希(使用默认盐轮数10) + const hashedPassword = await bcrypt.hash(password, 10); + + // 更新数据库 + await pool.query( + 'UPDATE user SET password = ? WHERE user_id = ?', + [hashedPassword, user_id] + ); + + console.log(`✓ 用户 ${username} (ID: ${user_id}) 密码已更新为哈希格式`); + migratedCount++; + } catch (err) { + console.error(`✗ 用户 ${username} (ID: ${user_id}) 密码更新失败:`, err); + errorCount++; + } + } + + console.log('\n迁移完成!'); + console.log(`成功迁移: ${migratedCount} 个用户`); + console.log(`跳过(已是哈希): ${skippedCount} 个用户`); + console.log(`失败: ${errorCount} 个用户`); + + // 关闭连接池 + await pool.end(); + process.exit(0); + } catch (error) { + console.error('密码迁移失败:', error); + process.exit(1); + } +} + +// 运行迁移 +migratePasswords(); \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index f96bc74..fa698bc 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "dependencies": { "axios": "^1.6.0", - "bcryptjs": "^2.4.3", + "bcrypt": "^5.1.1", "cors": "^2.8.5", "dotenv": "^16.3.1", "exceljs": "^4.4.0", @@ -18,7 +18,7 @@ "mysql2": "^3.6.5" }, "devDependencies": { - "@types/bcryptjs": "^2.4.6", + "@types/bcrypt": "^5.0.2", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.11", @@ -1012,6 +1012,78 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.10", "resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.10.tgz", @@ -1112,12 +1184,15 @@ "@babel/types": "^7.28.2" } }, - "node_modules/@types/bcryptjs": { - "version": "2.4.6", - "resolved": "https://registry.npmmirror.com/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", - "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/body-parser": { "version": "1.19.6", @@ -1351,6 +1426,12 @@ "dev": true, "license": "MIT" }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", @@ -1390,6 +1471,41 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/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/agent-base/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/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1410,7 +1526,6 @@ "version": "5.0.1", "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1446,6 +1561,12 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC" + }, "node_modules/archiver": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/archiver/-/archiver-5.3.2.tgz", @@ -1515,6 +1636,20 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz", @@ -1725,11 +1860,19 @@ "node": ">=6.0.0" } }, - "node_modules/bcryptjs": { - "version": "2.4.3", - "resolved": "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-2.4.3.tgz", - "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", - "license": "MIT" + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } }, "node_modules/big-integer": { "version": "1.6.52", @@ -2094,6 +2237,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-3.9.0.tgz", @@ -2170,6 +2322,15 @@ "dev": true, "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2203,6 +2364,12 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2387,6 +2554,12 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz", @@ -2415,6 +2588,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2559,7 +2741,6 @@ "version": "8.0.0", "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/encodeurl": { @@ -2958,6 +3139,36 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3016,6 +3227,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/generate-function": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/generate-function/-/generate-function-2.3.1.tgz", @@ -3216,6 +3448,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", @@ -3255,6 +3493,42 @@ "url": "https://opencollective.com/express" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/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/https-proxy-agent/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/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz", @@ -3409,7 +3683,6 @@ "version": "3.0.0", "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4754,11 +5027,50 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -4846,6 +5158,32 @@ "dev": true, "license": "MIT" }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz", @@ -4860,6 +5198,21 @@ "dev": true, "license": "MIT" }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4882,6 +5235,19 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", @@ -5474,6 +5840,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", @@ -5585,7 +5957,6 @@ "version": "3.0.7", "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, "license": "ISC" }, "node_modules/sisteransi": { @@ -5697,7 +6068,6 @@ "version": "4.2.3", "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -5712,7 +6082,6 @@ "version": "6.0.1", "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -5780,6 +6149,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar 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", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz", @@ -5796,6 +6183,12 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmmirror.com/test-exclude/-/test-exclude-6.0.0.tgz", @@ -5849,6 +6242,12 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/traverse": { "version": "0.3.9", "resolved": "https://registry.npmmirror.com/traverse/-/traverse-0.3.9.tgz", @@ -6284,6 +6683,22 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", @@ -6300,6 +6715,15 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index 83519e5..7f878bf 100644 --- a/backend/package.json +++ b/backend/package.json @@ -15,14 +15,16 @@ }, "dependencies": { "axios": "^1.6.0", + "bcrypt": "^5.1.1", "cors": "^2.8.5", "dotenv": "^16.3.1", - "express": "^4.18.2", "exceljs": "^4.4.0", + "express": "^4.18.2", "jsonwebtoken": "^9.0.2", "mysql2": "^3.6.5" }, "devDependencies": { + "@types/bcrypt": "^5.0.2", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.11", diff --git a/backend/src/config/database.ts b/backend/src/config/database.ts index 18ba2dd..fd8e3b5 100644 --- a/backend/src/config/database.ts +++ b/backend/src/config/database.ts @@ -9,6 +9,7 @@ const pool = mysql.createPool({ user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD || '', database: process.env.DB_NAME || 'employee_performance', + charset: 'utf8mb4', waitForConnections: true, connectionLimit: 10, queueLimit: 0, diff --git a/backend/src/db/seed.sql b/backend/src/db/seed.sql index 9fa4b56..6c4dfe9 100644 --- a/backend/src/db/seed.sql +++ b/backend/src/db/seed.sql @@ -6,32 +6,32 @@ USE employee_performance; -- 1. 总经理 INSERT INTO user (username, password, name, role, department, position, manager_id, status) -VALUES ('gm001', '123456', '张总', 'generalManager', '管理层', '总经理', NULL, 'active') +VALUES ('gm001', '$2b$10$BwEJVCKkcCcmSM3m15QLE.WzYJxifJRjY2c.IhDZczyshjyOLDsSu', '张总', 'generalManager', '管理层', '总经理', NULL, 'active') ON DUPLICATE KEY UPDATE username=username; -- 2. 部门经理(技术部) INSERT INTO user (username, password, name, role, department, position, manager_id, status) -VALUES ('mgr001', '123456', '李经理', 'manager', '技术部', '技术经理', 1, 'active') +VALUES ('mgr001', '$2b$10$BwEJVCKkcCcmSM3m15QLE.WzYJxifJRjY2c.IhDZczyshjyOLDsSu', '李经理', 'manager', '技术部', '技术经理', 1, 'active') ON DUPLICATE KEY UPDATE username=username; -- 3. 部门经理(销售部) INSERT INTO user (username, password, name, role, department, position, manager_id, status) -VALUES ('mgr002', '123456', '王经理', 'manager', '销售部', '销售经理', 1, 'active') +VALUES ('mgr002', '$2b$10$BwEJVCKkcCcmSM3m15QLE.WzYJxifJRjY2c.IhDZczyshjyOLDsSu', '王经理', 'manager', '销售部', '销售经理', 1, 'active') ON DUPLICATE KEY UPDATE username=username; -- 4. 员工(技术部) INSERT INTO user (username, password, name, role, department, position, manager_id, status) VALUES -('emp001', '123456', '张三', 'employee', '技术部', '前端工程师', 2, 'active'), -('emp002', '123456', '李四', 'employee', '技术部', '后端工程师', 2, 'active'), -('emp003', '123456', '王五', 'employee', '技术部', '测试工程师', 2, 'active') +('emp001', '$2b$10$BwEJVCKkcCcmSM3m15QLE.WzYJxifJRjY2c.IhDZczyshjyOLDsSu', '张三', 'employee', '技术部', '前端工程师', 2, 'active'), +('emp002', '$2b$10$BwEJVCKkcCcmSM3m15QLE.WzYJxifJRjY2c.IhDZczyshjyOLDsSu', '李四', 'employee', '技术部', '后端工程师', 2, 'active'), +('emp003', '$2b$10$BwEJVCKkcCcmSM3m15QLE.WzYJxifJRjY2c.IhDZczyshjyOLDsSu', '王五', 'employee', '技术部', '测试工程师', 2, 'active') ON DUPLICATE KEY UPDATE username=username; -- 5. 员工(销售部) INSERT INTO user (username, password, name, role, department, position, manager_id, status) VALUES -('emp004', '123456', '赵六', 'employee', '销售部', '销售专员', 3, 'active'), -('emp005', '123456', '孙七', 'employee', '销售部', '销售专员', 3, 'active') +('emp004', '$2b$10$BwEJVCKkcCcmSM3m15QLE.WzYJxifJRjY2c.IhDZczyshjyOLDsSu', '赵六', 'employee', '销售部', '销售专员', 3, 'active'), +('emp005', '$2b$10$BwEJVCKkcCcmSM3m15QLE.WzYJxifJRjY2c.IhDZczyshjyOLDsSu', '孙七', 'employee', '销售部', '销售专员', 3, 'active') ON DUPLICATE KEY UPDATE username=username; -- 插入默认考核规则配置 diff --git a/backend/src/db/seed.ts b/backend/src/db/seed.ts index 74b098a..bad6c4c 100644 --- a/backend/src/db/seed.ts +++ b/backend/src/db/seed.ts @@ -24,11 +24,11 @@ async function runSeed() { console.log('\n✅ 数据库种子数据插入完成!'); console.log('\n测试账号信息:'); - console.log('总经理: gm001 / 123456'); - console.log('技术部经理: mgr001 / 123456'); - console.log('销售部经理: mgr002 / 123456'); - console.log('技术部员工: emp001, emp002, emp003 / 123456'); - console.log('销售部员工: emp004, emp005 / 123456'); + console.log('总经理: gm001 / 123456(密码加密存储)'); + console.log('技术部经理: mgr001 / 123456(密码加密存储)'); + console.log('销售部经理: mgr002 / 123456(密码加密存储)'); + console.log('技术部员工: emp001, emp002, emp003 / 123456(密码加密存储)'); + console.log('销售部员工: emp004, emp005 / 123456(密码加密存储)'); process.exit(0); } catch (error) { diff --git a/backend/src/routes/employee.ts b/backend/src/routes/employee.ts index a9c907f..7811487 100644 --- a/backend/src/routes/employee.ts +++ b/backend/src/routes/employee.ts @@ -1,3 +1,4 @@ +import bcrypt from 'bcrypt'; import { Router, Request, Response } from 'express'; import { authenticate } from '../middlewares/authenticate'; import { authorize } from '../middlewares/authorize'; @@ -39,6 +40,12 @@ router.post('/create', authorize('manager', 'generalManager'), async (req: Reque return res.status(400).json({ code: 400, message: '用户名、密码、姓名、部门、岗位均为必填' }); } + // 检查字段是否包含占位符(多个问号) + const placeholderRegex = /^\?+$/; + if (placeholderRegex.test(name) || placeholderRegex.test(department) || placeholderRegex.test(position)) { + return res.status(400).json({ code: 400, message: '姓名、部门、岗位不能使用问号占位符,请填写真实信息' }); + } + try { // 检查用户名是否已存在 const [existing] = await pool.query('SELECT user_id FROM user WHERE username = ?', [username]); @@ -48,10 +55,13 @@ router.post('/create', authorize('manager', 'generalManager'), async (req: Reque const managerId = user.role === 'manager' ? user.userId : null; + // 哈希密码 + const hashedPassword = await bcrypt.hash(password, 10); + const [result] = await pool.query( `INSERT INTO user (username, password, name, role, department, position, manager_id, status) VALUES (?, ?, ?, 'employee', ?, ?, ?, 'active')`, - [username, '123456', name, department, position, managerId] + [username, hashedPassword, name, department, position, managerId] ); return res.json({ code: 200, message: '员工账号创建成功', data: { userId: result.insertId } }); diff --git a/backend/src/services/AuthService.ts b/backend/src/services/AuthService.ts index c56956f..23f1d9b 100644 --- a/backend/src/services/AuthService.ts +++ b/backend/src/services/AuthService.ts @@ -1,9 +1,11 @@ -// 注意:此版本使用明文密码验证,所有用户密码均为123456(仅用于测试环境) -// 生产环境必须使用加密密码存储和验证 +// 密码使用bcrypt哈希存储和验证 +// 迁移期间支持明文密码自动升级为哈希密码 +import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; import { findByUsername, createUser, CreateUserInput } from '../dao/UserDAO'; import { JWT_SECRET, JWT_EXPIRES_IN } from '../config/jwt'; import { LoginResult, UserInfo, UserRole } from '../types'; +import pool from '../config/database'; export async function login( username: string, @@ -16,7 +18,26 @@ export async function login( throw new Error('用户名或密码错误'); } - const passwordMatch = password === user.password; + // 检查密码是否为bcrypt哈希格式(以$2b$开头) + const isBcryptHash = user.password.startsWith('$2b$'); + + let passwordMatch = false; + + if (isBcryptHash) { + // 密码已经是哈希格式,使用bcrypt.compare验证 + passwordMatch = await bcrypt.compare(password, user.password); + } else { + // 密码是明文,直接比较(迁移期间) + passwordMatch = password === user.password; + + // 如果密码匹配,将明文密码更新为哈希密码 + if (passwordMatch) { + const hashedPassword = await bcrypt.hash(password, 10); + await pool.query('UPDATE user SET password = ? WHERE user_id = ?', [hashedPassword, user.user_id]); + console.log(`用户 ${username} 密码已从明文更新为哈希值`); + } + } + if (!passwordMatch) { throw new Error('用户名或密码错误'); } @@ -56,16 +77,23 @@ export async function register(userData: RegisterInput): Promise { throw new Error('用户名、密码、姓名、部门和岗位均为必填'); } + // 检查字段是否包含占位符(多个问号) + const placeholderRegex = /^\?+$/; + if (placeholderRegex.test(name) || placeholderRegex.test(department) || placeholderRegex.test(position)) { + throw new Error('姓名、部门、岗位不能使用问号占位符,请填写真实信息'); + } + // 检查用户名是否已存在 const existingUser = await findByUsername(username); if (existingUser) { throw new Error('用户名已存在'); } - // 创建用户 - 所有用户密码固定为123456(明文存储) + // 创建用户 - 密码哈希存储 + const hashedPassword = await bcrypt.hash(password, 10); const userId = await createUser({ username, - password: '123456', // 固定密码,忽略用户输入的密码 + password: hashedPassword, // 存储哈希后的密码 name, role, department, diff --git a/backend/tmpclaude-0fee-cwd b/backend/tmpclaude-0fee-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-0fee-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-1aba-cwd b/backend/tmpclaude-1aba-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-1aba-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-21bf-cwd b/backend/tmpclaude-21bf-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-21bf-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-3d35-cwd b/backend/tmpclaude-3d35-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-3d35-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-4cd8-cwd b/backend/tmpclaude-4cd8-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-4cd8-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-53c5-cwd b/backend/tmpclaude-53c5-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-53c5-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-8dfc-cwd b/backend/tmpclaude-8dfc-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-8dfc-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-ca77-cwd b/backend/tmpclaude-ca77-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-ca77-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-d5fe-cwd b/backend/tmpclaude-d5fe-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-d5fe-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-fe0a-cwd b/backend/tmpclaude-fe0a-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-fe0a-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/frontend/src/pages/Register.tsx b/frontend/src/pages/Register.tsx index ad2b11a..e480fb3 100644 --- a/frontend/src/pages/Register.tsx +++ b/frontend/src/pages/Register.tsx @@ -92,7 +92,7 @@ const RegisterPage: React.FC = () => { diff --git a/tmpclaude-044f-cwd b/tmpclaude-044f-cwd new file mode 100644 index 0000000..0a8904c --- /dev/null +++ b/tmpclaude-044f-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system diff --git a/tmpclaude-17c0-cwd b/tmpclaude-17c0-cwd new file mode 100644 index 0000000..0a8904c --- /dev/null +++ b/tmpclaude-17c0-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system diff --git a/tmpclaude-50b3-cwd b/tmpclaude-50b3-cwd new file mode 100644 index 0000000..0a8904c --- /dev/null +++ b/tmpclaude-50b3-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system diff --git a/tmpclaude-5a12-cwd b/tmpclaude-5a12-cwd new file mode 100644 index 0000000..0a8904c --- /dev/null +++ b/tmpclaude-5a12-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system diff --git a/tmpclaude-f3af-cwd b/tmpclaude-f3af-cwd new file mode 100644 index 0000000..0a8904c --- /dev/null +++ b/tmpclaude-f3af-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system