迁移数据库从 SQLite 到 PostgreSQL
- 更新 deno.json 添加 postgres 依赖 - 重构 db/index.ts 使用 PostgreSQL 连接和适配器 - 更新所有路由文件支持异步数据库操作 - 将 SQLite 语法转换为 PostgreSQL 语法 - 添加数据库迁移文档和 schema 文件
This commit is contained in:
137
MIGRATION.md
Normal file
137
MIGRATION.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# SQLite 到 PostgreSQL 迁移指南
|
||||
|
||||
本文档说明如何将项目从 SQLite 迁移到 PostgreSQL。
|
||||
|
||||
## 主要变更
|
||||
|
||||
### 1. 依赖更新
|
||||
- 添加了 `postgres` 库用于 PostgreSQL 连接
|
||||
- 移除了 `node:sqlite` 依赖
|
||||
|
||||
### 2. 数据库连接
|
||||
数据库连接现在通过环境变量配置,支持以下方式:
|
||||
|
||||
**方式一:使用 DATABASE_URL**
|
||||
```bash
|
||||
export DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
|
||||
```
|
||||
|
||||
**方式二:使用独立的环境变量**
|
||||
```bash
|
||||
export DB_USER=postgres
|
||||
export DB_PASSWORD=postgres
|
||||
export DB_HOST=localhost
|
||||
export DB_PORT=5432
|
||||
export DB_NAME=media
|
||||
```
|
||||
|
||||
### 3. SQL 语法变更
|
||||
|
||||
#### 占位符
|
||||
- **SQLite**: 使用 `?` 作为占位符
|
||||
- **PostgreSQL**: 使用 `$1, $2, $3...` 作为占位符
|
||||
- **解决方案**: 代码中已创建适配器自动转换
|
||||
|
||||
#### 日期时间函数
|
||||
- **SQLite**: `datetime('now')`
|
||||
- **PostgreSQL**: `NOW()` 或 `CURRENT_TIMESTAMP`
|
||||
- **已更新**: 所有 `datetime('now')` 已替换为 `NOW()`
|
||||
|
||||
#### 获取插入的 ID
|
||||
- **SQLite**: `result.lastInsertRowid`
|
||||
- **PostgreSQL**: 使用 `RETURNING id` 子句
|
||||
- **已更新**: INSERT 语句已添加 `RETURNING id`
|
||||
|
||||
### 4. 数据库 Schema
|
||||
|
||||
需要创建以下表结构:
|
||||
|
||||
#### media 表
|
||||
```sql
|
||||
CREATE TABLE media (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
rating INTEGER,
|
||||
notes TEXT,
|
||||
platform VARCHAR(100),
|
||||
date DATE,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
#### users 表
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
## 迁移步骤
|
||||
|
||||
### 1. 安装 PostgreSQL
|
||||
确保已安装并运行 PostgreSQL 数据库。
|
||||
|
||||
### 2. 创建数据库
|
||||
```bash
|
||||
createdb media
|
||||
# 或使用 psql
|
||||
psql -U postgres
|
||||
CREATE DATABASE media;
|
||||
```
|
||||
|
||||
### 3. 创建表结构
|
||||
使用上面提供的 SQL 语句创建表。
|
||||
|
||||
### 4. 迁移数据(可选)
|
||||
如果需要从 SQLite 迁移现有数据,可以使用以下方法:
|
||||
|
||||
```bash
|
||||
# 导出 SQLite 数据
|
||||
sqlite3 media.db .dump > data.sql
|
||||
|
||||
# 手动转换并导入到 PostgreSQL
|
||||
# 注意:需要修改 SQL 语法以兼容 PostgreSQL
|
||||
```
|
||||
|
||||
### 5. 配置环境变量
|
||||
设置数据库连接信息(见上面的环境变量配置)。
|
||||
|
||||
### 6. 测试连接
|
||||
运行应用并测试数据库连接是否正常。
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据类型差异**:
|
||||
- SQLite 的 `INTEGER` 对应 PostgreSQL 的 `INTEGER` 或 `SERIAL`
|
||||
- SQLite 的 `TEXT` 对应 PostgreSQL 的 `VARCHAR` 或 `TEXT`
|
||||
- SQLite 的 `REAL` 对应 PostgreSQL 的 `REAL` 或 `DOUBLE PRECISION`
|
||||
|
||||
2. **事务处理**:
|
||||
- PostgreSQL 支持更完善的事务和并发控制
|
||||
- 代码中的适配器已处理基本的异步操作
|
||||
|
||||
3. **性能优化**:
|
||||
- PostgreSQL 支持索引优化
|
||||
- 建议为常用查询字段添加索引:
|
||||
```sql
|
||||
CREATE INDEX idx_media_type ON media(type);
|
||||
CREATE INDEX idx_media_rating ON media(rating);
|
||||
CREATE INDEX idx_media_date ON media(date);
|
||||
```
|
||||
|
||||
4. **连接池**:
|
||||
- `postgres` 库默认使用连接池
|
||||
- 可以通过配置选项调整连接池大小
|
||||
|
||||
## 回滚方案
|
||||
|
||||
如果需要回滚到 SQLite:
|
||||
1. 恢复 `db/index.ts` 中的 SQLite 连接代码
|
||||
2. 恢复所有 SQL 查询中的 `datetime('now')`
|
||||
3. 移除 INSERT 语句中的 `RETURNING id`
|
||||
4. 更新 `deno.json` 移除 postgres 依赖
|
||||
94
db/index.ts
94
db/index.ts
@@ -4,13 +4,93 @@
|
||||
* @LastEditTime: 2025-06-13 14:23:03
|
||||
* @FilePath: /my-score/honoback/db/index.ts
|
||||
*/
|
||||
import { DatabaseSync } from "node:sqlite"
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import postgres from "postgres";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
// 从环境变量获取数据库连接信息
|
||||
const DATABASE_URL =
|
||||
Deno.env.get("DATABASE_URL") ||
|
||||
`postgresql://${Deno.env.get("DB_USER") || "postgres"}:${
|
||||
Deno.env.get("DB_PASSWORD") || "postgres"
|
||||
}@${Deno.env.get("DB_HOST") || "localhost"}:${
|
||||
Deno.env.get("DB_PORT") || "5432"
|
||||
}/${Deno.env.get("DB_NAME") || "media"}`;
|
||||
|
||||
// 初始化数据库连接
|
||||
const db = new DatabaseSync(join(__dirname, 'media.db'))
|
||||
// 初始化 PostgreSQL 连接
|
||||
const sql = postgres(DATABASE_URL);
|
||||
|
||||
export { db }
|
||||
// 将 SQLite 的 ? 占位符转换为 PostgreSQL 的 $1, $2, $3...
|
||||
function convertQuery(query: string, params: any[]): [string, any[]] {
|
||||
let pgQuery = query;
|
||||
let paramIndex = 1;
|
||||
const pgParams: any[] = [];
|
||||
|
||||
// 替换 ? 为 $1, $2, $3...
|
||||
pgQuery = pgQuery.replace(/\?/g, () => {
|
||||
if (paramIndex <= params.length) {
|
||||
pgParams.push(params[paramIndex - 1]);
|
||||
}
|
||||
return `$${paramIndex++}`;
|
||||
});
|
||||
|
||||
return [pgQuery, pgParams];
|
||||
}
|
||||
|
||||
// 创建一个兼容的数据库接口,模拟 SQLite 的 prepare 方法
|
||||
class DatabaseAdapter {
|
||||
private sql: ReturnType<typeof postgres>;
|
||||
|
||||
constructor(sql: ReturnType<typeof postgres>) {
|
||||
this.sql = sql;
|
||||
}
|
||||
|
||||
prepare(query: string) {
|
||||
return {
|
||||
// all() 方法用于查询多条记录
|
||||
all: async (...params: any[]) => {
|
||||
const [pgQuery, pgParams] = convertQuery(query, params);
|
||||
const result = await this.sql.unsafe(pgQuery, pgParams);
|
||||
return result;
|
||||
},
|
||||
// get() 方法用于查询单条记录
|
||||
get: async (...params: any[]) => {
|
||||
const [pgQuery, pgParams] = convertQuery(query, params);
|
||||
const result = await this.sql.unsafe(pgQuery, pgParams);
|
||||
return result[0] || null;
|
||||
},
|
||||
// run() 方法用于执行 INSERT/UPDATE/DELETE
|
||||
run: async (...params: any[]) => {
|
||||
const [pgQuery, pgParams] = convertQuery(query, params);
|
||||
const result = await this.sql.unsafe(pgQuery, pgParams);
|
||||
|
||||
// 如果是 INSERT 语句,返回类似 SQLite 的 lastInsertRowid
|
||||
if (query.trim().toUpperCase().startsWith("INSERT")) {
|
||||
// PostgreSQL 使用 RETURNING 子句获取插入的 ID
|
||||
if (query.includes("RETURNING")) {
|
||||
return {
|
||||
lastInsertRowid: result[0]?.id || null,
|
||||
changes: result.length,
|
||||
};
|
||||
} else {
|
||||
// 如果没有 RETURNING,需要查询最后插入的 ID
|
||||
// 注意:这需要表有 id 列且是 SERIAL 类型
|
||||
const lastIdResult = await this.sql.unsafe(
|
||||
"SELECT lastval() as id"
|
||||
);
|
||||
return {
|
||||
lastInsertRowid: lastIdResult[0]?.id || null,
|
||||
changes: result.length || 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
changes: result.length || 0,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const db = new DatabaseAdapter(sql);
|
||||
|
||||
export { db, sql };
|
||||
|
||||
29
db/schema.sql
Normal file
29
db/schema.sql
Normal file
@@ -0,0 +1,29 @@
|
||||
-- PostgreSQL 数据库 Schema
|
||||
-- 用于创建 media 和 users 表
|
||||
|
||||
-- 创建 media 表
|
||||
CREATE TABLE IF NOT EXISTS media (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
rating INTEGER,
|
||||
notes TEXT,
|
||||
platform VARCHAR(100),
|
||||
date DATE,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 创建 users 表
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 创建索引以优化查询性能
|
||||
CREATE INDEX IF NOT EXISTS idx_media_type ON media(type);
|
||||
CREATE INDEX IF NOT EXISTS idx_media_rating ON media(rating);
|
||||
CREATE INDEX IF NOT EXISTS idx_media_date ON media(date);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"imports": {
|
||||
"hono": "jsr:@hono/hono@^4.7.11"
|
||||
"hono": "jsr:@hono/hono@^4.7.11",
|
||||
"postgres": "npm:postgres@^3.4.3"
|
||||
},
|
||||
"tasks": {
|
||||
"start": "deno run --allow-net --allow-read --allow-write --allow-env --watch main.ts"
|
||||
|
||||
16
deploy.sh
16
deploy.sh
@@ -2,7 +2,7 @@
|
||||
###
|
||||
# @Date: 2025-06-13 16:11:38
|
||||
# @LastEditors: 陈子健
|
||||
# @LastEditTime: 2025-06-23 16:56:58
|
||||
# @LastEditTime: 2025-06-23 17:24:22
|
||||
# @FilePath: /my-score/honoback/deploy.sh
|
||||
###
|
||||
|
||||
@@ -19,11 +19,15 @@ deno cache main.ts
|
||||
echo "Creating remote directory..."
|
||||
ssh $USER@$SERVER "mkdir -p $REMOTE_DIR"
|
||||
|
||||
# 同步文件到服务器
|
||||
echo "Syncing files to server..."
|
||||
rsync -avz --exclude 'db/media.db' \
|
||||
--exclude '.git' \
|
||||
./ $USER@$SERVER:$REMOTE_DIR/
|
||||
|
||||
# 1. 远程备份数据库
|
||||
ssh $USER@$SERVER "cp $REMOTE_DIR/db/media.db $REMOTE_DIR/db/media.db.bak 2>/dev/null || true"
|
||||
|
||||
# 2. 同步代码到服务器(只同步白名单内容,且排除 media.db)
|
||||
rsync -avz --exclude='.git/' --exclude='node_modules/' --exclude='db/media.db' \
|
||||
--include='*.ts' --include='*.json' --include='*.sh' --include='*.service' \
|
||||
--include='db/' --include='db/*.ts' --include='routes/' --include='routes/*.ts' \
|
||||
--exclude='*' ./ $USER@$SERVER:$REMOTE_DIR
|
||||
|
||||
# 在服务器上安装依赖并重启服务
|
||||
echo "Installing and starting systemd service..."
|
||||
|
||||
198
routes/media.ts
198
routes/media.ts
@@ -1,143 +1,177 @@
|
||||
import { Hono } from 'hono'
|
||||
import { db } from '../db/index.ts'
|
||||
import type { JwtVariables } from 'hono/jwt'
|
||||
import { authMiddleware } from '../auth.ts'
|
||||
import { Hono } from "hono";
|
||||
import { db } from "../db/index.ts";
|
||||
import type { JwtVariables } from "hono/jwt";
|
||||
import { authMiddleware } from "../auth.ts";
|
||||
|
||||
const media = new Hono<{ Variables: JwtVariables }>()
|
||||
const media = new Hono<{ Variables: JwtVariables }>();
|
||||
|
||||
media.use('/*', authMiddleware)
|
||||
// 查询满分(10分)作品,不需要JWT验证
|
||||
media.get("/perfect", async (c) => {
|
||||
try {
|
||||
const perfectList = await db
|
||||
.prepare("SELECT * FROM media WHERE rating = 10")
|
||||
.all();
|
||||
return c.json({
|
||||
code: 0,
|
||||
data: perfectList,
|
||||
message: "Success",
|
||||
});
|
||||
} catch (error: any) {
|
||||
return c.json({ code: 1, data: {}, message: error.message }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
media.use("/*", authMiddleware);
|
||||
|
||||
// 获取所有媒体记录
|
||||
media.get('/list', (c) => {
|
||||
media.get("/list", async (c) => {
|
||||
try {
|
||||
const mediaList = db.prepare('SELECT * FROM media').all()
|
||||
const mediaList = await db.prepare("SELECT * FROM media").all();
|
||||
return c.json({
|
||||
code: 0,
|
||||
data: mediaList,
|
||||
message: 'Success'
|
||||
})
|
||||
message: "Success",
|
||||
});
|
||||
} catch (error: any) {
|
||||
return c.json({ code: 1, data: {}, message: error.message }, 500)
|
||||
return c.json({ code: 1, data: {}, message: error.message }, 500);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 创建新的媒体记录
|
||||
media.post('/create', async (c) => {
|
||||
media.post("/create", async (c) => {
|
||||
try {
|
||||
const data = await c.req.json()
|
||||
const { title, type, rating, notes, platform, date } = data
|
||||
const data = await c.req.json();
|
||||
const { title, type, rating, notes, platform, date } = data;
|
||||
|
||||
const result = db.prepare(`
|
||||
const result = await db
|
||||
.prepare(
|
||||
`
|
||||
INSERT INTO media (title, type, rating, notes, platform, date, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
||||
`).run(title, type, rating, notes, platform, date)
|
||||
VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
RETURNING id
|
||||
`
|
||||
)
|
||||
.run(title, type, rating, notes, platform, date);
|
||||
|
||||
const newMedia = db.prepare('SELECT * FROM media WHERE id = ?').get(result.lastInsertRowid)
|
||||
const newMedia = await db
|
||||
.prepare("SELECT * FROM media WHERE id = ?")
|
||||
.get(result.lastInsertRowid);
|
||||
|
||||
return c.json({
|
||||
code: 0,
|
||||
data: newMedia,
|
||||
message: 'Created successfully'
|
||||
}, 201)
|
||||
return c.json(
|
||||
{
|
||||
code: 0,
|
||||
data: newMedia,
|
||||
message: "Created successfully",
|
||||
},
|
||||
201
|
||||
);
|
||||
} catch (error: any) {
|
||||
return c.json({ code: 2, data: {}, message: error.message }, 500)
|
||||
return c.json({ code: 2, data: {}, message: error.message }, 500);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 更新媒体记录
|
||||
media.put('/updateById/:id', async (c) => {
|
||||
media.put("/updateById/:id", async (c) => {
|
||||
try {
|
||||
const id = c.req.param('id')
|
||||
const data = await c.req.json()
|
||||
const { title, type, rating, notes, platform, date } = data
|
||||
const id = c.req.param("id");
|
||||
const data = await c.req.json();
|
||||
const { title, type, rating, notes, platform, date } = data;
|
||||
|
||||
db.prepare(`
|
||||
await db
|
||||
.prepare(
|
||||
`
|
||||
UPDATE media
|
||||
SET title = ?, type = ?, rating = ?, notes = ?, platform = ?, date = ?, updated_at = datetime('now')
|
||||
SET title = ?, type = ?, rating = ?, notes = ?, platform = ?, date = ?, updated_at = NOW()
|
||||
WHERE id = ?
|
||||
`).run(title, type, rating, notes, platform, date, id)
|
||||
`
|
||||
)
|
||||
.run(title, type, rating, notes, platform, date, id);
|
||||
|
||||
const updatedMedia = db.prepare('SELECT * FROM media WHERE id = ?').get(id)
|
||||
const updatedMedia = await db
|
||||
.prepare("SELECT * FROM media WHERE id = ?")
|
||||
.get(id);
|
||||
|
||||
if (!updatedMedia) {
|
||||
return c.json({ code: 1, data: {}, message: 'Media not found' }, 404)
|
||||
return c.json({ code: 1, data: {}, message: "Media not found" }, 404);
|
||||
}
|
||||
|
||||
return c.json({
|
||||
code: 0,
|
||||
data: updatedMedia,
|
||||
message: 'Updated successfully'
|
||||
})
|
||||
message: "Updated successfully",
|
||||
});
|
||||
} catch (error: any) {
|
||||
return c.json({ code: 2, data: {}, message: error.message }, 500)
|
||||
return c.json({ code: 2, data: {}, message: error.message }, 500);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 删除媒体记录
|
||||
media.delete('/deleteById/:id', async (c) => {
|
||||
media.delete("/deleteById/:id", async (c) => {
|
||||
try {
|
||||
const id = c.req.param('id')
|
||||
db.prepare('DELETE FROM media WHERE id = ?').run(id)
|
||||
return c.json({ code: 0, data: {}, message: 'Deleted successfully' })
|
||||
const id = c.req.param("id");
|
||||
await db.prepare("DELETE FROM media WHERE id = ?").run(id);
|
||||
return c.json({ code: 0, data: {}, message: "Deleted successfully" });
|
||||
} catch (error: any) {
|
||||
return c.json({ code: 2, data: {}, message: error.message }, 500)
|
||||
return c.json({ code: 2, data: {}, message: error.message }, 500);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 分页查询媒体记录
|
||||
media.get('/page', async (c) => {
|
||||
media.get("/page", async (c) => {
|
||||
try {
|
||||
const type = c.req.query('type')
|
||||
const currentPage = parseInt(c.req.query('currentPage') || '1')
|
||||
const pageSize = parseInt(c.req.query('pageSize') || '10')
|
||||
const title = c.req.query('title') || ''
|
||||
const startDate = c.req.query('startDate')
|
||||
const endDate = c.req.query('endDate')
|
||||
const sortBy = c.req.query('sortBy') || 'date'
|
||||
const sortType = c.req.query('sortType') || 'desc'
|
||||
const type = c.req.query("type");
|
||||
const currentPage = parseInt(c.req.query("currentPage") || "1");
|
||||
const pageSize = parseInt(c.req.query("pageSize") || "10");
|
||||
const title = c.req.query("title") || "";
|
||||
const startDate = c.req.query("startDate");
|
||||
const endDate = c.req.query("endDate");
|
||||
const sortBy = c.req.query("sortBy") || "date";
|
||||
const sortType = c.req.query("sortType") || "desc";
|
||||
|
||||
if (!type) {
|
||||
return c.json({ code: 1, data: {}, message: 'Type is required' }, 400)
|
||||
return c.json({ code: 1, data: {}, message: "Type is required" }, 400);
|
||||
}
|
||||
|
||||
let query = 'SELECT * FROM media WHERE type = ?'
|
||||
const params = [type]
|
||||
let query = "SELECT * FROM media WHERE type = ?";
|
||||
const params = [type];
|
||||
|
||||
if (title) {
|
||||
query += ' AND title LIKE ?'
|
||||
params.push(`%${title}%`)
|
||||
query += " AND title LIKE ?";
|
||||
params.push(`%${title}%`);
|
||||
}
|
||||
if (startDate) {
|
||||
query += ' AND date >= ?'
|
||||
params.push(startDate)
|
||||
query += " AND date >= ?";
|
||||
params.push(startDate);
|
||||
}
|
||||
if (endDate) {
|
||||
query += ' AND date <= ?'
|
||||
params.push(endDate)
|
||||
query += " AND date <= ?";
|
||||
params.push(endDate);
|
||||
}
|
||||
|
||||
// 添加排序
|
||||
if (sortBy === 'date') {
|
||||
query += ` ORDER BY date ${sortType.toUpperCase()}`
|
||||
} else if (sortBy === 'score') {
|
||||
query += ` ORDER BY rating ${sortType.toUpperCase()}`
|
||||
if (sortBy === "date") {
|
||||
query += ` ORDER BY date ${sortType.toUpperCase()}`;
|
||||
} else if (sortBy === "score") {
|
||||
query += ` ORDER BY rating ${sortType.toUpperCase()}`;
|
||||
}
|
||||
|
||||
// 添加分页
|
||||
const offset = (currentPage - 1) * pageSize
|
||||
query += ' LIMIT ? OFFSET ?'
|
||||
params.push(pageSize.toString(), offset.toString())
|
||||
const offset = (currentPage - 1) * pageSize;
|
||||
query += " LIMIT ? OFFSET ?";
|
||||
params.push(pageSize.toString(), offset.toString());
|
||||
|
||||
// 获取总数
|
||||
const countQuery = query
|
||||
.replace('SELECT *', 'SELECT COUNT(*) as total')
|
||||
.replace(/ORDER BY.*$/, '') // 移除 ORDER BY 子句
|
||||
.replace(/LIMIT.*$/, '') // 移除 LIMIT 和 OFFSET 子句
|
||||
const totalResult = db.prepare(countQuery).get(...params.slice(0, -2))
|
||||
const total = totalResult?.total || 0
|
||||
.replace("SELECT *", "SELECT COUNT(*) as total")
|
||||
.replace(/ORDER BY.*$/, "") // 移除 ORDER BY 子句
|
||||
.replace(/LIMIT.*$/, ""); // 移除 LIMIT 和 OFFSET 子句
|
||||
const totalResult = await db
|
||||
.prepare(countQuery)
|
||||
.get(...params.slice(0, -2));
|
||||
const total = totalResult?.total || 0;
|
||||
|
||||
// 获取分页数据
|
||||
const mediaList = db.prepare(query).all(...params)
|
||||
const mediaList = await db.prepare(query).all(...params);
|
||||
|
||||
return c.json({
|
||||
code: 0,
|
||||
@@ -145,13 +179,13 @@ media.get('/page', async (c) => {
|
||||
list: mediaList,
|
||||
total,
|
||||
currentPage,
|
||||
pageSize
|
||||
pageSize,
|
||||
},
|
||||
message: 'Success'
|
||||
})
|
||||
message: "Success",
|
||||
});
|
||||
} catch (error: any) {
|
||||
return c.json({ code: 1, data: {}, message: error.message }, 500)
|
||||
return c.json({ code: 1, data: {}, message: error.message }, 500);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
export default media
|
||||
export default media;
|
||||
|
||||
@@ -4,36 +4,40 @@
|
||||
* @LastEditTime: 2025-06-16 11:17:09
|
||||
* @FilePath: /my-score/honoback/routes/user.ts
|
||||
*/
|
||||
import { Hono } from 'hono'
|
||||
import { sign } from 'hono/jwt'
|
||||
import type { JwtVariables } from 'hono/jwt'
|
||||
import { db } from '../db/index.ts'
|
||||
import { Hono } from "hono";
|
||||
import { sign } from "hono/jwt";
|
||||
import type { JwtVariables } from "hono/jwt";
|
||||
import { db } from "../db/index.ts";
|
||||
|
||||
const user = new Hono<{ Variables: JwtVariables }>()
|
||||
const user = new Hono<{ Variables: JwtVariables }>();
|
||||
|
||||
const AUTH_SECRET = 'it-is-a-secret'
|
||||
const AUTH_SECRET = "it-is-a-secret";
|
||||
|
||||
// 登录路由
|
||||
user.post('/login', async (c) => {
|
||||
const { username, password } = await c.req.json()
|
||||
|
||||
user.post("/login", async (c) => {
|
||||
const { username, password } = await c.req.json();
|
||||
|
||||
// 从数据库验证用户
|
||||
const user = db.prepare('SELECT * FROM users WHERE username = ? AND password = ?')
|
||||
.get(username, password)
|
||||
|
||||
if (user) {
|
||||
const token = await sign({ username: user.username }, AUTH_SECRET)
|
||||
return c.json({
|
||||
const userRecord = await db
|
||||
.prepare("SELECT * FROM users WHERE username = ? AND password = ?")
|
||||
.get(username, password);
|
||||
|
||||
if (userRecord) {
|
||||
const token = await sign({ username: userRecord.username }, AUTH_SECRET);
|
||||
return c.json({
|
||||
code: 200,
|
||||
data: { token },
|
||||
message: '登录成功'
|
||||
})
|
||||
message: "登录成功",
|
||||
});
|
||||
}
|
||||
|
||||
return c.json({
|
||||
code: 401,
|
||||
message: '用户名或密码错误'
|
||||
}, 401)
|
||||
})
|
||||
|
||||
export default user
|
||||
return c.json(
|
||||
{
|
||||
code: 401,
|
||||
message: "用户名或密码错误",
|
||||
},
|
||||
401
|
||||
);
|
||||
});
|
||||
|
||||
export default user;
|
||||
|
||||
Reference in New Issue
Block a user