feat: refactor member to pay-per-use gravity purchase; mv webview to clipboard+browser
- member.vue: rewrite from subscription plans (free/growth/sprint) to H5-only pay-per-use gravity purchase with quantity selector + QR code - user.vue: gravity card replacing quota card, add share/contribute/H5-buy entry points, plus gravity acquisition modal (share/contribute/buy) - share.vue: layout fix (flex column), smarter copyLink with cached URL, WeChat timeline hint instead of open-type - share.controller.ts: add GET /:shareCode redirect route (IP record + 302) - interview.vue: guest mode fix, H5 buy modal, clipboard copy instead of webview for mini-program - App.vue: handleH5UrlParams for ?token=&buy=gravity auto-login - composables/useGravityPurchase.ts: reusable gravity purchase composable - remove webview.vue (no longer used), replace with clipboard+browser flow - AGENTS.md: sync all above changes, fix duplicate numbering
This commit is contained in:
@@ -6,7 +6,6 @@ import { VipExpiryService } from './vip-expiry.service'
|
||||
import { GravityTopUpService } from './gravity-top-up.service'
|
||||
import { DailyQuestion, DailyQuestionSchema } from '../schemas/daily-question.schema'
|
||||
import { User, UserSchema } from '../user/user.schema'
|
||||
import { PricingService } from '../schemas/pricing.service'
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -15,6 +14,6 @@ import { PricingService } from '../schemas/pricing.service'
|
||||
{ name: User.name, schema: UserSchema },
|
||||
]),
|
||||
],
|
||||
providers: [WechatTokenService, DailyQuestionPushService, VipExpiryService, GravityTopUpService, PricingService],
|
||||
providers: [WechatTokenService, DailyQuestionPushService, VipExpiryService, GravityTopUpService],
|
||||
})
|
||||
export class ScheduleModule {}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Controller, Get, Post, Body, Param, Query, HttpException, HttpStatus, UseGuards } from '@nestjs/common'
|
||||
import { Controller, Get, Post, Body, Param, Query, HttpException, HttpStatus, UseGuards, Req, Res } from '@nestjs/common'
|
||||
import { Request, Response } from 'express'
|
||||
import { JwtService } from '@nestjs/jwt'
|
||||
import { ShareService } from './share.service'
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'
|
||||
import { CurrentUser } from '../../common/decorators/current-user.decorator'
|
||||
import { Public } from '../../common/decorators/public.decorator'
|
||||
import * as crypto from 'crypto'
|
||||
|
||||
@Controller('share')
|
||||
export class ShareController {
|
||||
@@ -66,4 +68,27 @@ export class ShareController {
|
||||
) {
|
||||
return this.shareService.visitors(userId, Number(page) || 1, Number(pageSize) || 20)
|
||||
}
|
||||
|
||||
// 泛匹配路由放在最后,避免拦截 stats/records/visitors 等
|
||||
@Public()
|
||||
@Get(':shareCode')
|
||||
async redirect(
|
||||
@Param('shareCode') shareCode: string,
|
||||
@Req() req: Request,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
try {
|
||||
const ip = req.ip || req.socket?.remoteAddress || 'unknown'
|
||||
const visitorId = crypto.createHash('md5').update(ip).digest('hex').slice(0, 16)
|
||||
let visitorUserId: string | undefined
|
||||
const token = req.query.token as string | undefined
|
||||
if (token) {
|
||||
try { const payload = this.jwtService.verify(token) as any; visitorUserId = payload.userId } catch {}
|
||||
}
|
||||
await this.shareService.visit(shareCode, visitorId, visitorUserId)
|
||||
} catch (e) {
|
||||
// 访问记录失败不影响跳转
|
||||
}
|
||||
res.redirect(HttpStatus.FOUND, `https://zhiyin.yzrcloud.cn/?share=${shareCode}`)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user