From 74692b05b31cc19051a1846b1fae05ff5090770a Mon Sep 17 00:00:00 2001 From: HXY <2479895356@qq.com> Date: Wed, 6 Sep 2023 18:56:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=94=A8=E6=88=B7=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=A4=B4=E5=83=8F=E4=B8=BA=E7=94=A8=E6=88=B7=E5=90=8D?= =?UTF-8?q?=E5=89=8D=E4=B8=A4=E4=BD=8D=EF=BC=8C=E5=A2=9E=E5=8A=A0=E7=B2=98?= =?UTF-8?q?=E8=B4=B4=E5=9B=BE=E7=89=87=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E6=94=B9=E5=85=85=E5=80=BC=E9=BB=98=E8=AE=A4=E9=87=91=E9=A2=9D?= =?UTF-8?q?=E4=B8=BA30=EF=BC=8C=E4=BC=98=E5=8C=96=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E6=96=B9=E6=B3=95=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/core/storage.go | 2 +- internal/dao/storage/localoss.go | 1 + internal/servants/web/pub.go | 13 +- internal/servants/web/utils.go | 128 +++ internal/servants/web/web.go | 2 +- web/src/api/post.ts | 16 + web/src/components/compose.vue | 1438 ++++++++++++++++------------- web/src/components/reply-item.vue | 4 +- web/src/components/rightbar.vue | 50 +- web/src/types/NetParams.d.ts | 7 + web/src/types/NetReq.d.ts | 11 + web/src/views/Wallet.vue | 2 +- 12 files changed, 981 insertions(+), 693 deletions(-) diff --git a/internal/core/storage.go b/internal/core/storage.go index 310edf29..35c524b7 100644 --- a/internal/core/storage.go +++ b/internal/core/storage.go @@ -24,7 +24,7 @@ type OssCreateService interface { PersistObject(objectKey string) error } -// OssCreateService Object Storage System Object Delete service +// OssDeleteService Object Storage System Object Delete service type OssDeleteService interface { DeleteObject(objectKey string) error DeleteObjects(objectKeys []string) error diff --git a/internal/dao/storage/localoss.go b/internal/dao/storage/localoss.go index 10131d29..24aa4c50 100644 --- a/internal/dao/storage/localoss.go +++ b/internal/dao/storage/localoss.go @@ -62,6 +62,7 @@ func (s *localossCreateServant) PutObject(objectKey string, reader io.Reader, ob return "", err } if written != objectSize { + fmt.Print("这里发生了错误.......") os.Remove(savePath) return "", errors.New("put object not complete") } diff --git a/internal/servants/web/pub.go b/internal/servants/web/pub.go index 827a88a2..ba042b6b 100644 --- a/internal/servants/web/pub.go +++ b/internal/servants/web/pub.go @@ -10,6 +10,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "github.com/rocboss/paopao-ce/internal/core" "image/color" "image/png" "io/ioutil" @@ -45,6 +46,8 @@ const ( type pubSrv struct { api.UnimplementedPubServant *base.DaoServant + + oss core.ObjectStorageService } type ThirdPartyUserDataVo struct { @@ -214,12 +217,17 @@ func (s *pubSrv) Register(req *web.RegisterReq) (*web.RegisterResp, mir.Error) { logrus.Errorf("scheckPassword err: %v", err) return nil, web.ErrUserRegisterFailed } + avatarURL := s.getRandomAvatarByUsername(req.Username) + if len(avatarURL) == 0 { + fmt.Println("头像生成失败或为空") + } + password, salt := encryptPasswordAndSalt(req.Password) user := &ms.User{ Nickname: req.Username, Username: req.Username, Password: password, - Avatar: getRandomAvatar(), + Avatar: avatarURL, Salt: salt, Status: ms.UserStatusNormal, } @@ -314,8 +322,9 @@ func (s *pubSrv) validUsername(username string) mir.Error { return nil } -func newPubSrv(s *base.DaoServant) api.Pub { +func newPubSrv(s *base.DaoServant, oss core.ObjectStorageService) api.Pub { return &pubSrv{ DaoServant: s, + oss: oss, } } diff --git a/internal/servants/web/utils.go b/internal/servants/web/utils.go index c3b11226..77a13ab7 100644 --- a/internal/servants/web/utils.go +++ b/internal/servants/web/utils.go @@ -5,7 +5,19 @@ package web import ( + "bytes" + "fmt" + "github.com/golang/freetype" + "github.com/golang/freetype/truetype" + "golang.org/x/image/font" + "hash/fnv" "image" + "image/color" + "image/draw" + "image/png" + "io" + "io/ioutil" + "log" "math/rand" "strings" "time" @@ -79,6 +91,122 @@ func getRandomAvatar() string { return defaultAvatars[rand.Intn(len(defaultAvatars))] } +func (s *pubSrv) getRandomAvatarByUsername(username string) string { + // 获取前两个字 + runes := []rune(username) + showName := string(runes[:2]) + //转换为大写 + showName = strings.ToUpper(showName) + + // 获取随机颜色作为头像底色 + finalColor := getRandomColor(username) + + //生成随机头像文件 + // 生成头像图片 + imageSize := 50 // 设置头像图片大小 + avatar := createAvatar(imageSize, finalColor, showName) + //avatarFilename := username + ".png" + // 保存头像图片到文件 + avatarBuffer := &bytes.Buffer{} + err := saveImageToBuffer(avatar, avatarBuffer) + if err != nil { + fmt.Println("保存头像到缓冲区失败:", err) + return "注册失败" + } + if avatarBuffer.Len() == 0 { + fmt.Println("头像缓冲区为空") + return "注册失败" + } + + // 生成随机路径 + randomPath := uuid.Must(uuid.NewV4()).String() + ossSavePath := "public/avatar/" + generatePath(randomPath[:8]) + "/" + randomPath[9:] + ".png" + if ossSavePath == "" { + fmt.Println("ossSavePath为空") + return "注册失败" + } + + //fmt.Print(ossSavePath + "\n" + avatarBuffer.String() + "\n" + strconv.Itoa(imageSize) + "\n" + "image/png" + "\n" + "false") + objectUrl, err := s.oss.PutObject(ossSavePath, avatarBuffer, int64(avatarBuffer.Len()), "image/png", false) + if err != nil { + logrus.Errorf("oss.PutObject err: %s", err) + return "失败" + } + + fmt.Print(objectUrl) + return objectUrl +} + +// 生成随机颜色 +func getRandomColor(username string) color.RGBA { + hasher := fnv.New32() + hasher.Write([]byte(username)) + hash := hasher.Sum32() + + return color.RGBA{ + R: 150 + uint8(hash>>16)%106, + G: 150 + uint8((hash>>8)%106), + B: 150 + uint8(hash%106), + A: 255, + } +} + +// 创建头像 +func createAvatar(size int, bgColor color.RGBA, text string) image.Image { + avatar := image.NewRGBA(image.Rect(0, 0, size, size)) + draw.Draw(avatar, avatar.Bounds(), &image.Uniform{bgColor}, image.Point{}, draw.Src) + + // 在头像上绘制文字 + textColor := color.RGBA{255, 255, 255, 255} // 白色文字 + + fontBytes, err := ioutil.ReadFile("internal/servants/web/typeface/TTF/SourceSans3-Black.ttf") + if err != nil { + log.Println("unable to read font file:", err) + return avatar + } + + fontParsed, err := truetype.Parse(fontBytes) + if err != nil { + log.Println("unable to parse font file:", err) + return avatar + } + + // 使用 freetype 作为字体渲染器 + c := freetype.NewContext() + c.SetDPI(72) + c.SetFont(fontParsed) + c.SetFontSize(float64(size / 2)) // 设置字体大小 + c.SetClip(avatar.Bounds()) + c.SetDst(avatar) + c.SetSrc(image.NewUniform(textColor)) + c.SetHinting(font.HintingFull) + + // 创建字体面以计算字符串大小 + opts := truetype.Options{} + opts.Size = float64(size) / 2 + face := truetype.NewFace(fontParsed, &opts) + + // 获取文本的宽度和高度 + textWidth := font.MeasureString(face, text).Round() + textHeight := face.Metrics().Height.Round() + + // 计算绘制文本的起点,以使其居中 + x := (size - textWidth) / 2 + y := (size + textHeight/2) / 2 + + pt := freetype.Pt(x, y) + _, err = c.DrawString(text, pt) + if err != nil { + log.Println("failed to draw string:", err) + } + + return avatar +} + +func saveImageToBuffer(img image.Image, buffer io.Writer) error { + return png.Encode(buffer, img) +} + // checkPassword 密码检查 func checkPassword(password string) mir.Error { // 检测用户是否合规 diff --git a/internal/servants/web/web.go b/internal/servants/web/web.go index a9347254..6b4b9836 100644 --- a/internal/servants/web/web.go +++ b/internal/servants/web/web.go @@ -31,7 +31,7 @@ func RouteWeb(e *gin.Engine) { api.RegisterCoreServant(e, newCoreSrv(ds, oss)) api.RegisterLooseServant(e, newLooseSrv(ds)) api.RegisterPrivServant(e, newPrivSrv(ds, oss)) - api.RegisterPubServant(e, newPubSrv(ds)) + api.RegisterPubServant(e, newPubSrv(ds, oss)) api.RegisterKeyQueryServant(e, NewShareKeyServant(ds)) api.RegisterRankServant(e, NewRankServant(ds)) api.RegisterFollowshipServant(e, newFollowshipSrv(ds)) diff --git a/web/src/api/post.ts b/web/src/api/post.ts index c4eb28c6..91403b9c 100644 --- a/web/src/api/post.ts +++ b/web/src/api/post.ts @@ -304,3 +304,19 @@ export const unfollowTopic = ( data, }); }; + +export const uploadImage = ( + data: NetParams.UploadImageReq, + uploadToken: string // 添加一个参数来传递 Authorization 头的值 +): Promise => { + return request({ + method: 'post', + url: '/v1/attachment', + data, + headers: { + 'Authorization': uploadToken, // 设置 Authorization 头,使用传递的 uploadToken + 'Content-Type': 'multipart/form-data', // 设置请求头,表明发送的是 FormData + }, + }); +}; + diff --git a/web/src/components/compose.vue b/web/src/components/compose.vue index 3c43927f..13fea5ca 100644 --- a/web/src/components/compose.vue +++ b/web/src/components/compose.vue @@ -1,110 +1,101 @@ \ No newline at end of file diff --git a/web/src/components/reply-item.vue b/web/src/components/reply-item.vue index 718792cc..107dc797 100644 --- a/web/src/components/reply-item.vue +++ b/web/src/components/reply-item.vue @@ -6,7 +6,7 @@ name: 'user', query: { s: props.reply.user.username }, }"> - {{ props.reply.user.username }} + {{ props.reply.user.nickname }} {{ props.reply.at_user_id > 0 ? '回复' : ':' }} @@ -16,7 +16,7 @@ name: 'user', query: { s: props.reply.at_user.username }, }" v-if="props.reply.at_user_id > 0"> - {{ props.reply.at_user.username }} + {{ props.reply.at_user.nickname }}
diff --git a/web/src/components/rightbar.vue b/web/src/components/rightbar.vue index 8b04545e..90f87926 100644 --- a/web/src/components/rightbar.vue +++ b/web/src/components/rightbar.vue @@ -160,6 +160,7 @@ import { useRouter } from "vue-router"; import { getDownloadRank, getHighQuailty, getTags } from "@/api/post"; import { Search } from "@vicons/ionicons5"; import { ChevronForward } from "@vicons/ionicons5"; +import { Ref } from 'vue'; const hotTags = ref([]); const followTags = ref([]); @@ -187,7 +188,7 @@ const DownloadPreWeekRankingList = ref([]); const DownloadPreMonthRankingList = ref([]); //获取排行榜数据 -const locadHeighQuailtyRankingList = () => { +const loadHeighQuailtyRankingList = () => { rankloading.value = true; getHighQuailty() .then((res) => { @@ -199,13 +200,13 @@ const locadHeighQuailtyRankingList = () => { }); }; -const loadDowmloadRankingByType = (type: number) => { +const loadDownloadRankingByType = (type: number) => { rankloading.value = true; getDownloadRank(type) .then((res) => { - if (type ===1 ) allDownloadRankingList.value = res.list; - if (type === 2) DownloadPreWeekRankingList.value = res.list; - if (type === 3) DownloadPreMonthRankingList.value = res.list; + if (type === 1) allDownloadRankingList.value = res.list; + else if (type === 2) DownloadPreWeekRankingList.value = res.list; + else if (type === 3) DownloadPreMonthRankingList.value = res.list; rankloading.value = false; }) .catch((err) => { @@ -231,25 +232,28 @@ const toggleRankingType = () => { NextRankingType.value = rankingTypes[(currentRankingTypeIndex + 1) % rankingTypes.length]; }; -//1总 2周 3月 +const rankingTypeToFunctionMap: { [key: string]: Ref } = { + highQuality: rankingList, + downloadAll: allDownloadRankingList, + downloadPreWeek: DownloadPreWeekRankingList, + downloadPreMonth: DownloadPreMonthRankingList +}; + +const rankingTypeToLoadFunctionMap: { [key: string]: () => void } = { + downloadAll: () => loadDownloadRankingByType(1), + downloadPreWeek: () => loadDownloadRankingByType(2), + downloadPreMonth: () => loadDownloadRankingByType(3) +}; + const getCurrentRankingList = computed(() => { - if (currentRankingType.value === "highQuality") { - return rankingList.value; - } else if (currentRankingType.value === "downloadAll") { - if (allDownloadRankingList.value.length === 0) { - loadDowmloadRankingByType(1); - } - return allDownloadRankingList.value; - } else if (currentRankingType.value === "downloadPreWeek") { - if (DownloadPreWeekRankingList.value.length === 0) { - loadDowmloadRankingByType(2); - } - return DownloadPreWeekRankingList.value; - } else if (currentRankingType.value === "downloadPreMonth") { - if (DownloadPreMonthRankingList.value.length === 0) { - loadDowmloadRankingByType(3); + const currentType = currentRankingType.value; + const rankingValue = rankingTypeToFunctionMap[currentType as keyof typeof rankingTypeToFunctionMap]?.value; + + if (rankingValue !== undefined) { + if (rankingValue.length === 0 && rankingTypeToLoadFunctionMap[currentType]) { + rankingTypeToLoadFunctionMap[currentType](); } - return DownloadPreMonthRankingList.value; + return rankingValue; } return []; }); @@ -320,7 +324,7 @@ watch( ); onMounted(() => { loadHotTags(); - locadHeighQuailtyRankingList(); + loadHeighQuailtyRankingList(); }); diff --git a/web/src/types/NetParams.d.ts b/web/src/types/NetParams.d.ts index 0d1f3f36..2937ab0b 100644 --- a/web/src/types/NetParams.d.ts +++ b/web/src/types/NetParams.d.ts @@ -279,4 +279,11 @@ declare module NetParams { interface PostUnfollowTopic { topic_id: number; } + + /** 上传图片请求参数 */ + interface UploadImageReq { + file: File; + type: string; + } + } diff --git a/web/src/types/NetReq.d.ts b/web/src/types/NetReq.d.ts index 458d726f..74fb17df 100644 --- a/web/src/types/NetReq.d.ts +++ b/web/src/types/NetReq.d.ts @@ -203,4 +203,15 @@ declare module NetReq { interface PostFollowTopic {} interface PostUnfollowTopic {} + + /** 上传图片响应参数 */ + interface UploadImageResp { + /** 图片URL */ + content: string; + file_size: number; + img_height: number; + img_width: number; + type: number; + user_id: number; + } } diff --git a/web/src/views/Wallet.vue b/web/src/views/Wallet.vue index 110b04b3..e9c1eb03 100644 --- a/web/src/views/Wallet.vue +++ b/web/src/views/Wallet.vue @@ -158,7 +158,7 @@ import MainNav from "@/components/main-nav.vue"; const store = useStore(); const route = useRoute(); const showRecharge = ref(false); -const selectedRechargeAmount = ref(100); +const selectedRechargeAmount = ref(3000); const recharging = ref(false); const rechargeQrcode = ref(''); const loading = ref(false);