Skip to content

Commit

Permalink
feat: 添加用户状态的接口以及对应面板
Browse files Browse the repository at this point in the history
  • Loading branch information
caixw committed Jan 6, 2025
1 parent 8a76768 commit af0516c
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 11 deletions.
6 changes: 6 additions & 0 deletions admin/src/messages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ const messages = {
dashboard: 'Dashboard',
logout: 'logout',
settings: 'Settings',
allMembers: 'All',
monthMembers: 'Month',
weekMembers: 'Week',
dayMembers: 'Day',
onlineMembers: 'online',
activeMembers: 'active',

login: 'login',
username: 'username',
Expand Down
7 changes: 7 additions & 0 deletions admin/src/messages/zh-Hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ const messages: Messages = {
dashboard: '控制台',
logout: '退出',
settings: '设置',
allMembers: '所有会员',
monthMembers: '过去 30 天新增会员',
weekMembers: '过去 7 天新增会员',
dayMembers: '今日新增会员',
onlineMembers: '当前在线会员',
activeMembers: '活跃会员',


login: '登录',
username: '账号',
Expand Down
6 changes: 6 additions & 0 deletions admin/src/pages/current/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Pages } from '@/pages/pages';
import { Dashboard } from './dashboard';
import { Login, Props as LoginProps } from './login';
import { Logout } from './logout';
import { MemStatistic } from './memstatistic';
import { componens, PassportComponents } from './passport';
import { Profile } from './profile';
import { SecurityLogs } from './securitylogs';
Expand All @@ -19,6 +20,11 @@ import { Settings } from './settings';
*/
export class current implements Pages {
static #passports: Map<string, PassportComponents> = componens;

/**
* 会员统计信息面板
*/
static MemberStatisticPanel = MemStatistic;

/**
* 提供当前用户的仪表盘
Expand Down
58 changes: 58 additions & 0 deletions admin/src/pages/current/memstatistic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: 2025 caixw
//
// SPDX-License-Identifier: MIT

import { Description, useApp } from '@/components';
import { JSX, createSignal, onMount } from 'solid-js';

export function MemStatistic(): JSX.Element {
const [statistic, setStatistic] = createSignal<Statistic>({
online: 0,
active: 0,
all: 0,
month: 0,
week: 0,
day: 0,
});

const ctx = useApp();

onMount(async () => {
const r = await ctx.api.get<Statistic>('/statistic/member');
if (!r.ok) {
await ctx.outputProblem(r.body);
return;
}
setStatistic(r.body!);
});

return <div class="c--memstatistic">
<Description class="item" icon='group' title={ctx.locale().t('_i.page.current.allMembers')}>
<p class="text-5xl">{statistic().all}</p>
</Description>
<Description class="item" icon='calendar_month' title={ctx.locale().t('_i.page.current.monthMembers')}>
<p class="text-5xl">{statistic().month}</p>
</Description>
<Description class="item" icon='calendar_view_week' title={ctx.locale().t('_i.page.current.weekMembers')}>
<p class="text-5xl">{statistic().week}</p>
</Description>
<Description class="item" icon='calendar_today' title={ctx.locale().t('_i.page.current.dayMembers')}>
<p class="text-5xl">{statistic().day}</p>
</Description>
<Description class="item" icon='person_check' title={ctx.locale().t('_i.page.current.activeMembers')}>
<p class="text-5xl">{statistic().active}</p>
</Description>
<Description class="item" icon='record_voice_over' title={ctx.locale().t('_i.page.current.onlineMembers')}>
<p class="text-5xl">{statistic().online}</p>
</Description>
</div>;
}

interface Statistic {
online: number;
active: number;
all: number;
month: number;
week: number;
day: number;
}
8 changes: 8 additions & 0 deletions admin/src/pages/current/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,12 @@
@apply flex flex-col rounded-md border border-palette-bg-low px-3 py-2;
}
}

.c--memstatistic {
@apply flex flex-row flex-wrap gap-5 items-center justify-center;

.item {
@apply flex-grow border border-palette-bg-low rounded-md text-center;
}
}
}
1 change: 1 addition & 0 deletions cmd/admin/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const system = pages.system.build('/system');
const members = pages.members.build('/members');
const current = pages.current.build('/current', () => {
return <>
<pages.current.MemberStatisticPanel />
<div class="flex gap-4">
<Card class="basis-1/2">1/2</Card>
<Card class="basis-1/2">1/2</Card>
Expand Down
4 changes: 4 additions & 0 deletions cmfx/modules/member/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ func Load(mod *cmfx.Module, conf *Config, up *upload.Module, adminMod *admin.Mod
o.Tag("member").
Desc(web.Phrase("add member type api"), nil).
Body(tag.TagTO{}, false, nil, nil)
})).
Get("/statistic/member", m.adminGetStatcstic, adminAPI(func(o *openapi.Operation) {
o.Tag("statistic", "member").Desc(web.Phrase("get member statistic"), nil).
Response200(user.Statistic{})
}))

// member 接口
Expand Down
8 changes: 8 additions & 0 deletions cmfx/modules/member/route_admins.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,11 @@ func (m *Module) adminPutLevel(ctx *web.Context) web.Responser {
func (m *Module) adminPutType(ctx *web.Context) web.Responser {
return m.types.HandlePutTag(ctx, "id")
}

func (m *Module) adminGetStatcstic(ctx *web.Context) web.Responser {
s, err := m.user.Statistic(ctx.Begin())
if err != nil {
return ctx.Error(err, "")
}
return web.OK(s)
}
31 changes: 27 additions & 4 deletions cmfx/user/models_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
// SPDX-FileCopyrightText: 2024 caixw
// SPDX-FileCopyrightText: 2024-2025 caixw
//
// SPDX-License-Identifier: MIT

package user
package user_test

import (
"testing"
"time"

"github.com/issue9/assert/v4"
"github.com/issue9/orm/v6/core"
"github.com/issue9/web/openapi"

"github.com/issue9/cmfx/cmfx/initial/test"
"github.com/issue9/cmfx/cmfx/user"
"github.com/issue9/cmfx/cmfx/user/usertest"
)

var (
_ core.PrimitiveTyper = StateNormal
_ openapi.OpenAPISchema = StateDeleted
_ core.PrimitiveTyper = user.StateNormal
_ openapi.OpenAPISchema = user.StateDeleted
)

func TestModule_Statistic(t *testing.T) {
a := assert.New(t, false)
suite := test.NewSuite(a)
defer suite.Close()

m := usertest.NewModule(suite)
s, err := m.Statistic(time.Now())
a.NotError(err).NotNil(s).Equal(s, &user.Statistic{
All: 1,
Month: 1,
Week: 1,
Day: 1,
})
}
45 changes: 43 additions & 2 deletions cmfx/user/module.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022-2024 caixw
// SPDX-FileCopyrightText: 2022-2025 caixw
//
// SPDX-License-Identifier: MIT

Expand All @@ -7,10 +7,12 @@ package user
import (
"context"
"net/http"
"time"

"github.com/issue9/cache"
"github.com/issue9/events"
"github.com/issue9/mux/v9/header"
"github.com/issue9/orm/v6"
"github.com/issue9/orm/v6/sqlbuilder"
"github.com/issue9/sliceutil"
"github.com/issue9/web"
Expand Down Expand Up @@ -129,7 +131,7 @@ func (m *Module) GetUserByUsername(username string) (*User, error) {
//
// alias 为 [User] 表的别名,on 为 LEFT JOIN 的条件。
func (m *Module) LeftJoin(sql *sqlbuilder.SelectStmt, alias, on string, states []State) {
sql.Columns(alias+".state", alias+".no", alias+".created", alias+".username").
sql.Columns(alias+".state", alias+".no", alias+".created", alias+".last", alias+".username").
Join("LEFT", m.mod.DB().TablePrefix()+(&User{}).TableName(), alias, on).
AndIn(alias+".state", sliceutil.AnySlice(states)...)
}
Expand All @@ -147,3 +149,42 @@ func (m *Module) OnAdd(f func(*User)) context.CancelFunc { return m.addEvent.Sub

// OnDelete 删除用户时的事件
func (m *Module) OnDelete(f func(*User)) context.CancelFunc { return m.addEvent.Subscribe(f) }

// Statistic 用户统计信息
type Statistic struct {
XMLName struct{} `orm:"-" xml:"statistic" json:"-" yaml:"-" cbor:"-"`

Online int `orm:"name(online)" xml:"online" json:"online" yaml:"online" cbor:"online"` // 在线用户数,10 分钟之内登录的用户。
Active int `orm:"name(active)" xml:"active" json:"active" yaml:"active" cbor:"active"` // 活跃用户数,一月之内登录的用户。
All int `orm:"name(all)" xml:"all" json:"all" yaml:"all" cbor:"all"` // 所有用户数
Month int `orm:"name(month)" xml:"month" json:"month" yaml:"month" cbor:"month"` // 本月新增用户数
Week int `orm:"name(week)" xml:"week" json:"week" yaml:"week" cbor:"week"` // 本周新增用户数
Day int `orm:"name(day)" xml:"day" json:"day" yaml:"day" cbor:"day"` // 今日新增用户数
}

// Statistic 统计用户信息
//
// now 为当前时间,用于往前推荐对应时间的各类数据。
func (m *Module) Statistic(now time.Time) (*Statistic, error) {
online := now.Add(-10 * time.Minute)
active := now.AddDate(0, 0, -30)
month := now.AddDate(0, -1, 0)
week := now.AddDate(0, 0, -7)
day := now.AddDate(0, 0, -1)

sql := m.mod.DB().SQLBuilder().Select().From(orm.TableName(&User{})).
Count(`
COUNT(CASE WHEN last > ? THEN id END) as online,
COUNT(CASE WHEN last > ? THEN id END) as active,
COUNT(CASE WHEN created > ? THEN id END) as month,
COUNT(CASE WHEN created > ? THEN id END) as week,
COUNT(CASE WHEN created > ? THEN id END) as day,
COUNT(*) as {all}
`, online, active, month, week, day)

s := &Statistic{}
if _, err := sql.QueryObject(true, s); err != nil {
return nil, err
}
return s, nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/issue9/events v0.9.1
github.com/issue9/logs/v7 v7.6.4
github.com/issue9/mux/v9 v9.1.2
github.com/issue9/orm/v6 v6.0.0-beta.3.0.20241018060335-bdbc5e5a6236
github.com/issue9/orm/v6 v6.0.0-beta.3.0.20250106072547-19dd3a7ecd5d
github.com/issue9/rands/v3 v3.0.1
github.com/issue9/scheduled v0.22.0
github.com/issue9/sliceutil v0.17.0
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ github.com/issue9/logs/v7 v7.6.4 h1:NFM4oM2ggLPPZNgDb8PAOSqELtJOx7PTwoFd3o814wk=
github.com/issue9/logs/v7 v7.6.4/go.mod h1:6fIj8OABk/qWm1c3E9f9oXe0FWwRyoZNWh449D391Zk=
github.com/issue9/mux/v9 v9.1.2 h1:Ng5uv8bwsduZV6w14gv0HR78ahBs8Fm0JWlc7Xjtbd4=
github.com/issue9/mux/v9 v9.1.2/go.mod h1:CLtV5ZfIrAmfaC/qxn07wXc3GALI45GYCsdE3c/h9CQ=
github.com/issue9/orm/v6 v6.0.0-beta.3.0.20241018060335-bdbc5e5a6236 h1:stQsBZ8tAbpt6XwI+XCEmxFyXDxrpk0Fb8LeTpygdhM=
github.com/issue9/orm/v6 v6.0.0-beta.3.0.20241018060335-bdbc5e5a6236/go.mod h1:a0Pq7xdgbd9HGEI6LSRABMYJd7+RKFYnbab0xRyvff4=
github.com/issue9/orm/v6 v6.0.0-beta.3.0.20250106072547-19dd3a7ecd5d h1:XljGZuIgY+ZM1S1lxZzyrOFH4t2fAWdnquakjKE4vsk=
github.com/issue9/orm/v6 v6.0.0-beta.3.0.20250106072547-19dd3a7ecd5d/go.mod h1:76V9dRlED2JsgjCxvQWAlceGjn17Qspn3DGkAcCjhCI=
github.com/issue9/query/v3 v3.1.3 h1:Y6ETEYXxaKqhpM4lXPKCffhJ72VuKQbrAwgwHlacu0Y=
github.com/issue9/query/v3 v3.1.3/go.mod h1:a/W/+7iel9K+5rRT4AFAKR8+OJeV5axeF6tK9My4lNA=
github.com/issue9/rands/v3 v3.0.1 h1:EnX9WNushGgHCzoL/R5eBPaLfvjLO/c7CGHNgLK0JhY=
Expand Down Expand Up @@ -166,8 +166,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
modernc.org/sqlite v1.34.4 h1:sjdARozcL5KJBvYQvLlZEmctRgW9xqIZc2ncN7PU0P8=
modernc.org/sqlite v1.34.4/go.mod h1:3QQFCG2SEMtc2nv+Wq4cQCH7Hjcg+p/RMlS1XK+zwbk=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
Expand Down

0 comments on commit af0516c

Please sign in to comment.