增加排行榜服务

pull/361/head
HXY 2 years ago
parent 82735c5592
commit 20cec3faa3

@ -0,0 +1,69 @@
package v1
import (
"github.com/alimy/mir/v4"
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/model/web"
"net/http"
)
type Rank interface {
_default_
GetHighQuality() ([]*web.GetHighQualityRankingResp, mir.Error)
mustEmbedUnimplementedRankServant()
//GetRankList(req *web.GetRankListReq) (interface{}, interface{})
//Chain() gin.HandlersChain
}
// RegisterRankServant 注册路由
func RegisterRankServant(e *gin.Engine, s Rank) {
router := e.Group("v1")
// use chain for router
//middlewares := s.Chain()
//router.Use(middlewares...)
//注册路由
router.Handle("GET", "/rank/highquality", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
//获取GetHighQuality完成后的数据然后将其放到GetHighQualityResp中并返回
resp := new(web.GetHighQualityResp)
highQualityRanking, err := s.GetHighQuality()
resp.List = highQualityRanking
s.Render(c, resp, err)
})
//获取所有的rank
//router.Handle("GET", "/rank/list", func(c *gin.Context) {
// select {
// case <-c.Request.Context().Done():
// return
// default:
// }
// req := new(web.GetRankListReq)
// var bv _binding_ = req
// if err := bv.Bind(c); err != nil {
// s.Render(c, nil, err)
// return
// }
// //返回值
// resp, err := s.GetRankList(req)
// s.Render(c, resp, err)
//}
}
// UnimplementedRankServant can be embedded to have forward compatible implementations.
type UnimplementedRankServant struct{}
// GetHighQuality 设置RegisterRankServant的默认实现
func (UnimplementedRankServant) GetHighQuality() (*web.GetHighQualityRankingResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedRankServant) mustEmbedUnimplementedRankServant() {}

@ -57,7 +57,6 @@ func RegisterKeyQueryServant(e *gin.Engine, s KeyQuery) {
resp, err := s.DeleteKeyDetail(req)
s.Render(c, resp, err)
})
}
type UnimplementedShareKeyServant struct{}

@ -37,6 +37,9 @@ type DataService interface {
// share_key服务
ShareKeyService
//排行榜服务
RankService
}
// WebDataServantA Web数据服务集成(版本A)

@ -0,0 +1,13 @@
package core
import (
"github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr"
)
type (
GetHighQualityRankingResp = dbr.GetHighQualityRankingResp
)
type RankService interface {
GetHighQualityRanking() ([]*GetHighQualityRankingResp, error)
}

@ -0,0 +1,24 @@
package dbr
type Rank struct {
UserName string `json:"user_name"`
ShareKey string `json:"share_key"`
AllDownLoadCount int `json:"all_download_count"`
DownloadCountPerWeek int `json:"download_count_per_week"`
DownloadCountPerMonth int `json:"download_count_per_month"`
}
type HighQualityRanking struct {
UserName string `json:"user_name"`
UserId int `json:"user_id"`
Avatar string `json:"avatar"`
}
// GetHighQualityRankingResp 优质榜单返回数据
type GetHighQualityRankingResp struct {
UserName string `json:"name"`
Avatar string `json:"avatar"`
PostCount int64 `json:"post_count"`
LikesCount int64 `json:"likes"`
ComprehensiveScore int64 `json:"comprehensive_score"`
}

@ -45,6 +45,7 @@ type dataSrv struct {
core.SecurityService
core.AttachmentCheckService
core.ShareKeyService
core.RankService
}
type webDataSrvA struct {
@ -114,6 +115,7 @@ func NewDataService() (core.DataService, core.VersionInfo) {
SecurityService: newSecurityService(db, pvs),
AttachmentCheckService: security.NewAttachmentCheckService(),
ShareKeyService: NewShareKeyService(db),
RankService: NewRankService(db),
}
return ds, ds
}

