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,166 +0,0 @@
---
title: Vue Plugin Best Practices
impact: MEDIUM
impactDescription: Incorrect plugin structure or injection key strategy causes install failures, collisions, and unsafe APIs
type: best-practice
tags: [vue3, plugins, provide-inject, typescript, dependency-injection]
---
# Vue Plugin Best Practices
**Impact: MEDIUM** - Vue plugins should follow the `app.use()` contract, expose explicit capabilities, and use collision-safe injection keys. This keeps plugin setup predictable and composable across large apps.
## Task List
- Export plugins as an object with `install()` or as an install function
- Use the `app` instance in `install()` to register components/directives/provides
- Type plugin APIs with `Plugin` (and options tuple types when needed)
- Use symbol keys (prefer `InjectionKey<T>`) for `provide/inject` in plugins
- Add a small typed composable wrapper for required injections to fail fast
## Structure Plugins for `app.use()`
A Vue plugin must be either:
- An object with `install(app, options?)`
- A function with the same signature
**BAD:**
```ts
const notAPlugin = {
doSomething() {}
}
app.use(notAPlugin)
```
**GOOD:**
```ts
import type { App } from 'vue'
interface PluginOptions {
prefix?: string
debug?: boolean
}
const myPlugin = {
install(app: App, options: PluginOptions = {}) {
const { prefix = 'my', debug = false } = options
if (debug) {
console.log('Installing myPlugin with prefix:', prefix)
}
app.provide('myPlugin', { prefix })
}
}
app.use(myPlugin, { prefix: 'custom', debug: true })
```
**GOOD:**
```ts
import type { App } from 'vue'
function simplePlugin(app: App, options?: { message: string }) {
app.config.globalProperties.$greet = () => options?.message ?? 'Hello!'
}
app.use(simplePlugin, { message: 'Welcome!' })
```
## Register Capabilities Explicitly in `install()`
Inside `install()`, wire behavior through Vue application APIs:
- `app.component()` for global components
- `app.directive()` for global directives
- `app.provide()` for injectable services and config
- `app.config.globalProperties` for optional global helpers (sparingly)
**BAD:**
```ts
const uselessPlugin = {
install(app, options) {
const service = createService(options)
}
}
```
**GOOD:**
```ts
const usefulPlugin = {
install(app, options) {
const service = createService(options)
app.provide(serviceKey, service)
}
}
```
## Type Plugin Contracts
Use Vue's `Plugin` type to keep install signatures and options type-safe.
```ts
import type { App, Plugin } from 'vue'
interface MyOptions {
apiKey: string
}
const myPlugin: Plugin<[MyOptions]> = {
install(app: App, options: MyOptions) {
app.provide(apiKeyKey, options.apiKey)
}
}
```
## Use Symbol Injection Keys in Plugins
String keys can collide (`'http'`, `'config'`, `'i18n'`). Use symbol keys with `InjectionKey<T>` so injections are unique and typed.
**BAD:**
```ts
export default {
install(app) {
app.provide('http', axios)
app.provide('config', appConfig)
}
}
```
**GOOD:**
```ts
import type { InjectionKey } from 'vue'
import type { AxiosInstance } from 'axios'
interface AppConfig {
apiUrl: string
timeout: number
}
export const httpKey: InjectionKey<AxiosInstance> = Symbol('http')
export const configKey: InjectionKey<AppConfig> = Symbol('appConfig')
export default {
install(app) {
app.provide(httpKey, axios)
app.provide(configKey, { apiUrl: '/api', timeout: 5000 })
}
}
```
## Provide Required Injection Helpers
Wrap required injections in composables that throw clear setup errors.
```ts
import { inject } from 'vue'
import { authKey, type AuthService } from '@/injection-keys'
export function useAuth(): AuthService {
const auth = inject(authKey)
if (!auth) {
throw new Error('Auth plugin not installed. Did you forget app.use(authPlugin)?')
}
return auth
}
```