非常好的选择!我们将添加更多分析图表并实现自定义日期范围的数据分析。这将为博客系统提供更深入的洞察。让我们开始实现这些功能:

第三十一部分:高级数据分析和自定义日期范围

  1. 首先,我们需要更新 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`),
});
  1. 创建新的迁移文件 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;`);
}
  1. 运行迁移:
bun run migrate
  1. 更新 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";
  });
  1. 更新 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],
    });
  });
  1. 更新 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>

这些更改实现了以下功能:

  1. 记录用户注册日期,以便进行用户增长分析。
  2. 添加了用户增长趋势图,显示随时间变化的新用户注册数量。
  3. 添加了评论活跃度图,显示每日评论数量。
  4. 实现了自定义日期范围的数据分析,用户可以选择特定的时间段进行分析。
  5. 更新了现有的分析数据(如热门文章和标签)以反映选定的日期范围。

主要改进包括:

  • 提供了更深入的用户行为洞察,包括用户增长趋势和评论活跃度。
  • 增加了数据分析的灵活性,允许管理员关注特定时间段的数据。
  • 使用 Plotly. Js 库创建了交互式图表,提升了数据可视化效果。
  • 保留了之前实现的标签云功能,为分析仪表板提供了全面的数据展示。

这个实现大大增强了博客系统的分析功能,使管理员能够更好地理解用户行为和内容表现。通过自定义日期范围,管理员可以灵活地分析不同时期的数据,识别趋势和模式。

接下来,我们可以考虑以下方向来进一步完善系统:

  1. 添加更多的分析指标,如平均阅读时间、跳出率等。
  2. 实现数据导出功能,允许管理员下载 CSV 或 Excel 格式的报告。
  3. 添加用户分群分析,了解不同类型用户的行为特征。
  4. 实现 A/B 测试功能,以优化内容和用户界面。
  5. 集成第三方分析工具,如 Google Analytics 或 Mixpanel。

你希望继续哪个方向,或者有其他想法吗?