feat: 完成全部接口替换为新后端接口
This commit is contained in:
+39
-148
@@ -1,165 +1,56 @@
|
||||
interface UserInfo {
|
||||
_id: string;
|
||||
openid: string;
|
||||
unionid: string | null;
|
||||
nickName: string | null;
|
||||
avatarUrl: string | null;
|
||||
totalDownloads: number;
|
||||
createdAt: string;
|
||||
lastActiveAt: string;
|
||||
}
|
||||
import { apiRequest } from '../services/api-request';
|
||||
import {
|
||||
cachedServerUser,
|
||||
clearSession,
|
||||
ensureSession,
|
||||
getAuthContext,
|
||||
updateSessionUser,
|
||||
type ServerUser,
|
||||
} from '../services/auth-session';
|
||||
|
||||
const STORAGE_KEY = 'user_info';
|
||||
const STORAGE_EXPIRE_KEY = 'user_info_expire';
|
||||
const EXPIRE_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 天
|
||||
export type UserInfo = ServerUser;
|
||||
export { ensureSession as ensureAuthenticated } from '../services/auth-session';
|
||||
|
||||
/** 内存中的用户数据(App 生命周期内有效) */
|
||||
let currentUser: UserInfo | null = null;
|
||||
|
||||
/** 登录进行中的 Promise(防止并发重复调用) */
|
||||
let loginPromise: Promise<UserInfo> | null = null;
|
||||
|
||||
function syncAppGlobalUser(user: UserInfo | null): void {
|
||||
try {
|
||||
const app = getApp<IAppOption>();
|
||||
if (app?.globalData) {
|
||||
app.globalData.user = user;
|
||||
}
|
||||
} catch {
|
||||
// App 尚未初始化时忽略
|
||||
}
|
||||
}
|
||||
|
||||
function syncUserState(user: UserInfo, persist = true): UserInfo {
|
||||
currentUser = user;
|
||||
if (persist) {
|
||||
saveToStorage(user);
|
||||
}
|
||||
syncAppGlobalUser(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户,优先内存 → Storage → 云函数登录
|
||||
*/
|
||||
/** 用户摘要与新后端认证绑定,所有远端用户业务只使用本站用户 ID。 */
|
||||
export async function getUser(): Promise<UserInfo> {
|
||||
// 1. 内存中有,直接返回
|
||||
if (currentUser) {
|
||||
syncAppGlobalUser(currentUser);
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
// 2. 尝试从 Storage 读取
|
||||
const cached = loadFromStorage();
|
||||
if (cached) {
|
||||
return syncUserState(cached, false);
|
||||
}
|
||||
|
||||
// 3. 静默登录(防止并发)
|
||||
if (!loginPromise) {
|
||||
loginPromise = silentLogin().finally(() => {
|
||||
loginPromise = null;
|
||||
});
|
||||
}
|
||||
return loginPromise;
|
||||
return (await ensureSession()).user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静默登录:调用云函数
|
||||
*/
|
||||
async function silentLogin(): Promise<UserInfo> {
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userLogin',
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
|
||||
if (result.code !== 0) {
|
||||
throw new Error('登录失败');
|
||||
}
|
||||
|
||||
const user = result.data as UserInfo;
|
||||
return syncUserState(user);
|
||||
}
|
||||
|
||||
/** 缓存只供展示,不能证明服务端仍认可当前凭证。 */
|
||||
export function getCachedUser(): UserInfo | null {
|
||||
if (currentUser) {
|
||||
syncAppGlobalUser(currentUser);
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
const cached = loadFromStorage();
|
||||
if (!cached) return null;
|
||||
return syncUserState(cached, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Storage 读取用户数据(检查过期)
|
||||
*/
|
||||
function loadFromStorage(): UserInfo | null {
|
||||
try {
|
||||
const expire = wx.getStorageSync(STORAGE_EXPIRE_KEY);
|
||||
if (!expire || Date.now() > expire) {
|
||||
wx.removeStorageSync(STORAGE_KEY);
|
||||
wx.removeStorageSync(STORAGE_EXPIRE_KEY);
|
||||
return null;
|
||||
}
|
||||
const user = wx.getStorageSync(STORAGE_KEY);
|
||||
return user || null;
|
||||
return cachedServerUser();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入 Storage
|
||||
*/
|
||||
function saveToStorage(user: UserInfo): void {
|
||||
try {
|
||||
wx.setStorageSync(STORAGE_KEY, user);
|
||||
wx.setStorageSync(STORAGE_EXPIRE_KEY, Date.now() + EXPIRE_DURATION);
|
||||
} catch {
|
||||
// Storage 写入失败不影响主流程
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除登录状态
|
||||
*/
|
||||
export function clearUser(): void {
|
||||
currentUser = null;
|
||||
syncAppGlobalUser(null);
|
||||
wx.removeStorageSync(STORAGE_KEY);
|
||||
wx.removeStorageSync(STORAGE_EXPIRE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制刷新用户数据(跳过缓存)
|
||||
*/
|
||||
/** 读取本人资料;GET /me 会按统一规则完成过期与 401 恢复。 */
|
||||
export async function refreshUser(): Promise<UserInfo> {
|
||||
clearUser();
|
||||
return await silentLogin();
|
||||
const context = getAuthContext();
|
||||
const user = await apiRequest<UserInfo>('/me');
|
||||
if (getAuthContext().key !== context.key)
|
||||
throw new Error('接口环境已变化,请重新操作');
|
||||
return updateSessionUser(user, context);
|
||||
}
|
||||
/** 暂停账户自动恢复时保留标记,不能通过清缓存自动注册。 */
|
||||
export function clearUser(blocked = false): void {
|
||||
clearSession(blocked);
|
||||
}
|
||||
/** 仅供用户主动确认“重新开始使用账户”的动作调用,禁止在失败回调自动调用。 */
|
||||
export async function startAccount(): Promise<UserInfo> {
|
||||
return (await ensureSession(getAuthContext(), true, true)).user;
|
||||
}
|
||||
|
||||
/** 本人资料更新只提交头像资产 ID,不允许传任意外链或用户身份字段。 */
|
||||
export async function updateUserProfile(payload: {
|
||||
nickName?: string | null;
|
||||
avatarUrl?: string | null;
|
||||
nickName?: string;
|
||||
avatarAssetId?: string | null;
|
||||
}): Promise<UserInfo> {
|
||||
await getUser();
|
||||
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userLogin',
|
||||
data: {
|
||||
action: 'updateProfile',
|
||||
...payload,
|
||||
},
|
||||
const context = getAuthContext();
|
||||
const user = await apiRequest<UserInfo>('/me/profile', {
|
||||
method: 'PUT',
|
||||
data: payload,
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
|
||||
if (result.code !== 0 || !result.data) {
|
||||
throw new Error(result.message || '更新用户资料失败');
|
||||
}
|
||||
|
||||
return syncUserState(result.data as UserInfo);
|
||||
if (getAuthContext().key !== context.key)
|
||||
throw new Error('接口环境已变化,请重新操作');
|
||||
return updateSessionUser(user, context);
|
||||
}
|
||||
|
||||
export type { UserInfo };
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type {
|
||||
WorksheetCloudCategory,
|
||||
WorksheetStatus,
|
||||
} from '../core/models/worksheet';
|
||||
import type { CategoryId } from '../core/models/category';
|
||||
import type { WorksheetConfig } from '../core/models/worksheet';
|
||||
|
||||
/**
|
||||
* 调试发布时的裁剪模式。
|
||||
@@ -19,7 +17,7 @@ export interface DebugPublishMeta {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
category: WorksheetCloudCategory;
|
||||
category: CategoryId;
|
||||
subcategory?: string;
|
||||
path: string;
|
||||
ageMin: number;
|
||||
@@ -31,7 +29,7 @@ export interface DebugPublishMeta {
|
||||
isNew: boolean;
|
||||
isHot: boolean;
|
||||
sortOrder?: number;
|
||||
status: WorksheetStatus;
|
||||
status: WorksheetConfig['status'];
|
||||
}
|
||||
|
||||
/** 发布弹窗里可调的图片处理参数。 */
|
||||
@@ -118,7 +116,7 @@ export function getCropRatios(mode: DebugCropMode): {
|
||||
|
||||
/** 统一构造预览图在云存储中的路径。 */
|
||||
export function buildWorksheetPreviewCloudPath(
|
||||
category: WorksheetCloudCategory,
|
||||
category: CategoryId,
|
||||
id: string,
|
||||
): string {
|
||||
return `assets/previews/${category}/${id}.jpg`;
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { getUser } from './auth';
|
||||
import { apiRequest } from '../services/api-request';
|
||||
import {
|
||||
legacyWorksheet,
|
||||
memberStats,
|
||||
type MemberPage,
|
||||
} from '../services/member-api';
|
||||
import { generateUUID } from './uuid';
|
||||
|
||||
export interface DownloadLogRecord {
|
||||
_id: string;
|
||||
userId: string;
|
||||
worksheetId: string;
|
||||
exportId?: string;
|
||||
exportId?: string | null;
|
||||
createdAt: string;
|
||||
worksheet?: {
|
||||
_id: string;
|
||||
@@ -20,66 +26,39 @@ export interface DownloadLogRecord {
|
||||
} | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录下载日志
|
||||
*/
|
||||
/** 一次成功保存沿用同一个 exportId,服务端保证记录及下载计数幂等。 */
|
||||
export async function addDownloadLog(
|
||||
worksheetId: string,
|
||||
exportId?: string,
|
||||
exportId = generateUUID(),
|
||||
expectedUserId?: string,
|
||||
): Promise<boolean> {
|
||||
if (!worksheetId) return false;
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userDownloadLogs',
|
||||
data: { action: 'add', worksheetId, exportId },
|
||||
const user = await getUser();
|
||||
if (expectedUserId && user.id !== expectedUserId) return false;
|
||||
await apiRequest('/me/downloads', {
|
||||
method: 'POST',
|
||||
data: { worksheetId, exportId },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0;
|
||||
} catch (e) {
|
||||
console.error('addDownloadLog failed', e);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 收藏页下载历史展示条数上限 */
|
||||
export const DOWNLOAD_HISTORY_MAX = 30;
|
||||
|
||||
/**
|
||||
* 获取下载历史(默认最近 30 条)
|
||||
*/
|
||||
export async function getDownloadLogs(
|
||||
page = 1,
|
||||
pageSize = DOWNLOAD_HISTORY_MAX,
|
||||
): Promise<DownloadLogRecord[]> {
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userDownloadLogs',
|
||||
data: { action: 'list', page, pageSize },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0 ? result.data : [];
|
||||
} catch (e) {
|
||||
console.error('getDownloadLogs failed', e);
|
||||
return [];
|
||||
}
|
||||
const result = await apiRequest<MemberPage>('/me/downloads', {
|
||||
data: { page, pageSize },
|
||||
});
|
||||
return result.items.map((row) => ({
|
||||
...row,
|
||||
_id: row.id,
|
||||
worksheet: legacyWorksheet(row.worksheet),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户下载(打印)总数
|
||||
*/
|
||||
export async function getDownloadLogCount(): Promise<number> {
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userDownloadLogs',
|
||||
data: { action: 'count' },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0 ? Number(result.data?.count || 0) : 0;
|
||||
} catch (e) {
|
||||
console.error('getDownloadLogCount failed', e);
|
||||
return 0;
|
||||
}
|
||||
return (await memberStats()).downloadCount;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getCachedUser } from './auth';
|
||||
/**
|
||||
* 下载打印通用工具
|
||||
* 支持下载次数限制和激励广告
|
||||
@@ -67,7 +68,9 @@ function showVideoAd(): Promise<boolean> {
|
||||
mask: true,
|
||||
});
|
||||
|
||||
const onClose: WechatMiniprogram.RewardedVideoAdOnCloseCallback = (res) => {
|
||||
const onClose: WechatMiniprogram.RewardedVideoAdOnCloseCallback = (
|
||||
res,
|
||||
) => {
|
||||
videoAd.offClose(onClose);
|
||||
wx.hideLoading();
|
||||
resolve(!!(res && res.isEnded));
|
||||
@@ -137,6 +140,7 @@ function doDownload(
|
||||
},
|
||||
): Promise<boolean> {
|
||||
const exportId = generateUUID();
|
||||
const expectedUserId = getCachedUser()?.id;
|
||||
const trackerPage = options.trackerPage || options.trackerName || '';
|
||||
const trackerParams = {
|
||||
export_id: exportId,
|
||||
@@ -154,7 +158,11 @@ function doDownload(
|
||||
recordDownloadSuccess();
|
||||
tracker.reportExportResult(trackerPage, true, trackerParams);
|
||||
if (options.worksheetId) {
|
||||
await addDownloadLog(options.worksheetId, exportId);
|
||||
await addDownloadLog(
|
||||
options.worksheetId,
|
||||
exportId,
|
||||
expectedUserId,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
tracker.reportExportResult(trackerPage, false, trackerParams);
|
||||
|
||||
+38
-102
@@ -1,14 +1,13 @@
|
||||
import { getUser } from './auth';
|
||||
import { apiRequest } from '../services/api-request';
|
||||
import {
|
||||
legacyWorksheet,
|
||||
memberStats,
|
||||
type MemberPage,
|
||||
} from '../services/member-api';
|
||||
import tracker from './tracker';
|
||||
|
||||
function getCurrentPageRoute(): string {
|
||||
const pages = getCurrentPages();
|
||||
return pages[pages.length - 1]?.route || '';
|
||||
}
|
||||
|
||||
export interface FavoriteRecord {
|
||||
_id: string;
|
||||
userId: string;
|
||||
worksheetId: string;
|
||||
createdAt: string;
|
||||
worksheet?: {
|
||||
@@ -26,122 +25,59 @@ export interface FavoriteRecord {
|
||||
} | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加收藏
|
||||
*/
|
||||
/** 收藏操作使用新后端当前账户;调用失败不能向其他用户补发。 */
|
||||
export async function addFavorite(worksheetId: string): Promise<boolean> {
|
||||
try {
|
||||
await getUser(); // 确保已登录
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userFavorites',
|
||||
data: { action: 'add', worksheetId },
|
||||
await apiRequest(`/me/favorites/${encodeURIComponent(worksheetId)}`, {
|
||||
method: 'PUT',
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
const success = result.code === 0;
|
||||
if (success) tracker.reportFavorite(getCurrentPageRoute(), worksheetId, true);
|
||||
return success;
|
||||
} catch (e) {
|
||||
console.error('addFavorite failed', e);
|
||||
tracker.reportFavorite(currentRoute(), worksheetId, true);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消收藏
|
||||
*/
|
||||
export async function removeFavorite(worksheetId: string): Promise<boolean> {
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userFavorites',
|
||||
data: { action: 'remove', worksheetId },
|
||||
await apiRequest(`/me/favorites/${encodeURIComponent(worksheetId)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
const success = result.code === 0;
|
||||
if (success) {
|
||||
tracker.reportFavorite(getCurrentPageRoute(), worksheetId, false);
|
||||
}
|
||||
return success;
|
||||
} catch (e) {
|
||||
console.error('removeFavorite failed', e);
|
||||
tracker.reportFavorite(currentRoute(), worksheetId, false);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取收藏列表
|
||||
*/
|
||||
export async function getFavoriteList(
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
): Promise<FavoriteRecord[]> {
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userFavorites',
|
||||
data: { action: 'list', page, pageSize },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0 ? result.data || [] : [];
|
||||
} catch (e) {
|
||||
console.error('getFavoriteList failed', e);
|
||||
return [];
|
||||
}
|
||||
const result = await apiRequest<MemberPage>('/me/favorites', {
|
||||
data: { page, pageSize },
|
||||
});
|
||||
return result.items.map((row) => ({
|
||||
...row,
|
||||
_id: row.id,
|
||||
worksheet: legacyWorksheet(row.worksheet),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户收藏总数
|
||||
*/
|
||||
export async function getFavoriteCount(): Promise<number> {
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userFavorites',
|
||||
data: { action: 'count' },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0 ? Number(result.data?.count || 0) : 0;
|
||||
} catch (e) {
|
||||
console.error('getFavoriteCount failed', e);
|
||||
return 0;
|
||||
}
|
||||
return (await memberStats()).favoriteCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已收藏
|
||||
*/
|
||||
export async function checkFavorited(worksheetId: string): Promise<boolean> {
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userFavorites',
|
||||
data: { action: 'check', worksheetId },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0 && result.data?.favorited;
|
||||
} catch (e) {
|
||||
console.error('checkFavorited failed', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量检查是否已收藏,返回 { worksheetId: true } 的 map
|
||||
*/
|
||||
export async function batchCheckFavorited(
|
||||
worksheetIds: string[],
|
||||
): Promise<Record<string, boolean>> {
|
||||
if (!worksheetIds.length) return {};
|
||||
try {
|
||||
await getUser();
|
||||
const res = await wx.cloud.callFunction({
|
||||
name: 'userFavorites',
|
||||
data: { action: 'batchCheck', worksheetIds },
|
||||
});
|
||||
const result = (res?.result as any) || {};
|
||||
return result.code === 0 ? result.data || {} : {};
|
||||
} catch (e) {
|
||||
console.error('batchCheckFavorited failed', e);
|
||||
return {};
|
||||
}
|
||||
return apiRequest('/me/favorites/check', {
|
||||
method: 'POST',
|
||||
data: { worksheetIds },
|
||||
});
|
||||
}
|
||||
export async function checkFavorited(worksheetId: string): Promise<boolean> {
|
||||
return (await batchCheckFavorited([worksheetId]))[worksheetId] === true;
|
||||
}
|
||||
|
||||
function currentRoute() {
|
||||
const pages = getCurrentPages();
|
||||
return pages[pages.length - 1]?.route || '';
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
type WorksheetStatsField = 'likes' | 'downloads';
|
||||
|
||||
export async function updateWorksheetStats(
|
||||
id: string,
|
||||
field: WorksheetStatsField,
|
||||
): Promise<boolean> {
|
||||
const worksheetId = String(id || '').trim();
|
||||
if (!worksheetId) return false;
|
||||
|
||||
try {
|
||||
const { result } = await wx.cloud.callFunction({
|
||||
name: 'worksheetsStatsUpdate',
|
||||
data: {
|
||||
id: worksheetId,
|
||||
field,
|
||||
},
|
||||
});
|
||||
return !!(result as { success?: boolean } | undefined)?.success;
|
||||
} catch (error) {
|
||||
console.error('update worksheet stats failed', field, worksheetId, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function incrementWorksheetLikes(id: string): Promise<boolean> {
|
||||
return updateWorksheetStats(id, 'likes');
|
||||
}
|
||||
|
||||
export function incrementWorksheetDownloads(id: string): Promise<boolean> {
|
||||
return updateWorksheetStats(id, 'downloads');
|
||||
}
|
||||
Reference in New Issue
Block a user