非常好的选择!我们将添加更多分析图表并实现自定义日期范围的数据分析。这将为博客系统提供更深入的洞察。让我们开始实现这些功能:
第三十一部分:高级数据分析和自定义日期范围
- 首先,我们需要更新
src/db/schema.ts文件,添加一个新表来记录用户注册日期:
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
// ... 保留现有的表定义
export const userRegistrations = sqliteTable("user_registrations", {
id: integer("id").primaryKey(),
userId: integer("user_id").notNull().references(() => users.id),
registrationDate: integer("registration_date", { mode: "timestamp" }).notNull().default(sql`CURRENT_TIMESTAMP`),
});- 创建新的迁移文件
src/db/migrations/0015_add_user_registrations_table.ts:
export async function up(db) {
await db.run(sql`
CREATE TABLE user_registrations (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
registration_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
`);
// 为现有用户添加注册记录
await db.run(sql`
INSERT INTO user_registrations (user_id, registration_date)
SELECT id, created_at FROM users;
`);
}
export async function down(db) {
await db.run(sql`DROP TABLE IF EXISTS user_registrations;`);
}- 运行迁移:
bun run migrate- 更新
src/routes/auth.ts文件,在用户注册时添加记录:
import { db } from "../db";
import { users, userRegistrations } from "../db/schema";
export const authRoutes = new Elysia()
// ... 保留现有的路由
.post("/register", async ({ body, set }) => {
const { name, email, password } = body;
const hashedPassword = await Bun.password.hash(password);
const [newUser] = await db.insert(users).values({
name,
email,
password: hashedPassword,
}).returning();
await db.insert(userRegistrations).values({
userId: newUser.id,
});
set.redirect = "/login";
});- 更新
src/routes/analytics.ts文件,添加新的分析数据和自定义日期范围功能:
import { Elysia } from "elysia";
import { db } from "../db";
import { posts, postViews, comments, tags, userRegistrations } from "../db/schema";
import { sql } from "drizzle-orm";
export const analyticsRoutes = new Elysia()
.get("/analytics", async ({ query, render }) => {
const startDate = query.startDate ? new Date(query.startDate) : new Date(0);
const endDate = query.endDate ? new Date(query.endDate) : new Date();
const topPosts = await db
.select({
id: posts.id,
title: posts.title,
viewCount: postViews.viewCount,
})
.from(posts)
.leftJoin(postViews, eq(posts.id, postViews.postId))
.where(sql`${postViews.lastViewed} BETWEEN ${startDate.toISOString()} AND ${endDate.toISOString()}`)
.orderBy(desc(postViews.viewCount))
.limit(10)
.all();
const topTags = await db
.select({
name: tags.name,
count: sql<number>`count(*)`.as("count"),
})
.from(tags)
.innerJoin(postTags, eq(tags.id, postTags.tagId))
.innerJoin(posts, eq(postTags.postId, posts.id))
.where(sql`${posts.createdAt} BETWEEN ${startDate.toISOString()} AND ${endDate.toISOString()}`)
.groupBy(tags.name)
.orderBy(desc(sql`count(*)`))
.limit(20)
.all();
const totalPosts = await db
.select({ count: sql<number>`count(*)` })
.from(posts)
.where(sql`${posts.createdAt} BETWEEN ${startDate.toISOString()} AND ${endDate.toISOString()}`)
.get();
const totalComments = await db
.select({ count: sql<number>`count(*)` })
.from(comments)
.where(sql`${comments.createdAt} BETWEEN ${startDate.toISOString()} AND ${endDate.toISOString()}`)
.get();
const userGrowth = await db
.select({
date: sql<string>`date(${userRegistrations.registrationDate})`.as("date"),
count: sql<number>`count(*)`.as("count"),
})
.from(userRegistrations)
.where(sql`${userRegistrations.registrationDate} BETWEEN ${startDate.toISOString()} AND ${endDate.toISOString()}`)
.groupBy(sql`date(${userRegistrations.registrationDate})`)
.orderBy(sql`date(${userRegistrations.registrationDate})`)
.all();
const commentActivity = await db
.select({
date: sql<string>`date(${comments.createdAt})`.as("date"),
count: sql<number>`count(*)`.as("count"),
})
.from(comments)
.where(sql`${comments.createdAt} BETWEEN ${startDate.toISOString()} AND ${endDate.toISOString()}`)
.groupBy(sql`date(${comments.createdAt})`)
.orderBy(sql`date(${comments.createdAt})`)
.all();
return render("analytics", {
topPosts,
topTags,
totalPosts: totalPosts.count,
totalComments: totalComments.count,
userGrowth,
commentActivity,
startDate: startDate.toISOString().split('T')[0],
endDate: endDate.toISOString().split('T')[0],
});
});- 更新
src/views/analytics.eta文件,添加新的图表和日期选择器:
<% layout('./layouts/main.eta', { title: 'Analytics' }) %>
<h1>Analytics Dashboard</h1>
<form id="date-range-form">
<label for="startDate">Start Date:</label>
<input type="date" id="startDate" name="startDate" value="<%= it.startDate %>">
<label for="endDate">End Date:</label>
<input type="date" id="endDate" name="endDate" value="<%= it.endDate %>">
<button type="submit">Update</button>
</form>
<div class="dashboard">
<div class="stat-box">
<h2>Total Posts</h2>
<p><%= it.totalPosts %></p>
</div>
<div class="stat-box">
<h2>Total Comments</h2>
<p><%= it.totalComments %></p>
</div>
</div>
<h2>User Growth</h2>
<div id="user-growth-chart"></div>
<h2>Comment Activity</h2>
<div id="comment-activity-chart"></div>
<h2>Top 10 Most Viewed Posts</h2>
<table>
<thead>
<tr>
<th>Title</th>
<th>Views</th>
</tr>
</thead>
<tbody>
<% it.topPosts.forEach(post => { %>
<tr>
<td><a href="/posts/<%= post.id %>"><%= post.title %></a></td>
<td><%= post.viewCount %></td>
</tr>
<% }) %>
</tbody>
</table>
<h2>Top Tags</h2>
<div id="tag-cloud"></div>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script>
// Tag cloud code (keep existing code)
// User Growth Chart
const userGrowthData = <%= JSON.stringify(it.userGrowth) %>;
Plotly.newPlot('user-growth-chart', [{
x: userGrowthData.map(d => d.date),
y: userGrowthData.map(d => d.count),
type: 'scatter'
}], {
title: 'User Growth Over Time',
xaxis: { title: 'Date' },
yaxis: { title: 'New Users' }
});
// Comment Activity Chart
const commentActivityData = <%= JSON.stringify(it.commentActivity) %>;
Plotly.newPlot('comment-activity-chart', [{
x: commentActivityData.map(d => d.date),
y: commentActivityData.map(d => d.count),
type: 'bar'
}], {
title: 'Comment Activity Over Time',
xaxis: { title: 'Date' },
yaxis: { title: 'Number of Comments' }
});
// Date range form submission
document.getElementById('date-range-form').addEventListener('submit', function(e) {
e.preventDefault();
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
window.location.href = `/analytics?startDate=${startDate}&endDate=${endDate}`;
});
</script>
<style>
/* Keep existing styles */
#user-growth-chart, #comment-activity-chart {
width: 100%;
height: 400px;
margin-bottom: 2em;
}
</style>这些更改实现了以下功能:
- 记录用户注册日期,以便进行用户增长分析。
- 添加了用户增长趋势图,显示随时间变化的新用户注册数量。
- 添加了评论活跃度图,显示每日评论数量。
- 实现了自定义日期范围的数据分析,用户可以选择特定的时间段进行分析。
- 更新了现有的分析数据(如热门文章和标签)以反映选定的日期范围。
主要改进包括:
- 提供了更深入的用户行为洞察,包括用户增长趋势和评论活跃度。
- 增加了数据分析的灵活性,允许管理员关注特定时间段的数据。
- 使用 Plotly. Js 库创建了交互式图表,提升了数据可视化效果。
- 保留了之前实现的标签云功能,为分析仪表板提供了全面的数据展示。
这个实现大大增强了博客系统的分析功能,使管理员能够更好地理解用户行为和内容表现。通过自定义日期范围,管理员可以灵活地分析不同时期的数据,识别趋势和模式。
接下来,我们可以考虑以下方向来进一步完善系统:
- 添加更多的分析指标,如平均阅读时间、跳出率等。
- 实现数据导出功能,允许管理员下载 CSV 或 Excel 格式的报告。
- 添加用户分群分析,了解不同类型用户的行为特征。
- 实现 A/B 测试功能,以优化内容和用户界面。
- 集成第三方分析工具,如 Google Analytics 或 Mixpanel。
你希望继续哪个方向,或者有其他想法吗?