# 人情往来站点服务端api代码基础版本
# 1. 数据库操作类 (db.js)
/**
* 数据库操作类
* 提供对所有数据表的CRUD操作
*/
export class Database {
constructor(db) {
this.db = db;
}
/**
* 创建账户
* @param {Object} account - 账户信息
* @returns {Promise<Object>} 创建的账户对象
*/
async createAccount(account) {
const result = await this.db.prepare(
`INSERT INTO accounts (username, email, password, avatar, nickname, phone, is_active, extra_data, last_login)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING *`
).bind(
account.username,
account.email,
account.password,
account.avatar,
account.nickname,
account.phone,
account.is_active,
account.extra_data,
account.last_login
).run();
return result.results[0];
}
/**
* 根据用户名获取账户
* @param {string} username - 用户名
* @returns {Promise<Object|null>} 账户对象或null
*/
async getAccountByUsername(username) {
const result = await this.db.prepare(
`SELECT * FROM accounts WHERE username = ? AND deleted_at IS NULL`
).bind(username).all();
return result.results[0] || null;
}
/**
* 根据ID获取账户
* @param {number} id - 账户ID
* @returns {Promise<Object|null>} 账户对象或null
*/
async getAccountById(id) {
const result = await this.db.prepare(
`SELECT * FROM accounts WHERE id = ? AND deleted_at IS NULL`
).bind(id).all();
return result.results[0] || null;
}
/**
* 更新账户最后登录时间
* @param {number} id - 账户ID
* @returns {Promise<void>}
*/
async updateAccountLastLogin(id) {
await this.db.prepare(
`UPDATE accounts SET last_login = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ?`
).bind(id).run();
}
/**
* 创建联系人
* @param {Object} contact - 联系人信息
* @returns {Promise<Object>} 创建的联系人对象
*/
async createContact(contact) {
const result = await this.db.prepare(
`INSERT INTO contacts (account_id, name, phone, relationship, notes, avatar, email, address, is_active, extra_data)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING *`
).bind(
contact.account_id,
contact.name,
contact.phone,
contact.relationship,
contact.notes,
contact.avatar,
contact.email,
contact.address,
contact.is_active,
contact.extra_data
).run();
return result.results[0];
}
/**
* 获取联系人列表
* @param {number} accountId - 账户ID
* @param {number} page - 页码
* @param {number} limit - 每页数量
* @returns {Promise<Object>} 包含联系人列表和总数的对象
*/
async getContacts(accountId, page = 1, limit = 20) {
const offset = (page - 1) * limit;
const [contactsResult, countResult] = await Promise.all([
this.db.prepare(
`SELECT * FROM contacts WHERE account_id = ? AND deleted_at IS NULL ORDER BY created_at DESC LIMIT ? OFFSET ?`
).bind(accountId, limit, offset).all(),
this.db.prepare(
`SELECT COUNT(*) as count FROM contacts WHERE account_id = ? AND deleted_at IS NULL`
).bind(accountId).all()
]);
return {
contacts: contactsResult.results,
total: countResult.results[0].count
};
}
/**
* 根据ID获取联系人
* @param {number} id - 联系人ID
* @param {number} accountId - 账户ID
* @returns {Promise<Object|null>} 联系人对象或null
*/
async getContactById(id, accountId) {
const result = await this.db.prepare(
`SELECT * FROM contacts WHERE id = ? AND account_id = ? AND deleted_at IS NULL`
).bind(id, accountId).all();
return result.results[0] || null;
}
/**
* 更新联系人
* @param {number} id - 联系人ID
* @param {number} accountId - 账户ID
* @param {Object} updates - 更新内容
* @returns {Promise<Object|null>} 更新后的联系人对象或null
*/
async updateContact(id, accountId, updates) {
const fields = [];
const values = [];
for (const [key, value] of Object.entries(updates)) {
if (value !== undefined) {
fields.push(`${key} = ?`);
values.push(value);
}
}
if (fields.length === 0) return null;
values.push(id, accountId);
const result = await this.db.prepare(
`UPDATE contacts SET ${fields.join(', ')}, updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND account_id = ? AND deleted_at IS NULL
RETURNING *`
).bind(...values).run();
return result.results[0] || null;
}
/**
* 删除联系人(软删除)
* @param {number} id - 联系人ID
* @param {number} accountId - 账户ID
* @returns {Promise<boolean>} 是否成功
*/
async deleteContact(id, accountId) {
const result = await this.db.prepare(
`UPDATE contacts SET deleted_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND account_id = ? AND deleted_at IS NULL`
).bind(id, accountId).run();
return result.success;
}
/**
* 创建事件
* @param {Object} event - 事件信息
* @returns {Promise<Object>} 创建的事件对象
*/
async createEvent(event) {
// 处理JSON字段
const photos = event.photos ? JSON.stringify(event.photos) : null;
const tags = event.tags ? JSON.stringify(event.tags) : null;
const extra_data = event.extra_data || null;
const result = await this.db.prepare(
`INSERT INTO events (account_id, contact_id, title, event_type, event_date, amount, description, direction, location, photos, tags, extra_data)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING *`
).bind(
event.account_id,
event.contact_id,
event.title,
event.event_type,
event.event_date,
event.amount,
event.description,
event.direction,
event.location,
photos,
tags,
extra_data
).run();
const createdEvent = result.results[0];
// 解析JSON字段
if (createdEvent.photos) {
try {
createdEvent.photos = JSON.parse(createdEvent.photos);
} catch (e) {
createdEvent.photos = null;
}
}
if (createdEvent.tags) {
try {
createdEvent.tags = JSON.parse(createdEvent.tags);
} catch (e) {
createdEvent.tags = null;
}
}
return createdEvent;
}
/**
* 获取事件列表
* @param {number} accountId - 账户ID
* @param {number} page - 页码
* @param {number} limit - 每页数量
* @param {Object} filters - 过滤条件
* @returns {Promise<Object>} 包含事件列表和总数的对象
*/
async getEvents(accountId, page = 1, limit = 20, filters = {}) {
const offset = (page - 1) * limit;
let whereClause = 'WHERE e.account_id = ? AND e.deleted_at IS NULL';
const values = [accountId];
if (filters.contact_id) {
whereClause += ' AND e.contact_id = ?';
values.push(filters.contact_id);
}
if (filters.event_type) {
whereClause += ' AND e.event_type = ?';
values.push(filters.event_type);
}
if (filters.direction) {
whereClause += ' AND e.direction = ?';
values.push(filters.direction);
}
if (filters.start_date) {
whereClause += ' AND e.event_date >= ?';
values.push(filters.start_date);
}
if (filters.end_date) {
whereClause += ' AND e.event_date <= ?';
values.push(filters.end_date);
}
const [eventsResult, countResult] = await Promise.all([
this.db.prepare(
`SELECT e.*, c.name as contact_name FROM events e
LEFT JOIN contacts c ON e.contact_id = c.id
${whereClause}
ORDER BY e.event_date DESC, e.created_at DESC
LIMIT ? OFFSET ?`
).bind(...values, limit, offset).all(),
this.db.prepare(
`SELECT COUNT(*) as count FROM events e ${whereClause}`
).bind(...values).all()
]);
const events = eventsResult.results.map(event => {
const parsedEvent = { ...event };
if (parsedEvent.photos) {
try {
parsedEvent.photos = JSON.parse(parsedEvent.photos);
} catch (e) {
parsedEvent.photos = null;
}
}
if (parsedEvent.tags) {
try {
parsedEvent.tags = JSON.parse(parsedEvent.tags);
} catch (e) {
parsedEvent.tags = null;
}
}
return parsedEvent;
});
return {
events,
total: countResult.results[0].count
};
}
/**
* 根据ID获取事件
* @param {number} id - 事件ID
* @param {number} accountId - 账户ID
* @returns {Promise<Object|null>} 事件对象或null
*/
async getEventById(id, accountId) {
const result = await this.db.prepare(
`SELECT e.*, c.name as contact_name FROM events e
LEFT JOIN contacts c ON e.contact_id = c.id
WHERE e.id = ? AND e.account_id = ? AND e.deleted_at IS NULL`
).bind(id, accountId).all();
if (!result.results[0]) return null;
const event = result.results[0];
if (event.photos) {
try {
event.photos = JSON.parse(event.photos);
} catch (e) {
event.photos = null;
}
}
if (event.tags) {
try {
event.tags = JSON.parse(event.tags);
} catch (e) {
event.tags = null;
}
}
return event;
}
/**
* 更新事件
* @param {number} id - 事件ID
* @param {number} accountId - 账户ID
* @param {Object} updates - 更新内容
* @returns {Promise<Object|null>} 更新后的事件对象或null
*/
async updateEvent(id, accountId, updates) {
const fields = [];
const values = [];
// 处理特殊字段
const processedUpdates = { ...updates };
if (processedUpdates.photos) {
processedUpdates.photos = JSON.stringify(processedUpdates.photos);
}
if (processedUpdates.tags) {
processedUpdates.tags = JSON.stringify(processedUpdates.tags);
}
for (const [key, value] of Object.entries(processedUpdates)) {
if (value !== undefined) {
fields.push(`${key} = ?`);
values.push(value);
}
}
if (fields.length === 0) return null;
values.push(id, accountId);
const result = await this.db.prepare(
`UPDATE events SET ${fields.join(', ')}, updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND account_id = ? AND deleted_at IS NULL
RETURNING *`
).bind(...values).run();
if (!result.results[0]) return null;
const updatedEvent = result.results[0];
if (updatedEvent.photos) {
try {
updatedEvent.photos = JSON.parse(updatedEvent.photos);
} catch (e) {
updatedEvent.photos = null;
}
}
if (updatedEvent.tags) {
try {
updatedEvent.tags = JSON.parse(updatedEvent.tags);
} catch (e) {
updatedEvent.tags = null;
}
}
return updatedEvent;
}
/**
* 删除事件(软删除)
* @param {number} id - 事件ID
* @param {number} accountId - 账户ID
* @returns {Promise<boolean>} 是否成功
*/
async deleteEvent(id, accountId) {
const result = await this.db.prepare(
`UPDATE events SET deleted_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND account_id = ? AND deleted_at IS NULL`
).bind(id, accountId).run();
return result.success;
}
/**
* 获取或创建统计记录
* @param {number} accountId - 账户ID
* @param {string} statType - 统计类型
* @param {string} statKey - 统计键值
* @param {number} giveAmount - 给出金额
* @param {number} receiveAmount - 收到金额
* @returns {Promise<Object>} 统计对象
*/
async upsertStatistics(accountId, statType, statKey, giveAmount, receiveAmount) {
const result = await this.db.prepare(
`INSERT INTO statistics (account_id, stat_type, stat_key, total_give, total_receive, event_count)
VALUES (?, ?, ?, ?, ?, 1)
ON CONFLICT(account_id, stat_type, stat_key)
DO UPDATE SET
total_give = total_give + ?,
total_receive = total_receive + ?,
event_count = event_count + 1,
updated_at = CURRENT_TIMESTAMP
RETURNING *`
).bind(accountId, statType, statKey, giveAmount, receiveAmount, giveAmount, receiveAmount).run();
return result.results[0];
}
/**
* 根据条件获取统计信息
* @param {number} accountId - 账户ID
* @param {string} statType - 统计类型
* @param {string} statKey - 统计键值
* @returns {Promise<Object|null>} 统计对象或null
*/
async getStatistics(accountId, statType, statKey) {
const result = await this.db.prepare(
`SELECT * FROM statistics WHERE account_id = ? AND stat_type = ? AND stat_key = ?`
).bind(accountId, statType, statKey).all();
return result.results[0] || null;
}
/**
* 根据键名获取配置
* @param {string} key - 配置键名
* @returns {Promise<Object|null>} 配置对象或null
*/
async getConfig(key) {
const result = await this.db.prepare(
`SELECT * FROM config WHERE config_key = ?`
).bind(key).all();
return result.results[0] || null;
}
/**
* 获取所有配置
* @returns {Promise<Object>} 配置对象
*/
async getAllConfig() {
const result = await this.db.prepare(
`SELECT config_key, config_value FROM config`
).all();
const config = {};
for (const row of result.results) {
try {
config[row.config_key] = JSON.parse(row.config_value);
} catch (e) {
config[row.config_key] = row.config_value;
}
}
return config;
}
}
# 2. 中间件 (middleware.js)
import { jwt } from 'hono/jwt';
import { Database } from './db';
/**
* 认证中间件
* 验证JWT token并设置用户信息
*/
export const authMiddleware = async (c, next) => {
const authHeader = c.req.header('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '未授权访问'
}, 401);
}
const token = authHeader.substring(7);
try {
const payload = await jwt.verify(token, c.env.JWT_SECRET);
c.set('user', payload);
await next();
} catch (e) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '无效的令牌'
}, 401);
}
};
/**
* 数据库中间件
* 将数据库实例注入到上下文中
*/
export const dbMiddleware = async (c, next) => {
const db = new Database(c.env.DB);
c.set('db', db);
await next();
};
/**
* 统一响应格式中间件
*/
export const responseMiddleware = async (c, next) => {
await next();
// 这里可以添加统一的响应格式处理
};
# 3. 认证API (routes/auth.js)
import { Hono } from 'hono';
import { jwt } from 'hono/jwt';
import { hash, verify } from 'argon2';
/**
* 认证相关API路由
* 包括注册、登录、获取当前用户信息等功能
*/
const auth = new Hono();
/**
* 用户注册接口
* POST /api/auth/register
*
* 请求体:
* {
* "username": "用户名",
* "email": "邮箱",
* "password": "密码",
* "nickname": "昵称" (可选)
* }
*
* 响应:
* {
* "success": true/false,
* "data": { 用户信息 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
auth.post('/register', async (c) => {
const db = c.get('db');
const body = await c.req.json();
// 验证输入
if (!body.username || !body.email || !body.password) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '用户名、邮箱和密码为必填项'
}, 400);
}
// 检查用户名是否已存在
const existingUser = await db.getAccountByUsername(body.username);
if (existingUser) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '用户名已存在'
}, 400);
}
// 密码加密
const hashedPassword = await hash(body.password);
try {
const account = await db.createAccount({
username: body.username,
email: body.email,
password: hashedPassword,
nickname: body.nickname,
is_active: true
});
// 生成JWT token
const payload = {
id: account.id,
username: account.username,
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7 // 7天过期
};
const token = await jwt.sign(payload, c.env.JWT_SECRET);
const { password, ...accountWithoutPassword } = account;
return c.json({
success: true,
data: {
token,
account: accountWithoutPassword
},
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '注册失败'
}, 500);
}
});
/**
* 用户登录接口
* POST /api/auth/login
*
* 请求体:
* {
* "username": "用户名",
* "password": "密码"
* }
*
* 响应:
* {
* "success": true/false,
* "data": { 用户信息 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
auth.post('/login', async (c) => {
const db = c.get('db');
const body = await c.req.json();
if (!body.username || !body.password) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '用户名和密码为必填项'
}, 400);
}
const account = await db.getAccountByUsername(body.username);
if (!account) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '用户名或密码错误'
}, 401);
}
const isValid = await verify(account.password, body.password);
if (!isValid) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '用户名或密码错误'
}, 401);
}
// 更新最后登录时间
await db.updateAccountLastLogin(account.id);
// 生成JWT token
const payload = {
id: account.id,
username: account.username,
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7 // 7天过期
};
const token = await jwt.sign(payload, c.env.JWT_SECRET);
const { password, ...accountWithoutPassword } = account;
return c.json({
success: true,
data: {
token,
account: accountWithoutPassword
},
error: false,
errorMessage: null
});
});
/**
* 获取当前用户信息接口
* GET /api/auth/me
*
* 响应:
* {
* "success": true/false,
* "data": { 用户信息 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
auth.get('/me', async (c) => {
const db = c.get('db');
const user = c.get('user');
const account = await db.getAccountById(user.id);
if (!account) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '用户不存在'
}, 404);
}
const { password, ...accountWithoutPassword } = account;
return c.json({
success: true,
data: { account: accountWithoutPassword },
error: false,
errorMessage: null
});
});
export default auth;
# 4. 联系人API (routes/contacts.js)
import { Hono } from 'hono';
/**
* 联系人相关API路由
* 包括创建、获取、更新、删除联系人等功能
*/
const contacts = new Hono();
/**
* 创建联系人接口
* POST /api/contacts
*
* 请求体:
* {
* "name": "联系人姓名",
* "phone": "电话" (可选),
* "relationship": "关系类型" (可选),
* "notes": "备注" (可选),
* "avatar": "头像URL" (可选),
* "email": "邮箱" (可选),
* "address": "地址" (可选)
* }
*
* 响应:
* {
* "success": true/false,
* "data": { 联系人信息 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
contacts.post('/', async (c) => {
const db = c.get('db');
const user = c.get('user');
const body = await c.req.json();
// 基本验证
if (!body.name) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '联系人姓名为必填项'
}, 400);
}
try {
const contact = await db.createContact({
account_id: user.id,
name: body.name,
phone: body.phone,
relationship: body.relationship,
notes: body.notes,
avatar: body.avatar,
email: body.email,
address: body.address,
is_active: true
});
return c.json({
success: true,
data: { contact },
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '创建联系人失败'
}, 500);
}
});
/**
* 获取联系人列表接口
* GET /api/contacts?page=1&limit=20
*
* 查询参数:
* - page: 页码 (默认1)
* - limit: 每页数量 (默认20)
*
* 响应:
* {
* "success": true/false,
* "data": { 联系人列表和总数 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
contacts.get('/', async (c) => {
const db = c.get('db');
const user = c.get('user');
let page = parseInt(c.req.query('page') || '1');
let limit = parseInt(c.req.query('limit') || '20');
// 参数验证
if (isNaN(page) || page < 1) page = 1;
if (isNaN(limit) || limit < 1 || limit > 100) limit = 20;
try {
const result = await db.getContacts(user.id, page, limit);
return c.json({
success: true,
data: result,
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '获取联系人列表失败'
}, 500);
}
});
/**
* 获取单个联系人接口
* GET /api/contacts/:id
*
* 路径参数:
* - id: 联系人ID
*
* 响应:
* {
* "success": true/false,
* "data": { 联系人信息 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
contacts.get('/:id', async (c) => {
const db = c.get('db');
const user = c.get('user');
const id = parseInt(c.req.param('id'));
if (isNaN(id)) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '无效的联系人ID'
}, 400);
}
try {
const contact = await db.getContactById(id, user.id);
if (!contact) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '联系人不存在'
}, 404);
}
return c.json({
success: true,
data: { contact },
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '获取联系人失败'
}, 500);
}
});
/**
* 更新联系人接口
* PUT /api/contacts/:id
*
* 路径参数:
* - id: 联系人ID
*
* 请求体:
* {
* "name": "联系人姓名" (可选),
* "phone": "电话" (可选),
* "relationship": "关系类型" (可选),
* "notes": "备注" (可选),
* "avatar": "头像URL" (可选),
* "email": "邮箱" (可选),
* "address": "地址" (可选),
* "is_active": "是否激活" (可选)
* }
*
* 响应:
* {
* "success": true/false,
* "data": { 更新后的联系人信息 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
contacts.put('/:id', async (c) => {
const db = c.get('db');
const user = c.get('user');
const id = parseInt(c.req.param('id'));
const body = await c.req.json();
if (isNaN(id)) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '无效的联系人ID'
}, 400);
}
try {
const contact = await db.updateContact(id, user.id, body);
if (!contact) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '联系人不存在'
}, 404);
}
return c.json({
success: true,
data: { contact },
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '更新联系人失败'
}, 500);
}
});
/**
* 删除联系人接口
* DELETE /api/contacts/:id
*
* 路径参数:
* - id: 联系人ID
*
* 响应:
* {
* "success": true/false,
* "data": { 删除结果 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
contacts.delete('/:id', async (c) => {
const db = c.get('db');
const user = c.get('user');
const id = parseInt(c.req.param('id'));
if (isNaN(id)) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '无效的联系人ID'
}, 400);
}
try {
const success = await db.deleteContact(id, user.id);
if (!success) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '联系人不存在'
}, 404);
}
return c.json({
success: true,
data: { message: '联系人删除成功' },
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '删除联系人失败'
}, 500);
}
});
/**
* 批量删除联系人接口
* POST /api/contacts/batch-delete
*
* 请求体:
* {
* "ids": [1, 2, 3] // 要删除的联系人ID数组
* }
*
* 响应:
* {
* "success": true/false,
* "data": { 删除结果 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
contacts.post('/batch-delete', async (c) => {
const db = c.get('db');
const user = c.get('user');
const body = await c.req.json();
if (!body.ids || !Array.isArray(body.ids) || body.ids.length === 0) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: 'ids数组为必填项'
}, 400);
}
let deletedCount = 0;
let failedCount = 0;
try {
for (const id of body.ids) {
const success = await db.deleteContact(id, user.id);
if (success) {
deletedCount++;
} else {
failedCount++;
}
}
return c.json({
success: true,
data: {
message: '批量删除成功',
deleted: deletedCount,
failed: failedCount
},
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '批量删除联系人失败'
}, 500);
}
});
export default contacts;
# 5. 事件API (routes/events.js)
import { Hono } from 'hono';
/**
* 事件相关API路由
* 包括创建、获取、更新、删除人情往来事件等功能
*/
const events = new Hono();
/**
* 创建事件接口
* POST /api/events
*
* 请求体:
* {
* "contact_id": "联系人ID",
* "title": "事件标题",
* "event_type": "事件类型",
* "event_date": "事件日期 (YYYY-MM-DD)",
* "amount": "金额",
* "description": "描述" (可选),
* "direction": "方向 (give/receive)",
* "location": "地点" (可选),
* "photos": ["照片URL数组"] (可选),
* "tags": ["标签数组"] (可选)
* }
*
* 响应:
* {
* "success": true/false,
* "data": { 事件信息 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
events.post('/', async (c) => {
const db = c.get('db');
const user = c.get('user');
const body = await c.req.json();
// 基本验证
if (!body.contact_id || !body.title || !body.event_type || !body.event_date || body.amount === undefined || !body.direction) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '联系人ID、事件标题、事件类型、事件日期、金额和方向为必填项'
}, 400);
}
// 验证方向字段
if (body.direction !== 'give' && body.direction !== 'receive') {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '方向必须是"give"或"receive"'
}, 400);
}
// 验证金额
if (isNaN(body.amount) || body.amount < 0) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '金额必须为非负数'
}, 400);
}
try {
const event = await db.createEvent({
account_id: user.id,
contact_id: body.contact_id,
title: body.title,
event_type: body.event_type,
event_date: body.event_date,
amount: parseFloat(body.amount),
description: body.description,
direction: body.direction,
location: body.location,
photos: body.photos,
tags: body.tags
});
// 更新统计信息
const giveAmount = body.direction === 'give' ? parseFloat(body.amount) : 0;
const receiveAmount = body.direction === 'receive' ? parseFloat(body.amount) : 0;
const year = new Date(body.event_date).getFullYear();
const month = new Date(body.event_date).getMonth() + 1;
const yearMonth = `${year}-${month.toString().padStart(2, '0')}`;
await db.upsertStatistics(user.id, 'month', yearMonth, giveAmount, receiveAmount);
await db.upsertStatistics(user.id, 'year', year.toString(), giveAmount, receiveAmount);
return c.json({
success: true,
data: { event },
error: false,
errorMessage: null
});
} catch (error) {
console.error('Create event error:', error);
return c.json({
success: false,
data: null,
error: true,
errorMessage: '创建事件失败'
}, 500);
}
});
/**
* 获取事件列表接口
* GET /api/events?page=1&limit=20&contact_id=1&event_type=birthday&direction=give&start_date=2024-01-01&end_date=2024-12-31
*
* 查询参数:
* - page: 页码 (默认1)
* - limit: 每页数量 (默认20)
* - contact_id: 联系人ID (可选)
* - event_type: 事件类型 (可选)
* - direction: 方向 (give/receive) (可选)
* - start_date: 开始日期 (可选)
* - end_date: 结束日期 (可选)
*
* 响应:
* {
* "success": true/false,
* "data": { 事件列表和总数 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
events.get('/', async (c) => {
const db = c.get('db');
const user = c.get('user');
let page = parseInt(c.req.query('page') || '1');
let limit = parseInt(c.req.query('limit') || '20');
// 参数验证
if (isNaN(page) || page < 1) page = 1;
if (isNaN(limit) || limit < 1 || limit > 100) limit = 20;
const filters = {
contact_id: c.req.query('contact_id') ? parseInt(c.req.query('contact_id')) : undefined,
event_type: c.req.query('event_type'),
direction: c.req.query('direction'),
start_date: c.req.query('start_date'),
end_date: c.req.query('end_date')
};
// 过滤参数验证
if (filters.contact_id && isNaN(filters.contact_id)) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '无效的联系人ID'
}, 400);
}
if (filters.direction && filters.direction !== 'give' && filters.direction !== 'receive') {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '方向必须是"give"或"receive"'
}, 400);
}
try {
const result = await db.getEvents(user.id, page, limit, filters);
return c.json({
success: true,
data: result,
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '获取事件列表失败'
}, 500);
}
});
/**
* 获取单个事件接口
* GET /api/events/:id
*
* 路径参数:
* - id: 事件ID
*
* 响应:
* {
* "success": true/false,
* "data": { 事件信息 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
events.get('/:id', async (c) => {
const db = c.get('db');
const user = c.get('user');
const id = parseInt(c.req.param('id'));
if (isNaN(id)) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '无效的事件ID'
}, 400);
}
try {
const event = await db.getEventById(id, user.id);
if (!event) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '事件不存在'
}, 404);
}
return c.json({
success: true,
data: { event },
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '获取事件失败'
}, 500);
}
});
/**
* 更新事件接口
* PUT /api/events/:id
*
* 路径参数:
* - id: 事件ID
*
* 请求体:
* {
* "contact_id": "联系人ID" (可选),
* "title": "事件标题" (可选),
* "event_type": "事件类型" (可选),
* "event_date": "事件日期" (可选),
* "amount": "金额" (可选),
* "description": "描述" (可选),
* "direction": "方向" (可选),
* "location": "地点" (可选),
* "photos": ["照片URL数组"] (可选),
* "tags": ["标签数组"] (可选)
* }
*
* 响应:
* {
* "success": true/false,
* "data": { 更新后的事件信息 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
events.put('/:id', async (c) => {
const db = c.get('db');
const user = c.get('user');
const id = parseInt(c.req.param('id'));
const body = await c.req.json();
if (isNaN(id)) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '无效的事件ID'
}, 400);
}
try {
const event = await db.updateEvent(id, user.id, body);
if (!event) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '事件不存在'
}, 404);
}
return c.json({
success: true,
data: { event },
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '更新事件失败'
}, 500);
}
});
/**
* 删除事件接口
* DELETE /api/events/:id
*
* 路径参数:
* - id: 事件ID
*
* 响应:
* {
* "success": true/false,
* "data": { 删除结果 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
events.delete('/:id', async (c) => {
const db = c.get('db');
const user = c.get('user');
const id = parseInt(c.req.param('id'));
if (isNaN(id)) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '无效的事件ID'
}, 400);
}
try {
const success = await db.deleteEvent(id, user.id);
if (!success) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '事件不存在'
}, 404);
}
return c.json({
success: true,
data: { message: '事件删除成功' },
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '删除事件失败'
}, 500);
}
});
export default events;
# 6. 配置API (routes/config.js)
import { Hono } from 'hono';
/**
* 配置相关API路由
* 包括获取系统配置等功能
*/
const config = new Hono();
/**
* 获取所有配置接口
* GET /api/config
*
* 响应:
* {
* "success": true/false,
* "data": { 配置信息 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
config.get('/', async (c) => {
const db = c.get('db');
try {
const configData = await db.getAllConfig();
return c.json({
success: true,
data: configData,
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '获取配置失败'
}, 500);
}
});
/**
* 获取指定配置接口
* GET /api/config/:key
*
* 路径参数:
* - key: 配置键名
*
* 响应:
* {
* "success": true/false,
* "data": { 配置信息 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
config.get('/:key', async (c) => {
const db = c.get('db');
const key = c.req.param('key');
try {
const configData = await db.getConfig(key);
if (!configData) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '配置不存在'
}, 404);
}
// 解析配置值
let parsedValue;
try {
parsedValue = JSON.parse(configData.config_value);
} catch (e) {
parsedValue = configData.config_value;
}
return c.json({
success: true,
data: {
key: configData.config_key,
value: parsedValue,
description: configData.description
},
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '获取配置失败'
}, 500);
}
});
export default config;
# 7. 统计API (routes/statistics.js)
import { Hono } from 'hono';
/**
* 统计相关API路由
* 包括获取统计信息等功能
*/
const statistics = new Hono();
/**
* 获取统计信息接口
* GET /api/statistics?type=month&key=2024-01
*
* 查询参数:
* - type: 统计类型 (year/month/quarter)
* - key: 统计键值 (如: 2024, 2024-01, 2024-Q1)
*
* 响应:
* {
* "success": true/false,
* "data": { 统计信息 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
statistics.get('/', async (c) => {
const db = c.get('db');
const user = c.get('user');
const type = c.req.query('type');
const key = c.req.query('key');
if (!type || !key) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '统计类型和键值为必填项'
}, 400);
}
try {
const stat = await db.getStatistics(user.id, type, key);
return c.json({
success: true,
data: { statistics: stat },
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '获取统计信息失败'
}, 500);
}
});
/**
* 获取年度统计概览
* GET /api/statistics/overview?year=2024
*
* 响应:
* {
* "success": true/false,
* "data": { 统计概览 },
* "error": true/false,
* "errorMessage": "错误信息"
* }
*/
statistics.get('/overview', async (c) => {
const db = c.get('db');
const user = c.get('user');
const year = c.req.query('year') || new Date().getFullYear().toString();
try {
// 获取年度总计
const yearStat = await db.getStatistics(user.id, 'year', year);
// 获取该年度各月统计
const monthStats = [];
for (let month = 1; month <= 12; month++) {
const monthKey = `${year}-${month.toString().padStart(2, '0')}`;
const monthStat = await db.getStatistics(user.id, 'month', monthKey);
if (monthStat) {
monthStats.push(monthStat);
}
}
return c.json({
success: true,
data: {
year_total_give: yearStat?.total_give || 0,
year_total_receive: yearStat?.total_receive || 0,
month_stats: monthStats
},
error: false,
errorMessage: null
});
} catch (error) {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '获取统计概览失败'
}, 500);
}
});
export default statistics;
# 8. 入口文件 (index.js)
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { Database } from './db';
import { authMiddleware, dbMiddleware } from './middleware';
// 导入路由
import authRoutes from './routes/auth';
import contactsRoutes from './routes/contacts';
import eventsRoutes from './routes/events';
import configRoutes from './routes/config';
import statisticsRoutes from './routes/statistics';
/**
* 主应用入口文件
* 配置中间件和路由
*/
const app = new Hono();
// CORS中间件
app.use('*', cors());
// 数据库中间件
app.use('*', dbMiddleware);
// 公共路由(无需认证)
app.route('/api/auth', authRoutes);
app.route('/api/config', configRoutes);
// 需要认证的路由
app.use('/api/contacts/*', authMiddleware);
app.use('/api/events/*', authMiddleware);
app.use('/api/statistics/*', authMiddleware);
app.route('/api/contacts', contactsRoutes);
app.route('/api/events', eventsRoutes);
app.route('/api/statistics', statisticsRoutes);
// 健康检查接口
app.get('/health', (c) => {
return c.json({
success: true,
data: { status: 'ok', timestamp: new Date().toISOString() },
error: false,
errorMessage: null
});
});
// API版本信息
app.get('/api', (c) => {
return c.json({
success: true,
data: {
version: '1.0.0',
timestamp: new Date().toISOString()
},
error: false,
errorMessage: null
});
});
// 404处理
app.notFound((c) => {
return c.json({
success: false,
data: null,
error: true,
errorMessage: '接口不存在'
}, 404);
});
// 错误处理
app.onError((err, c) => {
console.error('Error:', err);
return c.json({
success: false,
data: null,
error: true,
errorMessage: '服务器内部错误'
}, 500);
});
export default app;
# API接口文档
# 认证相关接口
POST /api/auth/register
- 用户注册POST /api/auth/login
- 用户登录GET /api/auth/me
- 获取当前用户信息
# 联系人相关接口
POST /api/contacts
- 创建联系人GET /api/contacts
- 获取联系人列表GET /api/contacts/:id
- 获取单个联系人PUT /api/contacts/:id
- 更新联系人DELETE /api/contacts/:id
- 删除联系人POST /api/contacts/batch-delete
- 批量删除联系人
# 事件相关接口
POST /api/events
- 创建事件GET /api/events
- 获取事件列表GET /api/events/:id
- 获取单个事件PUT /api/events/:id
- 更新事件DELETE /api/events/:id
- 删除事件
# 统计相关接口
GET /api/statistics
- 获取统计信息GET /api/statistics/overview
- 获取统计概览
# 配置相关接口
GET /api/config
- 获取所有配置GET /api/config/:key
- 获取指定配置
所有API响应格式统一为:
{
"success": true/false,
"data": {},
"error": true/false,
"errorMessage": "错误信息"
}