DownloadVersionService.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. const db = require("../plugin/DataBase/db");
  2. function normalizeClientType(value) {
  3. return String(value || "").trim().toLowerCase();
  4. }
  5. function parseVersionParts(version) {
  6. return String(version || "")
  7. .trim()
  8. .replace(/^[vV]/, "")
  9. .split(/[.+_-]/)
  10. .map(part => {
  11. const n = Number.parseInt(part, 10);
  12. return Number.isFinite(n) ? n : 0;
  13. });
  14. }
  15. function compareVersionName(a, b) {
  16. const left = parseVersionParts(a);
  17. const right = parseVersionParts(b);
  18. const len = Math.max(left.length, right.length);
  19. for (let i = 0; i < len; i++) {
  20. const diff = (left[i] || 0) - (right[i] || 0);
  21. if (diff !== 0) return diff > 0 ? 1 : -1;
  22. }
  23. return 0;
  24. }
  25. function isVersionNewer(remote, local) {
  26. const remoteCode = Number(remote.version_code);
  27. const localCode = Number(local.version_code);
  28. if (Number.isFinite(remoteCode) && remoteCode > 0 && Number.isFinite(localCode) && localCode > 0)
  29. return remoteCode > localCode;
  30. return compareVersionName(remote.version_name, local.version_name) > 0;
  31. }
  32. function parseOptionalNumber(value) {
  33. const n = Number(value);
  34. return Number.isFinite(n) && n > 0 ? n : 0;
  35. }
  36. function compareClientVersion(left, right) {
  37. const leftCode = parseOptionalNumber(left.version_code);
  38. const rightCode = parseOptionalNumber(right.version_code);
  39. if (leftCode > 0 && rightCode > 0) {
  40. if (leftCode === rightCode) return 0;
  41. return leftCode > rightCode ? 1 : -1;
  42. }
  43. return compareVersionName(left.version_name, right.version_name);
  44. }
  45. function hasRangeBoundary(boundary) {
  46. return Boolean(boundary.version_name) || parseOptionalNumber(boundary.version_code) > 0;
  47. }
  48. function versionMatchesRange(row, localVersion = {}) {
  49. const local = {
  50. version_name: localVersion.version_name,
  51. version_code: localVersion.version_code
  52. };
  53. const minVersion = {
  54. version_name: row.min_current_version,
  55. version_code: row.min_current_version_code
  56. };
  57. const maxVersion = {
  58. version_name: row.max_current_version,
  59. version_code: row.max_current_version_code
  60. };
  61. if (hasRangeBoundary(minVersion) && compareClientVersion(local, minVersion) < 0)
  62. return false;
  63. if (hasRangeBoundary(maxVersion) && compareClientVersion(local, maxVersion) > 0)
  64. return false;
  65. return true;
  66. }
  67. function toUniAppPayload(row) {
  68. return {
  69. describe: row.release_notes_html || row.release_notes || "",
  70. edition_url: row.download_url,
  71. edition_force: Number(row.force_update) || 0,
  72. package_type: Number(row.package_type) || 0,
  73. edition_issue: Number(row.is_active) === 0 ? 0 : 1,
  74. edition_number: Number(row.version_code) || 0,
  75. edition_name: row.version_name,
  76. edition_silence: Number(row.silent_update) || 0,
  77. log: row.release_notes || ""
  78. };
  79. }
  80. function toPublicPayload(row, localVersion = {}) {
  81. if (!row) {
  82. return {
  83. has_update: false,
  84. update_available: false
  85. };
  86. }
  87. const remote = {
  88. version_name: row.version_name,
  89. version_code: Number(row.version_code) || 0
  90. };
  91. const local = {
  92. version_name: localVersion.version_name,
  93. version_code: localVersion.version_code
  94. };
  95. const hasUpdate = isVersionNewer(remote, local);
  96. const payload = {
  97. has_update: hasUpdate,
  98. update_available: hasUpdate,
  99. client_type: row.client_type,
  100. title: row.title || "",
  101. version: row.version_name,
  102. version_name: row.version_name,
  103. version_code: Number(row.version_code) || 0,
  104. download_url: row.download_url,
  105. sha256: row.sha256 || "",
  106. file_size: Number(row.file_size) || 0,
  107. package_type: Number(row.package_type) || 0,
  108. force_update: Number(row.force_update) || 0,
  109. silent_update: Number(row.silent_update) || 0,
  110. release_notes: row.release_notes || "",
  111. release_notes_html: row.release_notes_html || "",
  112. min_supported_version: row.min_supported_version || "",
  113. min_current_version: row.min_current_version || "",
  114. min_current_version_code: Number(row.min_current_version_code) || 0,
  115. max_current_version: row.max_current_version || "",
  116. max_current_version_code: Number(row.max_current_version_code) || 0
  117. };
  118. if (normalizeClientType(row.client_type) === "android") {
  119. Object.assign(payload, toUniAppPayload(row), {
  120. uniapp: toUniAppPayload(row)
  121. });
  122. }
  123. return payload;
  124. }
  125. async function getLatestVersion(clientType) {
  126. return await getMatchedVersion(clientType, {});
  127. }
  128. async function getMatchedVersion(clientType, localVersion = {}) {
  129. const normalized = normalizeClientType(clientType);
  130. const rows = await db.query(`
  131. SELECT id, client_type, version_name, version_code, title, download_url,
  132. sha256, file_size, package_type, force_update, silent_update,
  133. is_active, release_notes, release_notes_html, min_supported_version,
  134. min_current_version, min_current_version_code,
  135. max_current_version, max_current_version_code,
  136. sort_order, created_at, updated_at
  137. FROM download_version
  138. WHERE client_type = ? AND is_active = 1
  139. ORDER BY version_code DESC, sort_order ASC, id DESC
  140. `, [normalized]);
  141. if (!rows || rows.length === 0) return null;
  142. const updateRows = rows.filter(row => isVersionNewer(row, localVersion));
  143. return updateRows.find(row => versionMatchesRange(row, localVersion)) || updateRows[0] || null;
  144. }
  145. async function listVersions(clientType = "") {
  146. const normalized = normalizeClientType(clientType);
  147. if (normalized) {
  148. return await db.query(`
  149. SELECT id, client_type, version_name, version_code, title, download_url,
  150. sha256, file_size, package_type, force_update, silent_update,
  151. is_active, release_notes, release_notes_html, min_supported_version,
  152. min_current_version, min_current_version_code,
  153. max_current_version, max_current_version_code,
  154. sort_order, created_at, updated_at
  155. FROM download_version
  156. WHERE client_type = ?
  157. ORDER BY client_type ASC, version_code DESC, sort_order ASC, id DESC
  158. `, [normalized]);
  159. }
  160. return await db.query(`
  161. SELECT id, client_type, version_name, version_code, title, download_url,
  162. sha256, file_size, package_type, force_update, silent_update,
  163. is_active, release_notes, release_notes_html, min_supported_version,
  164. min_current_version, min_current_version_code,
  165. max_current_version, max_current_version_code,
  166. sort_order, created_at, updated_at
  167. FROM download_version
  168. ORDER BY client_type ASC, version_code DESC, sort_order ASC, id DESC
  169. `);
  170. }
  171. async function saveVersion(payload) {
  172. const now = Date.now();
  173. const data = {
  174. client_type: normalizeClientType(payload.client_type),
  175. version_name: String(payload.version_name || payload.version || "").trim(),
  176. version_code: Number(payload.version_code) || 0,
  177. title: String(payload.title || "").trim(),
  178. download_url: String(payload.download_url || "").trim(),
  179. sha256: String(payload.sha256 || "").trim().toLowerCase(),
  180. file_size: Number(payload.file_size) || 0,
  181. package_type: Number(payload.package_type) || 0,
  182. force_update: Number(payload.force_update) === 1 ? 1 : 0,
  183. silent_update: Number(payload.silent_update) === 1 ? 1 : 0,
  184. is_active: Number(payload.is_active) === 0 ? 0 : 1,
  185. release_notes: payload.release_notes || "",
  186. release_notes_html: payload.release_notes_html || "",
  187. min_supported_version: String(payload.min_supported_version || "").trim(),
  188. min_current_version: String(payload.min_current_version || "").trim(),
  189. min_current_version_code: Number(payload.min_current_version_code) || 0,
  190. max_current_version: String(payload.max_current_version || "").trim(),
  191. max_current_version_code: Number(payload.max_current_version_code) || 0,
  192. sort_order: Number(payload.sort_order) || 0
  193. };
  194. if (payload.id) {
  195. const result = await db.query(`
  196. UPDATE download_version SET
  197. client_type = ?, version_name = ?, version_code = ?, title = ?,
  198. download_url = ?, sha256 = ?, file_size = ?, package_type = ?,
  199. force_update = ?, silent_update = ?, is_active = ?,
  200. release_notes = ?, release_notes_html = ?, min_supported_version = ?,
  201. min_current_version = ?, min_current_version_code = ?,
  202. max_current_version = ?, max_current_version_code = ?,
  203. sort_order = ?, updated_at = ?
  204. WHERE id = ?
  205. `, [
  206. data.client_type, data.version_name, data.version_code, data.title,
  207. data.download_url, data.sha256, data.file_size, data.package_type,
  208. data.force_update, data.silent_update, data.is_active,
  209. data.release_notes, data.release_notes_html, data.min_supported_version,
  210. data.min_current_version, data.min_current_version_code,
  211. data.max_current_version, data.max_current_version_code,
  212. data.sort_order, now, payload.id
  213. ]);
  214. return { id: payload.id, result };
  215. }
  216. const result = await db.query(`
  217. INSERT INTO download_version
  218. (client_type, version_name, version_code, title, download_url,
  219. sha256, file_size, package_type, force_update, silent_update,
  220. is_active, release_notes, release_notes_html, min_supported_version,
  221. min_current_version, min_current_version_code,
  222. max_current_version, max_current_version_code,
  223. sort_order, created_at, updated_at)
  224. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  225. `, [
  226. data.client_type, data.version_name, data.version_code, data.title,
  227. data.download_url, data.sha256, data.file_size, data.package_type,
  228. data.force_update, data.silent_update, data.is_active,
  229. data.release_notes, data.release_notes_html, data.min_supported_version,
  230. data.min_current_version, data.min_current_version_code,
  231. data.max_current_version, data.max_current_version_code,
  232. data.sort_order, now, now
  233. ]);
  234. return { id: result && result.insertId, result };
  235. }
  236. async function deleteVersion(id) {
  237. return await db.query("DELETE FROM download_version WHERE id = ?", [id]);
  238. }
  239. module.exports = {
  240. getLatestVersion,
  241. getMatchedVersion,
  242. listVersions,
  243. saveVersion,
  244. deleteVersion,
  245. toPublicPayload,
  246. toUniAppPayload,
  247. normalizeClientType
  248. };