feat(deploy): production deployment setup and fixes

- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma
  binaryTargets for openssl 3.0
- nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal
  cron, http->https redirect
- prisma: add origin_goods.delisted migration, sync missing schema
  (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz
- api: CORS wildcard reflection, helmet CORP cross-origin, price
  backfill in persistProductDetail, categoryIcon ancestor fallback,
  mediaByColor per-color gallery in public goods detail
- admin: /admin base path (vite + router)
- import-data.mjs: udt_name casting, serial sequence advance fix
This commit is contained in:
yeuimu
2026-08-26 14:23:09 +08:00
parent be0b90e68f
commit 6c61a4e871
982 changed files with 74156 additions and 179393 deletions
@@ -1,96 +0,0 @@
---
title: Avoid Comparing Reactive Objects with === Operator
impact: HIGH
impactDescription: Reactive proxies have different identity than original objects - comparison bugs are silent and hard to debug
type: gotcha
tags: [vue3, reactivity, proxy, comparison, debugging, identity]
---
# Avoid Comparing Reactive Objects with === Operator
**Impact: HIGH** - Vue's `reactive()` returns a Proxy wrapper that has a different identity than the original object. Using `===` to compare reactive objects can lead to silent bugs where comparisons unexpectedly return `false`.
When you wrap an object with `reactive()`, the returned proxy is NOT equal to the original object. Additionally, accessing nested objects from a reactive object returns new proxy wrappers each time, which can cause identity comparison issues.
## Task Checklist
- [ ] Never compare reactive object instances with `===` directly
- [ ] Use unique identifiers (ID, UUID) for object comparison instead
- [ ] Use `toRaw()` on both sides when identity comparison is absolutely necessary
- [ ] Consider using primitive identifiers from database records for comparison
**Incorrect:**
```javascript
import { reactive } from 'vue'
const original = { id: 1, name: 'Item' }
const state = reactive(original)
// BUG: Always returns false - proxy !== original
if (state === original) {
console.log('Same object') // Never executes
}
// BUG: Nested object comparison fails
const items = reactive([{ id: 1 }, { id: 2 }])
const item = items[0]
// Later...
if (items[0] === item) {
// May or may not work depending on Vue's proxy caching
}
// BUG: Comparing items from different reactive sources
const listA = reactive([{ id: 1 }])
const listB = reactive([{ id: 1 }])
if (listA[0] === listB[0]) {
// Never true, even though they represent the same data
}
```
**Correct:**
```javascript
import { reactive, toRaw } from 'vue'
const original = { id: 1, name: 'Item' }
const state = reactive(original)
// CORRECT: Use toRaw() for identity comparison
if (toRaw(state) === original) {
console.log('Same underlying object') // Works!
}
// BEST: Use unique identifiers instead
const items = reactive([
{ id: 'uuid-1', name: 'Item 1' },
{ id: 'uuid-2', name: 'Item 2' }
])
function findItem(targetId) {
return items.find(item => item.id === targetId)
}
function isSelected(item) {
return selectedId.value === item.id // Compare IDs, not objects
}
// CORRECT: For Set/Map operations, use primitive keys
const selectedIds = reactive(new Set())
selectedIds.add(item.id) // Use ID, not object
selectedIds.has(item.id) // Check by ID
```
```javascript
// When you must compare objects, use toRaw on both sides
import { toRaw, isReactive } from 'vue'
function areEqual(a, b) {
const rawA = isReactive(a) ? toRaw(a) : a
const rawB = isReactive(b) ? toRaw(b) : b
return rawA === rawB
}
```
## Reference
- [Vue.js Reactivity in Depth](https://vuejs.org/guide/extras/reactivity-in-depth.html)
- [Vue.js toRaw() API](https://vuejs.org/api/reactivity-advanced.html#toraw)