@ -0,0 +1,143 @@
package jinzhu
import (
"fmt"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/core/ms"
"gorm.io/gorm"
"sort"
"strconv"
"strings"
)
type RankService struct {
db *gorm.DB
}
func NewRankService(db *gorm.DB) *RankService {
return &RankService{db: db}
}
type UserPosts struct {
UserId int64
PostId []int64
}
type UserScore struct {
//用户id
UserId int64
//用户名
Username string
//用户头像
Avatar string
//帖子数量&得分一个帖子是1分
PostCount int64
//图像得分一个图像加2分
ImageCount int64
//视频得分一个视频加2分
VideoCount int64
//点赞总数
LikeCount int64
//评论总数
CommentCount int64
//分享码得分
ShareCodeCount int64
//总分
Score int64
}
// GetHighQualityRanking 优质榜单函数
func (s RankService) GetHighQualityRanking() ([]*core.GetHighQualityRankingResp, error) {
//获取p_post表的所有数据
var posts []*ms.Post
err := s.db.Table("p_post").Find(&posts).Error
//一个用户对应一个总分建立一个map
var userScores = make(map[int64]*UserScore)
//判断posts是否为空
if len(posts) == 0 {
fmt.Print("posts is empty")
return nil, nil
}
for _, post := range posts {
//根据user_id获取用户信息其中包括用户名用户头像
var user ms.User
//如果userScores中没有该用户的信息则新建一个用户信息
if _, ok := userScores[post.UserID]; !ok {
userScores[post.UserID] = &UserScore{}
err = s.db.Table("p_user").Select("id,username,avatar").Where("id = ?", post.UserID).Find(&user).Error
if err != nil {
fmt.Print("get user info error")
return nil, err
}
userScores[post.UserID].Username = user.Username
userScores[post.UserID].Avatar = user.Avatar
}
var userPostContent []*ms.PostContent
userScores[post.UserID].LikeCount += post.UpvoteCount
userScores[post.UserID].CommentCount += post.CommentCount
//根据post_id查询p_post_content表获取type
err = s.db.Table("p_post_content").Select("post_id,type,content").Where("post_id = ?", post.ID).Find(&userPostContent).Error
if err != nil {
return nil, err
}
//userPostCount[post.UserId] = userPostCount[post.UserId] + 1
//计算每个用户发帖是否存在正文、图像、视频即查询p_post_content表若帖子id对应的type存在3或4则存在图像或视频
for _, postContent := range userPostContent {
//有正文内容的帖子加1分
if postContent.Type == 2 {
userScores[post.UserID].PostCount = userScores[post.UserID].PostCount + 1
//判断正文字符串中是否包含其用户名若存在则加5分
var userNameStr = userScores[post.UserID].Username + "-"
if strings.Index(postContent.Content, userNameStr) != -1 {
userScores[post.UserID].ShareCodeCount = userScores[post.UserID].ShareCodeCount + 5
}
}
if postContent.Type == 3 {
//总分加2
userScores[post.UserID].ImageCount = userScores[post.UserID].ImageCount + 2
} else if postContent.Type == 4 {
//总分加2
userScores[post.UserID].VideoCount = userScores[post.UserID].VideoCount + 2
}
}
}
//计算每个用户的总分
for _, userScore := range userScores {
userScore.Score = (userScore.PostCount + userScore.ImageCount + userScore.VideoCount + userScore.ShareCodeCount) * ((userScore.LikeCount*3 + userScore.CommentCount) / 10)
}
// 对userScore排序
var userScoreSlice []*UserScore
for _, userScore := range userScores {
userScoreSlice = append(userScoreSlice, userScore)
}
sort.Slice(userScoreSlice, func(i, j int) bool {
return userScoreSlice[i].Score > userScoreSlice[j].Score
})
var rank []*core.GetHighQualityRankingResp
var i = 0
//将userScoreSlice中的数据放到rank中
for _, userScore := range userScoreSlice {
if i >= 5 {
break
}
i++
fmt.Print(userScore.Username + " " + strconv.FormatInt(userScore.CommentCount, 10) +
" " + strconv.FormatInt(userScore.LikeCount, 10) +
" " + strconv.FormatInt(userScore.ImageCount, 10) +
" " + strconv.FormatInt(userScore.PostCount, 10) +
" " + strconv.FormatInt(userScore.VideoCount, 10) +
" " + strconv.FormatInt(userScore.ShareCodeCount, 10) +
" " + strconv.FormatInt(userScore.Score, 10) +
"\n")
rank = append(rank, &core.GetHighQualityRankingResp{
UserName: userScore.Username,
Avatar: userScore.Avatar,
PostCount: userScore.PostCount,
LikesCount: userScore.LikeCount,
ComprehensiveScore: userScore.Score,
})
}
return rank, nil
}

