Compare commits

...

13 Commits

Author SHA1 Message Date
e29a718b2f feat: 修改为4/3比例的图 2026-05-22 18:43:45 +08:00
c8501c9895 docs: 完善Go SDK集成说明
- SDK包名修正为 dysmsapi-20170525/v4/client
- 补充三种操作系统环境变量配置(Linux/macOS/Windows CMD/PowerShell)
- 完善SDK使用代码(初始化、发送短信、异常处理)
- 补充更多返回码和常见错误处理
- 推荐单例模式创建客户端

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:43:44 +08:00
3370f0507d docs: 补充阿里云Go SDK详细信息
- 修正SDK包名为 github.com/alibabacloud-go/dysmsapi-20180501/v2
- 补充环境要求、安装方式、API Endpoint
- 新增12.5节:Go SDK使用说明(依赖安装、客户端初始化、发送短信代码示例)
- 新增返回码说明(OK、频率限制、内容违规等)
- 更新12.6相关文档链接,补充快速入门和SDK示例地址

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:43:44 +08:00
7588498452 docs: 更新阿里云短信价格信息
补充实际套餐包价格(200条¥10、1500条¥73.5、5000条¥170、15000条¥507.6),
修正原错误价格,删除"待确认"标注。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:43:44 +08:00
aff114afdd docs: 补充阿里云短信服务详情
新增第12章:价格计费、模板类型、签名、开通流程、相关文档链接。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:43:44 +08:00
7222598961 docs: 修正流程图中的verify_token存储位置
verify_token通过temp_register_verify_token存储,非直接verify_token,
并补充说明setNickname.vue需透传verify_token到registerApi。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:43:44 +08:00
c05953b84b docs: 修正技术选型说明
明确短信服务使用直接AccessKey认证,与OSS的STS方式区分。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:43:44 +08:00
091a3eb5c9 docs: 修正部署说明和防暴力破解策略
- 部署说明移除错误的OSS_STS_ROLE_ARN,短信使用直接AccessKey
- 防暴力破解策略补充独立Key的TTL说明

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:43:43 +08:00
ef46ec832e docs: 补充前端完整注册流程图
明确 register.vue → setNickname.vue 的页面跳转和数据传递逻辑,
verify_token 需要跟随 temp_* 一起存储和传递。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:43:43 +08:00
cc5565ffaf docs: 简化验证后处理流程
删除验证码 -> 创建 verify:register:{mobile} 记录 -> 注册时比对

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:43:43 +08:00
95d8c75500 docs: 修正 verify_token 为简单随机字符串非JWT
verify_token 使用 32 位随机字符串(vtf_xxx)而非 JWT,
避免签名验证的复杂性,直接 Redis 比对即可。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:43:43 +08:00
72ea3f4305 docs: 完善注册短信验证码设计文档
根据评审意见修改:
- 配置项移至 userService(方案一)
- Redis 存储增加 scene 维度、防暴力破解策略、发送频率限制
- verify-code 增加 verify_token 供后续注册接口使用
- 补充错误处理场景(Redis失败、阿里云超时、限流等)
- 增加安全注意事项(日志脱敏、token安全等)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:43:43 +08:00
d5d311c2b2 docs: 添加注册短信验证码功能设计文档
包含三种实现方案(userService集成、独立微服务、gateway)的对比分析,
推荐方案一在userService中直接集成SMS SDK。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:43:43 +08:00
2 changed files with 1185 additions and 53 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +1,24 @@
<template>
<scroll-view
class="waterfall-scroll"
:style="scrollStyle"
scroll-x
:show-scrollbar="false"
:scroll-left="isIOS ? undefined : scrollLeft"
:bounce="false"
@scroll="onScroll"
@touchstart="onTouchStart"
@touchend="onTouchEnd"
@touchcancel="onTouchEnd"
>
<view
ref="waterfallInnerRef"
:class="{'waterfall-inner': true, 'ios-css-animate': !iosScrollPaused}"
:style="innerStyle"
>
<view
v-for="card in cards"
:key="card.id"
class="wf-card"
:style="cardStyle(card)"
@click="handleCardClick(card)"
>
<scroll-view class="waterfall-scroll" :style="scrollStyle" scroll-x :show-scrollbar="false"
:scroll-left="isIOS ? undefined : scrollLeft" :bounce="false" @scroll="onScroll" @touchstart="onTouchStart"
@touchend="onTouchEnd" @touchcancel="onTouchEnd">
<view ref="waterfallInnerRef" :class="{ 'waterfall-inner': true, 'ios-css-animate': !iosScrollPaused }"
:style="innerStyle">
<view v-for="card in cards" :key="card.id" class="wf-card" :style="cardStyle(card)"
@click="handleCardClick(card)">
<!-- 渐变边框光效 -->
<view class="wf-card-border" :style="borderStyle(card)" />
<!-- 封面图 -->
<image
v-if="card.coverUrl"
class="wf-card-img"
:src="card.coverUrl"
mode="aspectFill"
:style="{ width: card.w + 'px', height: card.h + 'px', borderRadius: card.radius + 'px' }"
/>
<image v-if="card.coverUrl" class="wf-card-img" :src="card.coverUrl" mode="aspectFill"
:style="{ width: card.w + 'px', height: card.h + 'px', borderRadius: card.radius + 'px' }" />
<!-- 光波动画层 - 外层 -->
<view
class="wf-like-wave wf-like-wave-outer"
:class="{ 'wf-like-wave-active': likingMap[card.id] }"
:style="{ width: card.w + 'px', height: card.h + 'px', borderRadius: card.radius + 'px' }"
/>
<view class="wf-like-wave wf-like-wave-outer" :class="{ 'wf-like-wave-active': likingMap[card.id] }"
:style="{ width: card.w + 'px', height: card.h + 'px', borderRadius: card.radius + 'px' }" />
<!-- 光波动画层 - 内层 -->
<view
class="wf-like-wave wf-like-wave-inner"
:class="{ 'wf-like-wave-active': likingMap[card.id] }"
:style="{ width: card.w + 'px', height: card.h + 'px', borderRadius: card.radius + 'px' }"
/>
<view class="wf-like-wave wf-like-wave-inner" :class="{ 'wf-like-wave-active': likingMap[card.id] }"
:style="{ width: card.w + 'px', height: card.h + 'px', borderRadius: card.radius + 'px' }" />
<!-- 底部点赞数 -->
<view class="wf-card-footer">
@ -184,6 +156,10 @@ const startAutoScroll = () => {
scrollUpdateTimer = setInterval(() => {
if (!isComponentMounted || userInteracting || isLoadingMore) return
autoScrollPos += AUTO_SCROLL_SPEED_ANDROID
// 0
if (autoScrollPos >= totalWidth.value) {
autoScrollPos = 0
}
scrollLeft.value = autoScrollPos
//
@ -358,6 +334,13 @@ class WaterfallLayout {
// = rowH × 9/16 9:16span
this.colW = Math.round(this.rowH * 4 / 3)
this.curX = 0
// N 5 6 N
this.consecutiveNCount = 0
}
_isNLevel(item) {
const span = item.span != null ? item.span : this._span(item.likes || 0)
return span === 1
}
_cardSize(span) {
@ -392,10 +375,28 @@ class WaterfallLayout {
let i = 0
while (i < remaining.length) {
const rawSpan = remaining[i].span
const isN = remaining[i]._blank ? false : this._isNLevel(remaining[i])
// N 5 6 N 1-2
if (isN && this.consecutiveNCount >= 5) {
const blankCount = Math.random() < 0.5 ? 1 : 2
for (let b = 0; b < blankCount; b++) {
if (sum + 1 <= ROWS) {
remaining.splice(i, 0, { id: idCounter++, span: 1, _blank: true, likes: 0 })
}
}
this.consecutiveNCount = 0
// i break
break
}
// ROWS
if (sum + rawSpan > ROWS) {
i++
// ROWS
if (i >= remaining.length && col.length > 0) {
break
}
continue
}
@ -404,6 +405,15 @@ class WaterfallLayout {
col.push(item)
sum += rawSpan
// N N
if (!item._blank) {
if (this._isNLevel(item)) {
this.consecutiveNCount++
} else {
this.consecutiveNCount = 0
}
}
// ROWS
if (sum >= ROWS) break
}
@ -413,6 +423,13 @@ class WaterfallLayout {
const item = remaining.shift()
col.push(item)
sum = item.span
if (!item._blank) {
if (this._isNLevel(item)) {
this.consecutiveNCount++
} else {
this.consecutiveNCount = 0
}
}
}
colIndex++
@ -437,7 +454,7 @@ class WaterfallLayout {
for (const u of colUsers) {
const span = u.span || 1
const { w, h } = this._cardSize(span)
if (!u._pad) {
if (!u._pad && !u._blank) {
result.push({ ...u, left: colX, top: curY, w, h, radius: 8 })
}
curY += h + this.gap
@ -450,6 +467,7 @@ class WaterfallLayout {
//
compute(users) {
this.curX = 0
this.consecutiveNCount = 0
const columns = this._groupIntoColumns(users)
const result = []
for (const col of columns) {
@ -508,9 +526,9 @@ const cardStyle = (card) => ({
const borderStyle = (card) => ({
position: 'absolute',
inset: `-${BORDER_W}px`,
borderRadius: (card.radius + BORDER_W) + 'px',
background: BORDER_COLORS[Math.abs(card.id) % BORDER_COLORS.length],
inset: `-${BORDER_W}px`,
zIndex: 0,
opacity: 0.85,
})
@ -566,7 +584,7 @@ const batchGetPresignedUrls = async (urls) => {
}
return map
}
} catch (_) {}
} catch (_) { }
return {}
}
@ -641,7 +659,7 @@ const loadUsers = async () => {
span: item.span ?? null,
}
})
allUsers.value = withData
cards.value = layout.compute(withData)
totalWidth.value = layout.getTotalWidth()
@ -731,6 +749,12 @@ const appendMore = async () => {
cards.value = [...cards.value, ...placed]
totalWidth.value = layout.getTotalWidth()
// totalWidth iOS CSS
if (isIOS && !iosScrollPaused.value) {
stopIOSAutoScroll()
startIOSAutoScroll()
}
} else {
//
appendFailed = true
@ -774,7 +798,7 @@ const handleCardClick = (card) => {
});
} else {
//
if(card.id){
if (card.id) {
cardTapTimers[card.id] = setTimeout(() => {
delete cardTapTimers[card.id];
uni.navigateTo({ url: `/pages/asset-detail/asset-detail?asset_id=${card.id}` });
@ -915,7 +939,7 @@ onUnmounted(() => {
try {
uni.offAppShow(onAppShowHandler)
uni.offAppHide(onAppHideHandler)
} catch (_) {}
} catch (_) { }
}
})
@ -999,8 +1023,11 @@ const loadUsersAndStartScroll = () => {
}
@keyframes iosAutoScroll {
/* from { transform: translateX(0); } */
to { transform: translateX(var(--scroll-dist)); }
to {
transform: translateX(var(--scroll-dist));
}
}
.wf-card {
@ -1010,6 +1037,7 @@ const loadUsersAndStartScroll = () => {
transform-origin: top center;
overflow: visible;
pointer-events: visible;
transform: translateZ(0);
}
.wf-card:active {
@ -1019,6 +1047,7 @@ const loadUsersAndStartScroll = () => {
.wf-card-border {
position: absolute;
pointer-events: none;
will-change: opacity;
}
.wf-card-img {
@ -1046,10 +1075,9 @@ const loadUsersAndStartScroll = () => {
.wf-likes-wrap {
background: linear-gradient(to bottom right,
#F0E4B1 0%,
#F08399 50%,
#B94E73 100%
);
#F0E4B1 0%,
#F08399 50%,
#B94E73 100%);
border-radius: 999rpx;
padding: 2rpx 10rpx;
display: flex;
@ -1099,10 +1127,10 @@ const loadUsersAndStartScroll = () => {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(1.1);
opacity: 0;
}
}
</style>