From ce2d44689d0ad54c945581cd4d25616c0f2de508 Mon Sep 17 00:00:00 2001 From: Tianpao Date: Sun, 5 Oct 2025 19:36:21 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E6=96=87=E4=BB=B6=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E4=B8=8E=E4=BE=9D=E8=B5=96=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +- config.example.json | 14 ++ package.json | 4 +- pnpm-lock.yaml | 208 ++++++++++++++++++++++- src/controller/download.controller.ts | 0 src/controller/versionlist.controller.ts | 110 ++++++++++++ src/main.ts | 10 ++ src/router/download.router.ts | 0 src/router/versionlist.router.ts | 23 +++ src/utils/config.ts | 22 +++ src/utils/pgsql.ts | 10 ++ 11 files changed, 395 insertions(+), 11 deletions(-) create mode 100644 config.example.json create mode 100644 src/controller/download.controller.ts create mode 100644 src/controller/versionlist.controller.ts create mode 100644 src/router/download.router.ts create mode 100644 src/router/versionlist.router.ts create mode 100644 src/utils/config.ts create mode 100644 src/utils/pgsql.ts diff --git a/.gitignore b/.gitignore index 04623dc..d2ba956 100644 --- a/.gitignore +++ b/.gitignore @@ -133,6 +133,5 @@ ndist .pnp.* # test files -*.zip -*.mrpack -instance/ \ No newline at end of file +config.json +*.zip \ No newline at end of file diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..77111d7 --- /dev/null +++ b/config.example.json @@ -0,0 +1,14 @@ +{ + "database":{ + "host":"string", + "port":0, + "user":"string", + "database":"string", + "password":"string" + }, + "express":{ + "port":0, + "host":"", + "logger":true + } +} \ No newline at end of file diff --git a/package.json b/package.json index 6ca75fd..a5ad748 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,13 @@ "test": "tsc&&node dist/main.js" }, "devDependencies": { + "@types/express": "^5.0.3", "@types/node": "^24.6.2", + "@types/pg": "^8.15.5", "typescript": "^5.9.3" }, "dependencies": { "express": "^5.1.0", - "postgres": "^3.4.7" + "pg": "^8.16.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e2c2551..c0d7b1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,22 +11,64 @@ importers: express: specifier: ^5.1.0 version: 5.1.0 - postgres: - specifier: ^3.4.7 - version: 3.4.7 + pg: + specifier: ^8.16.3 + version: 8.16.3 devDependencies: + '@types/express': + specifier: ^5.0.3 + version: 5.0.3 '@types/node': specifier: ^24.6.2 version: 24.6.2 + '@types/pg': + specifier: ^8.15.5 + version: 8.15.5 typescript: specifier: ^5.9.3 version: 5.9.3 packages: + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/express-serve-static-core@5.0.7': + resolution: {integrity: sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==} + + '@types/express@5.0.3': + resolution: {integrity: sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/node@24.6.2': resolution: {integrity: sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==} + '@types/pg@8.15.5': + resolution: {integrity: sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/send@0.17.5': + resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + + '@types/send@1.2.0': + resolution: {integrity: sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==} + + '@types/serve-static@1.15.9': + resolution: {integrity: sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -212,9 +254,55 @@ packages: path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} - postgres@3.4.7: - resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} - engines: {node: '>=12'} + pg-cloudflare@1.2.7: + resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} + + pg-connection-string@2.9.1: + resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.10.1: + resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.10.3: + resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.16.3: + resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} @@ -269,6 +357,10 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -304,12 +396,67 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + snapshots: + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 24.6.2 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 24.6.2 + + '@types/express-serve-static-core@5.0.7': + dependencies: + '@types/node': 24.6.2 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.0 + + '@types/express@5.0.3': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.0.7 + '@types/serve-static': 1.15.9 + + '@types/http-errors@2.0.5': {} + + '@types/mime@1.3.5': {} + '@types/node@24.6.2': dependencies: undici-types: 7.13.0 + '@types/pg@8.15.5': + dependencies: + '@types/node': 24.6.2 + pg-protocol: 1.10.3 + pg-types: 2.2.0 + + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/send@0.17.5': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 24.6.2 + + '@types/send@1.2.0': + dependencies: + '@types/node': 24.6.2 + + '@types/serve-static@1.15.9': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.6.2 + '@types/send': 0.17.5 + accepts@2.0.0: dependencies: mime-types: 3.0.1 @@ -506,7 +653,50 @@ snapshots: path-to-regexp@8.3.0: {} - postgres@3.4.7: {} + pg-cloudflare@1.2.7: + optional: true + + pg-connection-string@2.9.1: {} + + pg-int8@1.0.1: {} + + pg-pool@3.10.1(pg@8.16.3): + dependencies: + pg: 8.16.3 + + pg-protocol@1.10.3: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.16.3: + dependencies: + pg-connection-string: 2.9.1 + pg-pool: 3.10.1(pg@8.16.3) + pg-protocol: 1.10.3 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.2.7 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + + postgres-array@2.0.0: {} + + postgres-bytea@1.0.0: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 proxy-addr@2.0.7: dependencies: @@ -595,6 +785,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + split2@4.2.0: {} + statuses@2.0.1: {} statuses@2.0.2: {} @@ -616,3 +808,5 @@ snapshots: vary@1.1.2: {} wrappy@1.0.2: {} + + xtend@4.0.2: {} diff --git a/src/controller/download.controller.ts b/src/controller/download.controller.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/controller/versionlist.controller.ts b/src/controller/versionlist.controller.ts new file mode 100644 index 0000000..e5d0a0d --- /dev/null +++ b/src/controller/versionlist.controller.ts @@ -0,0 +1,110 @@ +import { createHash } from "node:crypto"; +import { Pool } from "pg"; + +export class VersionListController { + versionCache: Version[]; + private pool: Pool; + hashList: string[]; + constructor(pool: Pool) { + this.pool = pool; + this.versionCache = []; + this.hashList = ["md5", "sha1", "sha256"]; + this._refreshCache(); // 初始化缓存 + setInterval(this._refreshCache, 1000 * 60 * 60 * 2); // 2小时刷新缓存 + } + + async getVersionList() { + if (this.versionCache.length === 0) { + await this._refreshCache(); + } + console.log("use cache"); + return this.versionCache; + } + + private async _refreshCache() { + const arr: any[] = []; + const result = ( + await this.pool.query(` +SELECT mcversion."version", + mcversion."Type", + mcversion."Date", + array_agg(DISTINCT variation.arch) as arch, + variation."OSbuild" +FROM variation +LEFT JOIN mcversion ON mcversion.id = variation."MCVId" +GROUP BY mcversion."version", mcversion."Type", mcversion."Date", variation."OSbuild" +ORDER BY "Date" DESC +`) + ).rows; + result.forEach((row: VersionPool) => { + const url: string[] = []; + for (const arch of row.arch) { + url.push(`./mc/version/${row.version}/${arch}`); + arr.push({ + id: row.version, + type: row.Type, + BuildType: "UWP", + Arch: ["x64", "x86"], + url, + time: row.Date, + }); + } + }); + this.versionCache = arr; + } + + async getLibraries(id: string, arch: string) { + const archs = ["x64", "x86", "arm"]; + if ( + !archs.includes(arch) || + this.versionCache.filter((v) => v.id === id).length === 0 + ) { + return; + } + const r = ( + await this.pool.query( + ` +SELECT variation.id +FROM variation +LEFT JOIN mcversion ON mcversion.id = variation."MCVId" +WHERE mcversion."versionHash" = $1 + AND variation.arch = $2 +LIMIT 1 +`, + [createHash("sha256").update(id).digest(), arch] + ) + ).rows; // 获取版本id + + const data = ( + await this.pool.query( + ` +SELECT "filePathName"."pathName", + "filesHash"."size", + ${this.hashList.map(hash => `encode("filesHash"."${hash}", 'hex') as "${hash}"`).join(', ')} +FROM variation_files_data +LEFT JOIN files ON files.id = variation_files_data."filesId" +LEFT JOIN "filePathName" ON "filePathName".id = files."pathNameId" +LEFT JOIN "filesHash" ON "filesHash".id = files."hashId" +WHERE variation_files_data."variationId" = $1 +`, + [r[0].id] + ) + ).rows; + console.log(data); + return data; + } +} +interface VersionPool { + id: number; + version: string; + Type: string; + Date: string; + arch: string; +} + +interface Version { + id: string; + type: string; + url: string; + time: string; +} diff --git a/src/main.ts b/src/main.ts index e69de29..89a625c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -0,0 +1,10 @@ +import express from "express"; +import config from "./utils/config.js"; +import versionlistRouter from "./router/versionlist.router.js"; + +const app = express(); + +app.use('/mc',versionlistRouter) +app.listen(config.express.port,()=>{ + console.log(`server is running on port ${config.express.port}`); +}) \ No newline at end of file diff --git a/src/router/download.router.ts b/src/router/download.router.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/router/versionlist.router.ts b/src/router/versionlist.router.ts new file mode 100644 index 0000000..06e6896 --- /dev/null +++ b/src/router/versionlist.router.ts @@ -0,0 +1,23 @@ +import { Router, Request, Response } from "express"; +import { VersionListController } from "../controller/versionlist.controller.js"; +import pool from "../utils/pgsql.js"; + +const versionListController = new VersionListController(pool); +const router = Router(); + +router.get("/version_manifest", async (req: Request, res: Response) => { + const version = await versionListController.getVersionList(); + res.status(200).json({ version }); +}); + +router.get("/version/:id/:arch", async (req: Request, res: Response) => { + const version = req.params.id; + const arch = req.params.arch; + const libraries = await versionListController.getLibraries(version,arch); + if(!libraries){ + res.sendStatus(404); + return; + } + res.status(200).json({ libraries }); +}) +export default router; diff --git a/src/utils/config.ts b/src/utils/config.ts new file mode 100644 index 0000000..b57c28b --- /dev/null +++ b/src/utils/config.ts @@ -0,0 +1,22 @@ +import fs from "fs"; +interface Config { + database: { + host: string; + port: number; + user: string; + database: string; + password: string; + }, + express:{ + port:number; + logger:boolean; + } +} + +const config:Config = (() => { + if (!fs.existsSync(`./config.json`)) { + throw new Error(`config is not defined`); + } + return JSON.parse(fs.readFileSync(`./config.json`, "utf8")); +})(); +export default config; diff --git a/src/utils/pgsql.ts b/src/utils/pgsql.ts new file mode 100644 index 0000000..1db1bb9 --- /dev/null +++ b/src/utils/pgsql.ts @@ -0,0 +1,10 @@ +import { Pool } from "pg"; +import config from "./config.js"; +const pool = new Pool({ + host: config.database.host, + port: config.database.port, + user: config.database.user, + database: config.database.database, + password: config.database.password, +}); +export default pool; \ No newline at end of file