数据统计

dev
truthhun 1 year ago
parent d3f28a5ca0
commit 0c589190e5

@ -48,7 +48,7 @@ message ConfigSystem {
string favicon = 6; string favicon = 6;
string icp = 7; string icp = 7;
string analytics = 8; string analytics = 8;
string sitename = 9; string sitename = 9;
string copyright_start_year = 10; string copyright_start_year = 10;
string register_background = 11; string register_background = 11;
string login_background = 12; string login_background = 12;
@ -81,7 +81,22 @@ message Settings {
ConfigSystem system = 1; ConfigSystem system = 1;
ConfigFooter footer = 2; ConfigFooter footer = 2;
ConfigSecurity security = 3; ConfigSecurity security = 3;
// ConfigCaptcha captcha = 4; // ConfigCaptcha captcha = 4;
}
message Stats {
int64 user_count = 1;
int64 document_count = 2;
int64 category_count = 3;
int64 article_count = 4;
int64 comment_count = 5;
int64 banner_count = 6;
int64 friendlink_count = 7;
string os = 8;
string version = 9;
string hash = 10;
string build_at = 11;
int64 report_count = 12;
} }
service ConfigAPI { service ConfigAPI {
@ -106,4 +121,11 @@ service ConfigAPI {
get : '/api/v1/config/list', get : '/api/v1/config/list',
}; };
} }
//
rpc GetStats(google.protobuf.Empty) returns (Stats) {
option (google.api.http) = {
get : "/api/v1/stats"
};
}
} }

@ -3,6 +3,7 @@ package biz
import ( import (
"context" "context"
"fmt" "fmt"
"runtime"
pb "moredoc/api/v1" pb "moredoc/api/v1"
"moredoc/middleware/auth" "moredoc/middleware/auth"
@ -104,3 +105,32 @@ func (s *ConfigAPIService) GetSettings(ctx context.Context, req *emptypb.Empty)
return res, nil return res, nil
} }
func (s *ConfigAPIService) GetStats(ctx context.Context, req *emptypb.Empty) (res *pb.Stats, err error) {
res = &pb.Stats{
UserCount: 0,
DocumentCount: 0,
CategoryCount: 0,
ArticleCount: 0,
CommentCount: 0,
BannerCount: 0,
FriendlinkCount: 0,
Os: runtime.GOOS,
Version: "1.0.0",
Hash: "hash",
BuildAt: "2021-01-01 00:00:00",
}
res.UserCount, _ = s.dbModel.CountUser()
res.DocumentCount, _ = s.dbModel.CountDocument()
_, errPermission := s.checkPermission(ctx)
if errPermission == nil {
res.CategoryCount, _ = s.dbModel.CountCategory()
res.ArticleCount, _ = s.dbModel.CountArticle()
res.CommentCount, _ = s.dbModel.CountComment()
res.BannerCount, _ = s.dbModel.CountBanner()
res.FriendlinkCount, _ = s.dbModel.CountFriendlink()
res.ReportCount, _ = s.dbModel.CountReport()
}
return
}

@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -17,6 +17,7 @@ package cmd
import ( import (
"moredoc/service" "moredoc/service"
"moredoc/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -27,6 +28,9 @@ var serveCmd = &cobra.Command{
Short: "start a server", Short: "start a server",
Long: `start a server`, Long: `start a server`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
util.Version = Version
util.Hash = GitHash
util.BuildAt = BuildAt
service.Run(cfg, logger) service.Run(cfg, logger)
}, },
} }

@ -215,3 +215,8 @@ func (m *DBModel) checkArticleFile(article *Article) {
} }
} }
} }
func (m *DBModel) CountArticle() (count int64, err error) {
err = m.db.Model(&Article{}).Count(&count).Error
return
}

@ -136,3 +136,8 @@ func (m *DBModel) DeleteBanner(ids []int64) (err error) {
return return
} }
func (m *DBModel) CountBanner() (count int64, err error) {
err = m.db.Model(&Banner{}).Count(&count).Error
return
}