@ -0,0 +1,37 @@
package web
import (
"github.com/alimy/mir/v4"
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/pkg/convert"
)
// GetHighQualityResp 构造相应参数包含一个GetHighQualityRankingResp数组
type GetHighQualityResp struct {
List any `json:"list"`
}
type GetHighQualityRankingResp struct {
UserName string `json:"name"`
Avatar string `json:"avatar"`
PostCount int64 `json:"post_count"`
LikesCount int64 `json:"likes"`
ComprehensiveScore int64 `json:"comprehensive_score"`
}
type GetRankListReq struct {
ListType string `json:"list_type"`
}
type GetRankListResp struct {
UserName string `json:"name"`
Avatar string `json:"avatar"`
PostCount int64 `json:"post_count"`
LikesCount int64 `json:"likes"`
ComprehensiveScore int64 `json:"comprehensive_score"`
}
func (g *GetRankListReq) Bind(c *gin.Context) mir.Error {
g.ListType = convert.StrTo(c.Query("list_type")).String()
return nil
}

@ -36,7 +36,7 @@ type KeyInfo struct {
type GetUserKeysResp base.PageResp
// 逻辑删除服务
// DeleteKeyReq 逻辑删除服务
type DeleteKeyReq struct {
ShareKey string `json:"share_key"`
UserId int64

@ -97,5 +97,7 @@ var (
ErrDeleteUserKeyFailed = xerror.NewError(11004, "删除用户Share Key失败")
ErrGetUserShareKeyCountFailed = xerror.NewError(11005, "获取用户Share Key数量失败")
ErrGetHighQualityRankingFailed = xerror.NewError(12001, "获取优质帖子排行榜失败")
ErrNotImplemented = xerror.NewError(10501, "功能未实现")
)

@ -0,0 +1,57 @@
package web
import (
"github.com/alimy/mir/v4"
"github.com/gin-gonic/gin"
api "github.com/rocboss/paopao-ce/auto/api/v1"
"github.com/rocboss/paopao-ce/internal/model/web"
"github.com/rocboss/paopao-ce/internal/servants/base"
"github.com/rocboss/paopao-ce/internal/servants/chain"
"github.com/sirupsen/logrus"
)
var (
_ api.Rank = (*rankSrv)(nil)
)
type rankSrv struct {
api.UnimplementedRankServant
*base.DaoServant
}
func (s *rankSrv) Chain() gin.HandlersChain {
return gin.HandlersChain{chain.JWT()}
}
func (s *rankSrv) GetHighQuality() ([]*web.GetHighQualityRankingResp, mir.Error) {
if s.Ds == nil {
logrus.Errorf("GetKeyDetail err: %s", web.ErrDsNil)
return nil, web.ErrDsNil
} else {
logrus.Info("GetKeyDetail success")
}
//调用数据源的方式查询所有的rank信息
ranks, err := s.Ds.GetHighQualityRanking()
if err != nil {
return nil, web.ErrGetHighQualityRankingFailed
}
//将查询到的rank信息转换为GetHighQualityRankingResp结构体
var rankInfos []*web.GetHighQualityRankingResp
for _, rank := range ranks {
rankInfos = append(rankInfos, &web.GetHighQualityRankingResp{
UserName: rank.UserName,
Avatar: rank.Avatar,
PostCount: rank.PostCount,
LikesCount: rank.LikesCount,
ComprehensiveScore: rank.ComprehensiveScore,
})
}
return rankInfos, nil
}
func NewRankServant(ds *base.DaoServant) api.Rank {
return &rankSrv{
DaoServant: ds,
}
}

@ -33,6 +33,7 @@ func RouteWeb(e *gin.Engine) {
api.RegisterPrivServant(e, newPrivSrv(ds, oss))
api.RegisterPubServant(e, newPubSrv(ds))
api.RegisterKeyQueryServant(e, NewShareKeyServant(ds))
api.RegisterRankServant(e, NewRankServant(ds))
// regster servants if needed by configure
cfg.In(cfg.Actions{
"Alipay": func() {

@ -22,6 +22,14 @@ export const getTags = (
});
};
/** 获取优质帖榜 */
export const getHighQuailty = (): Promise<NetReq.PostGetHighQuailty> => {
return request({
method: "get",
url: "/v1/rank/highquality",
});
}
/** 获取动态详情 */
export const getPost = (
params: NetParams.PostGetPost

@ -13,7 +13,14 @@
</template>
</n-input>
</div>
<n-card v-if="showFollowTopics" class="hottopic-wrap" title="关注专题" embedded :bordered="false" size="small">
<n-card
v-if="showFollowTopics"
class="hottopic-wrap"
title="关注专题"
embedded
:bordered="false"
size="small"
>
<n-spin :show="loading">
<div class="hot-tag-item" v-for="tag in followTags" :key="tag.id">
<router-link
@ -35,7 +42,13 @@
</div>
</n-spin>
</n-card>
<n-card class="hottopic-wrap" title="热门专题" embedded :bordered="false" size="small">
<n-card
class="hottopic-wrap"
title="热门专题"
embedded
:bordered="false"
size="small"
>
<n-spin :show="loading">
<div class="hot-tag-item" v-for="tag in hotTags" :key="tag.id">
<router-link
@ -61,63 +74,113 @@
<div class="copyright">&copy; {{ copyrightTop }}</div>
<div>
<n-space>
<a
:href="copyrightLeftLink"
target="_blank"
class="hash-link"
>
<a :href="copyrightLeftLink" target="_blank" class="hash-link">
{{ copyrightLeft }}
</a>
<a
:href="copyrightRightLink"
target="_blank"
class="hash-link"
>
<a :href="copyrightRightLink" target="_blank" class="hash-link">
{{ copyrightRight }}
</a>
</n-space>
</div>
</n-card>
<!-- <n-card class="hottopic-wrap" title="排行榜" embedded :bordered="false" size="small">
<n-card
class="hottopic-wrap"
title="排行榜"
embedded
:bordered="false"
size="small"
>
<n-spin :show="loading">
~
<div
class="ranking-item"
v-for="(highQuality, index) in rankingList"
:key="highQuality.id"
>
<div class="ranking-number">{{ index + 1 }}</div> <!-- -->
<div class="ranking-avatar">
<!-- -->
<img :src="highQuality.avatar" alt="User Avatar" />
</div>
<div class="user-name">{{ highQuality.name }}</div>
<div class="ranking-info">
<div class="name-stats">
<div class="downloads">
<div class="download-box">
<div class="download-value">{{ highQuality.comprehensive_score }}</div>
</div>
</div>
<div class="stats">
<div class="stat-item">
<div class="stat-value">{{ highQuality.post_count }} </div>
</div>
<div class="stat-drop">·</div>
<!-- -->
<div class="stat-item">
<div class="stat-value">{{ highQuality.likes }} </div>
</div>
</div>
</div>
</div>
</div>
</n-spin>
</n-card> -->
</n-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed, watch } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { getTags } from '@/api/post';
import { Search } from '@vicons/ionicons5';
import { ref, onMounted, computed, watch } from "vue";
import { useStore } from "vuex";
import { useRouter } from "vue-router";
import { getHighQuailty, getTags } from "@/api/post";
import { Search } from "@vicons/ionicons5";
import { ChevronForward } from "@vicons/ionicons5";
const hotTags = ref<Item.TagProps[]>([]);
const followTags = ref<Item.TagProps[]>([]);
const loading = ref(false);
const keyword = ref('');
const keyword = ref("");
const store = useStore();
const router = useRouter();
const copyrightTop = import.meta.env.VITE_COPYRIGHT_TOP
const copyrightLeft = import.meta.env.VITE_COPYRIGHT_LEFT
const copyrightLeftLink = import.meta.env.VITE_COPYRIGHT_LEFT_LINK
const copyrightRight = import.meta.env.VITE_COPYRIGHT_RIGHT
const copyrightRightLink = import.meta.env.VITE_COPYRIGHT_RIGHT_LINK
const rightFollowTopicMaxSize = Number(import.meta.env.VITE_RIGHT_FOLLOW_TOPIC_MAX_SIZE)
const rightHotTopicMaxSize = Number(import.meta.env.VITE_RIGHT_HOT_TOPIC_MAX_SIZE)
const copyrightTop = import.meta.env.VITE_COPYRIGHT_TOP;
const copyrightLeft = import.meta.env.VITE_COPYRIGHT_LEFT;
const copyrightLeftLink = import.meta.env.VITE_COPYRIGHT_LEFT_LINK;
const copyrightRight = import.meta.env.VITE_COPYRIGHT_RIGHT;
const copyrightRightLink = import.meta.env.VITE_COPYRIGHT_RIGHT_LINK;
const rightFollowTopicMaxSize = Number(
import.meta.env.VITE_RIGHT_FOLLOW_TOPIC_MAX_SIZE
);
const rightHotTopicMaxSize = Number(
import.meta.env.VITE_RIGHT_HOT_TOPIC_MAX_SIZE
);
// 模拟排行榜数据
const rankingList = ref<Item.highqualityProps[]>([]);
//获取排行榜数据
const locadHeighQuailtyRankingList = () => {
loading.value = true;
getHighQuailty()
.then((res) => {
rankingList.value = res.list;
loading.value = false;
})
.catch((err) => {
loading.value = false;
});
}
const loadHotTags = () => {
loading.value = true;
getTags({
type: 'hot_extral',
type: "hot_extral",
num: rightHotTopicMaxSize,
extral_num: rightFollowTopicMaxSize,
})
.then((res) => {
hotTags.value = res.topics;
followTags.value = res.extral_topics??[];
showFollowTopics.value = true
followTags.value = res.extral_topics ?? [];
showFollowTopics.value = true;
loading.value = false;
})
.catch((err) => {
@ -126,14 +189,14 @@ const loadHotTags = () => {
};
const formatQuoteNum = (num: number) => {
if (num >= 1000) {
return (num / 1000).toFixed(1) + 'k';
return (num / 1000).toFixed(1) + "k";
}
return num;
};
const handleSearch = () => {
router.push({
name: 'home',
name: "home",
query: {
q: keyword.value,
},
@ -141,7 +204,7 @@ const handleSearch = () => {
};
const showFollowTopics = computed({
get: () => {
return store.state.userLogined && followTags.value.length !==0;
return store.state.userLogined && followTags.value.length !== 0;
},
set: (newVal) => {
// do nothing
@ -150,7 +213,7 @@ const showFollowTopics = computed({
watch(
() => ({
refreshTopicFollow: store.state.refreshTopicFollow,
userLogined: store.state.userLogined
userLogined: store.state.userLogined,
}),
(to, from) => {
if (to.refreshTopicFollow !== from.refreshTopicFollow || to.userLogined) {
@ -160,14 +223,23 @@ watch(
);
onMounted(() => {
loadHotTags();
locadHeighQuailtyRankingList();
});
</script>
<style lang="less" scoped>
.rightbar-wrap::-webkit-scrollbar {
width: 0; /* 隐藏滚动条的宽度 */
height: 0; /* 隐藏滚动条的高度 */
}
.rightbar-wrap {
width: 240px;
position: fixed;
left: calc(50% + var(--content-main) / 2 + 10px);
max-height: calc(100vh); /* 调整高度 */
overflow: auto;
.search-wrap {
margin: 12px 0;
}
@ -210,6 +282,78 @@ onMounted(() => {
font-size: 12px;
}
}
.ranking-item {
display: flex;
align-items: center;
padding: 2px;
height: 60px; /* 调整排行榜每个数据块的高度 */
border-top: 1px solid #eaeaea; /* 添加分割线 */
.ranking-number {
margin-right: 8px; /* 调整排序编号与内容的间距 */
font-size: 16px; /* 设置排序编号的字体大小 */
margin-top: -20px;
}
.ranking-avatar {
//头像
width: 28px;
height: 28px;
margin-right: 5px;
img {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.user-name {
//用户名
font-weight: bold;
font-size: 15px;
}
.ranking-info {
margin-left: auto; /* 将左边距设置为 auto使排行榜内容靠右 */
flex: 1;
display: flex;
align-items: center; /* 将 align-items 设置为 center */
justify-content: flex-end; /* 将 justify-content 设置为 flex-end */
.name-stats {
display: flex;
flex-direction: column;
align-items: flex-end; /* 将 align-items 设置为 flex-end */
justify-content: right;
flex: 1;
.stats {
display: flex;
align-items: center;
.stat-drop{
margin-top: -10px;
}
.stat-item {
.stat-value {
font-size: 1px;
margin-top: -10px;
}
}
}
}
.downloads {
margin-left: auto;
text-align: right;
margin-top: 13px;
.download-value {
font-weight: bold;
font-size: 15px;
}
}
}
}
}
.dark {
.hottopic-wrap {

@ -334,4 +334,18 @@ declare module Item {
//描述
description: string;
}
//优质榜参数
interface highqualityProps {
//用户名
name: string;
//头像
avatar: string;
//发帖数量
post_count: number;
//被赞数量
likes: number;
//总得分
comprehensive_score: number;
}
}

@ -168,6 +168,11 @@ declare module NetReq {
pager: Item.PagerProps;
}
interface PostGetHighQuailty {
/** 帖子列表 */
list: Item.highqualityProps[];
}
type PostCreatePost = Item.PostProps;
interface PostDeletePost {}

Loading…
Cancel
Save