新增消息功能,包括消息类型和状态的定义,创建消息和用户消息关联的数据库迁移,添加消息相关的API路由,提升系统的消息处理能力和用户体验。
This commit is contained in:
@@ -448,3 +448,52 @@ export interface LoginLocation {
|
||||
/** 登录时间 */
|
||||
login_time?: string;
|
||||
}
|
||||
|
||||
// 消息类型枚举
|
||||
export enum MessageType {
|
||||
SYSTEM = 'system', // 系统通知
|
||||
PRIVATE = 'private', // 私信
|
||||
ANNOUNCE = 'announce' // 公告
|
||||
}
|
||||
|
||||
// 消息状态枚举
|
||||
export enum MessageStatus {
|
||||
UNREAD = 0, // 未读
|
||||
READ = 1, // 已读
|
||||
DELETED = 2 // 已删除
|
||||
}
|
||||
|
||||
// 消息状态中文映射
|
||||
export const MessageStatusNameMap: Record<MessageStatus, string> = {
|
||||
[MessageStatus.UNREAD]: '未读',
|
||||
[MessageStatus.READ]: '已读',
|
||||
[MessageStatus.DELETED]: '已删除'
|
||||
};
|
||||
|
||||
// 消息实体接口
|
||||
export interface Message {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
type: MessageType;
|
||||
sender_id?: number; // 发送者ID(系统消息可为空)
|
||||
sender_name?: string; // 发送者名称
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// 用户消息关联接口
|
||||
export interface UserMessage {
|
||||
id: number;
|
||||
user_id: number;
|
||||
message_id: number;
|
||||
status: MessageStatus;
|
||||
is_deleted?: DeleteStatus;
|
||||
read_at?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
|
||||
// 关联信息
|
||||
message?: Message;
|
||||
sender?: User;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import { migrations } from './migrations.ts';
|
||||
// 导入基础路由
|
||||
import { createAuthRoutes } from "./routes_auth.ts";
|
||||
import { createUserRoutes } from "./routes_users.ts";
|
||||
import { createMessagesRoutes } from "./routes_messages.ts";
|
||||
|
||||
dayjs.extend(utc)
|
||||
// 初始化debug实例
|
||||
@@ -328,6 +329,7 @@ export default function({ apiClient, app, moduleDir }: ModuleParams) {
|
||||
api.route('/charts', createChartRoutes(withAuth)) // 添加图表数据路由
|
||||
api.route('/map', createMapRoutes(withAuth)) // 添加地图数据路由
|
||||
api.route('/settings', createSystemSettingsRoutes(withAuth)) // 添加系统设置路由
|
||||
api.route('/messages', createMessagesRoutes(withAuth)) // 添加消息路由
|
||||
|
||||
// 注册API路由
|
||||
honoApp.route('/api', api)
|
||||
|
||||
@@ -390,6 +390,53 @@ const seedInitialData: MigrationLiveDefinition = {
|
||||
}
|
||||
};
|
||||
|
||||
// 创建消息表迁移
|
||||
const createMessagesTable: MigrationLiveDefinition = {
|
||||
name: "create_messages_table",
|
||||
up: async (api) => {
|
||||
await api.schema.createTable('messages', (table) => {
|
||||
table.increments('id').primary().comment('消息ID');
|
||||
table.string('title').notNullable().comment('消息标题');
|
||||
table.text('content').notNullable().comment('消息内容');
|
||||
table.enum('type', ['system', 'private', 'announce']).notNullable().comment('消息类型');
|
||||
table.integer('sender_id').unsigned().references('id').inTable('users').onDelete('SET NULL').comment('发送者ID');
|
||||
table.string('sender_name').comment('发送者名称');
|
||||
table.timestamps(true, true);
|
||||
|
||||
// 添加索引
|
||||
table.index('type');
|
||||
table.index('sender_id');
|
||||
});
|
||||
},
|
||||
down: async (api) => {
|
||||
await api.schema.dropTable('messages');
|
||||
}
|
||||
};
|
||||
|
||||
// 创建用户消息关联表迁移
|
||||
const createUserMessagesTable: MigrationLiveDefinition = {
|
||||
name: "create_user_messages_table",
|
||||
up: async (api) => {
|
||||
await api.schema.createTable('user_messages', (table) => {
|
||||
table.increments('id').primary().comment('关联ID');
|
||||
table.integer('user_id').unsigned().references('id').inTable('users').onDelete('CASCADE').comment('用户ID');
|
||||
table.integer('message_id').unsigned().references('id').inTable('messages').onDelete('CASCADE').comment('消息ID');
|
||||
table.integer('status').defaultTo(0).comment('阅读状态(0=未读,1=已读)');
|
||||
table.integer('is_deleted').defaultTo(0).comment('删除状态(0=未删除,1=已删除)');
|
||||
table.timestamp('read_at').nullable().comment('阅读时间');
|
||||
table.timestamps(true, true);
|
||||
|
||||
// 添加复合索引
|
||||
table.index(['user_id', 'status']);
|
||||
table.index(['user_id', 'is_deleted']);
|
||||
table.unique(['user_id', 'message_id']);
|
||||
});
|
||||
},
|
||||
down: async (api) => {
|
||||
await api.schema.dropTable('user_messages');
|
||||
}
|
||||
};
|
||||
|
||||
// 导出所有迁移
|
||||
export const migrations = [
|
||||
createUsersTable,
|
||||
@@ -399,5 +446,7 @@ export const migrations = [
|
||||
createFileLibraryTable,
|
||||
createThemeSettingsTable,
|
||||
createSystemSettingsTable,
|
||||
seedInitialData
|
||||
createMessagesTable,
|
||||
createUserMessagesTable,
|
||||
seedInitialData,
|
||||
];
|
||||
178
server/routes_messages.ts
Normal file
178
server/routes_messages.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import { Hono } from 'hono'
|
||||
import type { Variables } from './app.tsx'
|
||||
import type { WithAuth } from './app.tsx'
|
||||
import { MessageType, MessageStatus } from '../client/share/types.ts'
|
||||
|
||||
export function createMessagesRoutes(withAuth: WithAuth) {
|
||||
const messagesRoutes = new Hono<{ Variables: Variables }>()
|
||||
|
||||
// 发送消息
|
||||
messagesRoutes.post('/', withAuth, async (c) => {
|
||||
try {
|
||||
const auth = c.get('auth')
|
||||
const apiClient = c.get('apiClient')
|
||||
const { title, content, type, receiver_ids } = await c.req.json()
|
||||
|
||||
if (!title || !content || !type || !receiver_ids?.length) {
|
||||
return c.json({ error: '缺少必要参数' }, 400)
|
||||
}
|
||||
|
||||
// 创建消息
|
||||
const user = c.get('user')
|
||||
if (!user) return c.json({ error: '未授权访问' }, 401)
|
||||
|
||||
const [messageId] = await apiClient.database.table('messages').insert({
|
||||
title,
|
||||
content,
|
||||
type,
|
||||
sender_id: user.id,
|
||||
sender_name: user.username,
|
||||
created_at: apiClient.database.fn.now(),
|
||||
updated_at: apiClient.database.fn.now()
|
||||
})
|
||||
|
||||
// 关联用户消息
|
||||
const userMessages = receiver_ids.map((userId: number) => ({
|
||||
user_id: userId,
|
||||
message_id: messageId,
|
||||
status: MessageStatus.UNREAD,
|
||||
created_at: apiClient.database.fn.now(),
|
||||
updated_at: apiClient.database.fn.now()
|
||||
}))
|
||||
|
||||
await apiClient.database.table('user_messages').insert(userMessages)
|
||||
|
||||
return c.json({ message: '消息发送成功', id: messageId }, 201)
|
||||
} catch (error) {
|
||||
console.error('发送消息失败:', error)
|
||||
return c.json({ error: '发送消息失败' }, 500)
|
||||
}
|
||||
})
|
||||
|
||||
// 获取用户消息列表
|
||||
messagesRoutes.get('/', withAuth, async (c) => {
|
||||
try {
|
||||
const apiClient = c.get('apiClient')
|
||||
|
||||
const page = Number(c.req.query('page')) || 1
|
||||
const pageSize = Number(c.req.query('pageSize')) || 20
|
||||
const type = c.req.query('type')
|
||||
const status = c.req.query('status')
|
||||
|
||||
const user = c.get('user')
|
||||
if (!user) return c.json({ error: '未授权访问' }, 401)
|
||||
|
||||
const query = apiClient.database.table('user_messages')
|
||||
.select('m.*', 'um.status as user_status', 'um.read_at', 'um.id as user_message_id')
|
||||
.from('user_messages as um')
|
||||
.leftJoin('messages as m', 'um.message_id', 'm.id')
|
||||
.where('um.user_id', user.id)
|
||||
.where('um.is_deleted', 0)
|
||||
.orderBy('m.created_at', 'desc')
|
||||
.limit(pageSize)
|
||||
.offset((page - 1) * pageSize)
|
||||
|
||||
if (type) query.where('m.type', type)
|
||||
if (status) query.where('um.status', status)
|
||||
|
||||
const messages = await query
|
||||
|
||||
return c.json(messages)
|
||||
} catch (error) {
|
||||
console.error('获取消息列表失败:', error)
|
||||
return c.json({ error: '获取消息列表失败' }, 500)
|
||||
}
|
||||
})
|
||||
|
||||
// 获取消息详情
|
||||
messagesRoutes.get('/:id', withAuth, async (c) => {
|
||||
try {
|
||||
const apiClient = c.get('apiClient')
|
||||
|
||||
const messageId = c.req.param('id')
|
||||
|
||||
const user = c.get('user')
|
||||
if (!user) return c.json({ error: '未授权访问' }, 401)
|
||||
|
||||
const message = await apiClient.database.table('user_messages')
|
||||
.select('m.*', 'um.status as user_status', 'um.read_at')
|
||||
.from('user_messages as um')
|
||||
.leftJoin('messages as m', 'um.message_id', 'm.id')
|
||||
.where('um.user_id', user.id)
|
||||
.where('um.message_id', messageId)
|
||||
.first()
|
||||
|
||||
if (!message) {
|
||||
return c.json({ error: '消息不存在或无权访问' }, 404)
|
||||
}
|
||||
|
||||
// 标记为已读
|
||||
if (message.user_status === MessageStatus.UNREAD) {
|
||||
const user = c.get('user')
|
||||
if (!user) return c.json({ error: '未授权访问' }, 401)
|
||||
|
||||
await apiClient.database.table('user_messages')
|
||||
.where('user_id', user.id)
|
||||
.where('message_id', messageId)
|
||||
.update({
|
||||
status: MessageStatus.READ,
|
||||
read_at: apiClient.database.fn.now(),
|
||||
updated_at: apiClient.database.fn.now()
|
||||
})
|
||||
}
|
||||
|
||||
return c.json(message)
|
||||
} catch (error) {
|
||||
console.error('获取消息详情失败:', error)
|
||||
return c.json({ error: '获取消息详情失败' }, 500)
|
||||
}
|
||||
})
|
||||
|
||||
// 删除消息(软删除)
|
||||
messagesRoutes.delete('/:id', withAuth, async (c) => {
|
||||
try {
|
||||
const apiClient = c.get('apiClient')
|
||||
const user = c.get('user')
|
||||
if (!user) return c.json({ error: '未授权访问' }, 401)
|
||||
|
||||
const messageId = c.req.param('id')
|
||||
|
||||
await apiClient.database.table('user_messages')
|
||||
.where('user_id', user.id)
|
||||
.where('message_id', messageId)
|
||||
.update({
|
||||
is_deleted: 1,
|
||||
updated_at: apiClient.database.fn.now()
|
||||
})
|
||||
|
||||
return c.json({ message: '消息已删除' })
|
||||
} catch (error) {
|
||||
console.error('删除消息失败:', error)
|
||||
return c.json({ error: '删除消息失败' }, 500)
|
||||
}
|
||||
})
|
||||
|
||||
// 获取未读消息数量
|
||||
messagesRoutes.get('/unread-count', withAuth, async (c) => {
|
||||
try {
|
||||
const apiClient = c.get('apiClient')
|
||||
|
||||
const user = c.get('user')
|
||||
if (!user) return c.json({ error: '未授权访问' }, 401)
|
||||
|
||||
const count = await apiClient.database.table('user_messages')
|
||||
.where('user_id', user.id)
|
||||
.where('status', MessageStatus.UNREAD)
|
||||
.where('is_deleted', 0)
|
||||
.clone()
|
||||
.count()
|
||||
|
||||
return c.json({ count: Number(count) })
|
||||
} catch (error) {
|
||||
console.error('获取未读消息数失败:', error)
|
||||
return c.json({ error: '获取未读消息数失败' }, 500)
|
||||
}
|
||||
})
|
||||
|
||||
return messagesRoutes
|
||||
}
|
||||
Reference in New Issue
Block a user