@ -163,3 +163,11 @@ func (m *DBModel) DeleteCategory(ids []int64) (err error) {
} }
return return
} }
func (m *DBModel) CountCategory() (count int64, err error) {
err = m.db.Model(&Category{}).Count(&count).Error
if err != nil {
m.logger.Error("CountCategory", zap.Error(err))
}
return
}

@ -226,3 +226,11 @@ func (m *DBModel) UpdateCommentStatus(ids []int64, status int32) (err error) {
} }
return return
} }
func (m *DBModel) CountComment() (count int64, err error) {
err = m.db.Model(&Comment{}).Count(&count).Error
if err != nil {
m.logger.Error("CountComment", zap.Error(err))
}
return
}

@ -59,9 +59,10 @@ func getPermissions() (permissions []Permission) {
{Title: "批量审核评论", Description: "", Method: "GRPC", Path: "/api.v1.CommentAPI/CheckComment"}, {Title: "批量审核评论", Description: "", Method: "GRPC", Path: "/api.v1.CommentAPI/CheckComment"},
{Title: "删除评论", Description: "", Method: "GRPC", Path: "/api.v1.CommentAPI/DeleteComment"}, {Title: "删除评论", Description: "", Method: "GRPC", Path: "/api.v1.CommentAPI/DeleteComment"},
{Title: "推荐文档", Description: "", Method: "GRPC", Path: "/api.v1.DocumentAPI/SetDocumentRecommend"}, {Title: "推荐文档", Description: "", Method: "GRPC", Path: "/api.v1.DocumentAPI/SetDocumentRecommend"},
{Title: "获取举报列表", Description: "", Method: "GRPC", Path: "/api.v1.ReportAPI/ListReport"}, {Title: "查询举报列表", Description: "", Method: "GRPC", Path: "/api.v1.ReportAPI/ListReport"},
{Title: "处理举报信息", Description: "", Method: "GRPC", Path: "/api.v1.ReportAPI/UpdateReport"}, {Title: "处理举报内容", Description: "", Method: "GRPC", Path: "/api.v1.ReportAPI/UpdateReport"},
{Title: "删除举报内容", Description: "", Method: "GRPC", Path: "/api.v1.ReportAPI/DeleteReport"}, {Title: "删除举报内容", Description: "", Method: "GRPC", Path: "/api.v1.ReportAPI/DeleteReport"},
{Title: "查看系统信息", Description: "", Method: "GRPC", Path: "/api.v1.ConfigAPI/GetStats"},
} }
return return
} }

@ -743,3 +743,15 @@ func (m *DBModel) SetDocumentRecommend(documentIds []int64, typ int32) (err erro
} }
return return
} }
func (m *DBModel) CountDocument(status ...int) (count int64, err error) {
db := m.db.Model(&Document{})
if len(status) > 0 {
db = db.Where("status in (?)", status)
}
err = db.Count(&count).Error
if err != nil {
m.logger.Error("CountDocument", zap.Error(err))
}
return
}

@ -116,3 +116,15 @@ func (m *DBModel) DeleteFriendlink(ids []int64) (err error) {
} }
return return
} }
func (m *DBModel) CountFriendlink(enable ...bool) (count int64, err error) {
db := m.db.Model(&Friendlink{})
if len(enable) > 0 {
db = db.Where("enable in ?", enable)
}
err = db.Count(&count).Error
if err != nil {
m.logger.Error("CountFriendlink", zap.Error(err))
}
return
}

@ -132,3 +132,12 @@ func (m *DBModel) GetReportByDocUser(docId, userId int64) (report Report, err er
err = m.db.Where("document_id = ? and user_id = ?", docId, userId).First(&report).Error err = m.db.Where("document_id = ? and user_id = ?", docId, userId).First(&report).Error
return return
} }
func (m *DBModel) CountReport(status ...bool) (count int64, err error) {
db := m.db.Model(&Report{})
if len(status) > 0 {
db = db.Where("status in ?", status)
}
err = db.Count(&count).Error
return
}

@ -470,3 +470,16 @@ func (m *DBModel) CanIPublishComment(userId int64) (defaultCommentStatus int8, e
return return
} }
func (s *DBModel) CountUser(status ...int) (count int64, err error) {
db := s.db.Model(&User{})
if len(status) > 0 {
db = db.Where("status in (?)", status)
}
err = db.Count(&count).Error
if err != nil {
s.logger.Error("CountUser", zap.Error(err))
return
}
return
}

@ -0,0 +1,10 @@
package util
var (
// Version 版本号
Version = "0.0.1"
// Hash 版本号
Hash = "hash"
// BuildAt 编译时间
BuildAt = "2006-01-02 00:00:00"
)

@ -22,3 +22,10 @@ export const getSettings = () => {
method: 'get', method: 'get',
}) })
} }
export const getStats = () => {
return service({
url: '/api/v1/stats',
method: 'get',
})
}

@ -1,16 +1,170 @@
<template> <template>
<div> <div class="page-admin-dashboard">
<h1>Welcome to MOREDOC!</h1> <el-card shadow="never">
<div slot="header">数据统计</div>
<el-descriptions class="margin-top" :column="2" border>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-tickets"></i>
文档
</template>
{{ stats.document_count || 0 }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-chat-dot-square"></i>
评论
</template>
{{ stats.comment_count || 0 }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-user"></i>
用户
</template>
{{ stats.user_count || 0 }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-s-grid"></i>
分类
</template>
{{ stats.category_count || 0 }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-warning"></i>
举报
</template>
{{ stats.report_count || 0 }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-picture-outline"></i>
横幅
</template>
{{ stats.banner_count || 0 }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<i class="el-icon-link"></i>
友链
</template>
{{ stats.friendlink_count || 0 }}
</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card shadow="never" class="mgt-20px">
<div slot="header">系统信息</div>
<el-descriptions class="margin-top" :column="1" border>
<el-descriptions-item>
<template slot="label"> 操作系统 </template>
{{ stats.os }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"> 程序名称 </template>
moredoc · 魔豆文库
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"> 程序版本 </template>
{{ stats.version }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"> 程序Hash </template>
{{ stats.hash }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"> 构建时间 </template>
{{ formatDatetime(stats.build_at) }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"> 程序开发 </template>
深圳市摩枫网络科技有限公司 <b>M</b>orefun <b>N</b>etwork
<b>T</b>echnology Co., <b>Ltd</b>.
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"> 服务支持邮箱 </template>
truthhun@mnt.ltd
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"> 服务支持官网 </template>
<a
href="https://mnt.ltd"
class="el-link el-link--primary"
target="_blank"
title="摩枫网络科技 MNT.Ltd"
>https://mnt.ltd</a
>
</el-descriptions-item>
</el-descriptions>
</el-card>
</div> </div>
</template> </template>
<script> <script>
import { getStats } from '~/api/config'
import { formatDatetime } from '~/utils/utils'
export default { export default {
layout: 'admin', layout: 'admin',
head() { head() {
return { return {
title: `面板 - MOREDOC · 魔豆文库`, title: `面板 - ${this.settings.system.sitename}`,
} }
}, },
data() {
return {
stats: {
admin_count: 0,
student_count: 0,
company_count: 0,
category_count: 0,
article_count: 0,
article_pending_count: 0,
comment_count: 0,
comment_pending_count: 0,
banner_count: 0,
friendlink_count: 0,
user_pending_count: 0,
os: '-',
version: '-',
hash: '-',
build_at: '',
},
}
},
computed: {
settings() {
return this.$store.state.setting.settings
},
},
created() {
this.getStats()
},
methods: {
formatDatetime,
async getStats() {
const res = await getStats()
if (res.status === 200) {
this.stats = {
...this.stats,
...res.data,
}
}
console.log(res)
},
},
} }
</script> </script>
<style lang="scss">
.page-admin-dashboard {
.el-descriptions-item__label.is-bordered-label {
width: 150px;
}
.systeminfo {
b {
color: crimson;
}
}
}
</style>

@ -53,12 +53,18 @@
<el-col :span="12"> <el-col :span="12">
<small>收录文档</small> <small>收录文档</small>
<div> <div>
<span class="el-link el-link--primary">3235</span> <span class="el-link el-link--primary">{{
stats.document_count || 0
}}</span>
</div> </div>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<small>注册用户</small> <small>注册用户</small>
<div><span class="el-link el-link--primary">872</span></div> <div>
<span class="el-link el-link--primary">{{
stats.user_count || 0
}}</span>
</div>
</el-col> </el-col>
</el-row> </el-row>
</el-card> </el-card>
@ -283,6 +289,7 @@ import UserAvatar from '~/components/UserAvatar.vue'
import { listBanner } from '~/api/banner' import { listBanner } from '~/api/banner'
import { listDocument, listDocumentForHome } from '~/api/document' import { listDocument, listDocumentForHome } from '~/api/document'
import { getSignedToday, signToday } from '~/api/user' import { getSignedToday, signToday } from '~/api/user'
import { getStats } from '~/api/config'
export default { export default {
components: { UserAvatar }, components: { UserAvatar },
data() { data() {
@ -296,6 +303,10 @@ export default {
sign: { sign: {
sign_at: 0, sign_at: 0,
}, },
stats: {
document_count: '-',
user_count: '-',
},
} }
}, },
head() { head() {
@ -315,6 +326,7 @@ export default {
this.listBanner(), this.listBanner(),
this.getDocuments(), this.getDocuments(),
this.getSignedToday(), this.getSignedToday(),
this.getStats(),
]) ])
}, },
methods: { methods: {
@ -374,6 +386,14 @@ export default {
console.log(res) console.log(res)
} }
}, },
async getStats() {
const res = await getStats()
if (res.status === 200) {
this.stats = res.data || {}
} else {
console.log(res)
}
},
login() { login() {
// //
this.$router.push('/login') this.$router.push('/login')

@ -108,7 +108,9 @@
本次搜索耗时 本次搜索耗时
<span class="el-link el-link--danger">{{ spend || '0.000' }}</span> <span class="el-link el-link--danger">{{ spend || '0.000' }}</span>
<span class="el-link el-link--primary">3235</span> <span class="el-link el-link--primary">{{
stats.document_count || '0'
}}</span>
篇文档中为您找到相关结果约 篇文档中为您找到相关结果约
<span class="el-link el-link--danger">{{ total || 0 }}</span> . <span class="el-link el-link--danger">{{ total || 0 }}</span> .
</div> </div>
@ -194,6 +196,7 @@
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { getStats } from '~/api/config'
import { searchDocument } from '~/api/document' import { searchDocument } from '~/api/document'
import { formatBytes, getIcon } from '~/utils/utils' import { formatBytes, getIcon } from '~/utils/utils'
export default { export default {
@ -229,6 +232,9 @@ export default {
total: 0, total: 0,
spend: '', spend: '',
keywords: [], keywords: [],
stats: {
document_count: '-',
},
} }
}, },
head() { head() {
@ -261,6 +267,7 @@ export default {
query.page = parseInt(query.page) || 1 query.page = parseInt(query.page) || 1
query.size = parseInt(query.size) || 10 query.size = parseInt(query.size) || 10
this.query = query this.query = query
this.getStats()
}, },
methods: { methods: {
formatBytes, formatBytes,
@ -276,6 +283,12 @@ export default {
}, },
}) })
}, },
async getStats() {
const res = await getStats()
if (res.status === 200) {
this.stats = res.data
}
},
async execSearch() { async execSearch() {
this.loading = true this.loading = true
const res = await searchDocument(this.query) const res = await searchDocument(this.query)

Loading…
Cancel
Save