feat(platforms): refactor platform architecture and update API endpoints

- Refactor app.tsx to use new platform factory authorization system
- Update TemplateCard to use new URL property names (inputExampleUrl, outputExampleUrl)
- Enhance platform factory with authorization support for H5, TT, and WeApp
- Add new platform-specific authorization modules for multi-platform support
- Update SDK server endpoints to match new API structure
- Fix useAd hook factory instantiation timing
- Update API base URL for development environment
This commit is contained in:
imeepos 2025-09-26 22:28:58 +08:00
parent 9d890478d8
commit 142a38d7d1
14 changed files with 190 additions and 50 deletions

View File

@ -1,36 +1,29 @@
import { PropsWithChildren } from 'react' import { PropsWithChildren } from 'react'
import Taro, { useLaunch } from '@tarojs/taro' import { useLaunch } from '@tarojs/taro'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import configStore from './store' import configStore from './store'
import './app.css' import './app.css'
import { useServerSdk } from './hooks' import { createPlatformFactory } from './platforms'
const store = configStore() const store = configStore()
function App({ children }: PropsWithChildren<any>) { function App({ children }: PropsWithChildren<any>) {
const serverSdk = useServerSdk() useLaunch(async () => {
useLaunch(() => { const authorize = createPlatformFactory().createAuthorize()
Taro.login({
success: async (res) => { try {
try { // 检查登录状态包括OAuth 2.0回调处理
const login = await serverSdk.login({ ...res }) const isLoggedIn = await authorize.checkLogin()
// 获取accessToken然后存储到本地 if (!isLoggedIn) {
if (login?.tokens?.accessToken) { // 可以根据需要决定是否自动跳转登录
serverSdk.setAccessToken(login.tokens.accessToken, login.user.id) await authorize.login()
}
} catch (error) {
console.error('Login failed:', error)
}
},
fail: (error) => {
console.error('Taro.login failed:', error)
} }
}) } catch (error) {
console.log('App launched.') console.error('登录检查失败:', error)
}
}) })
// children 是将要会渲染的页面
return ( return (
<Provider store={store}> <Provider store={store}>
{children} {children}

View File

@ -18,13 +18,13 @@ export default function TemplateCard({ template, onClick }: TemplateCardProps) {
// 检测output是否为视频 // 检测output是否为视频
const isOutputVideo = useMemo(() => { const isOutputVideo = useMemo(() => {
return /\.(mp4|webm|ogg|mov|avi|mkv|flv)$/i.test(template.outputExample); return /\.(mp4|webm|ogg|mov|avi|mkv|flv)$/i.test(template.outputExampleUrl);
}, [template.outputExample]); }, [template.outputExampleUrl]);
// 检测input是否为视频 // 检测input是否为视频
const isInputVideo = useMemo(() => { const isInputVideo = useMemo(() => {
return /\.(mp4|webm|ogg|mov|avi|mkv|flv)$/i.test(template.inputExample); return /\.(mp4|webm|ogg|mov|avi|mkv|flv)$/i.test(template.inputExampleUrl);
}, [template.inputExample]); }, [template.inputExampleUrl]);
const handleClick = () => { const handleClick = () => {
if (!isDragging) { if (!isDragging) {
@ -86,10 +86,10 @@ export default function TemplateCard({ template, onClick }: TemplateCardProps) {
{isOutputVideo ? ( {isOutputVideo ? (
// 当output是视频时只显示单个视频 // 当output是视频时只显示单个视频
<View className="single-video-container"> <View className="single-video-container">
<Image className="video-poster" src={template.inputExample} mode="aspectFill" /> <Image className="video-poster" src={template.inputExampleUrl} mode="aspectFill" />
<Video <Video
className="single-video" className="single-video"
src={template.outputExample} src={template.outputExampleUrl}
autoplay autoplay
muted muted
loop loop
@ -116,7 +116,7 @@ export default function TemplateCard({ template, onClick }: TemplateCardProps) {
{isInputVideo ? ( {isInputVideo ? (
<Video <Video
className="full-video" className="full-video"
src={template.inputExample} src={template.inputExampleUrl}
autoplay autoplay
muted muted
loop loop
@ -127,7 +127,7 @@ export default function TemplateCard({ template, onClick }: TemplateCardProps) {
controls={false} controls={false}
/> />
) : ( ) : (
<Image className="full-image" src={template.inputExample} mode="aspectFill" lazyLoad /> <Image className="full-image" src={template.inputExampleUrl} mode="aspectFill" lazyLoad />
)} )}
</View> </View>
@ -138,7 +138,7 @@ export default function TemplateCard({ template, onClick }: TemplateCardProps) {
clipPath: `polygon(${splitPosition}% 0%, 100% 0%, 100% 100%, ${splitPosition}% 100%)`, clipPath: `polygon(${splitPosition}% 0%, 100% 0%, 100% 100%, ${splitPosition}% 100%)`,
}} }}
> >
<Image className="full-image" src={template.outputExample} mode="aspectFill" lazyLoad /> <Image className="full-image" src={template.outputExampleUrl} mode="aspectFill" lazyLoad />
</View> </View>
{/* 可拖拽的分割线 */} {/* 可拖拽的分割线 */}

View File

@ -18,8 +18,8 @@ interface UseAdOptions {
onClose?: (isEnded: boolean) => void; // 广告关闭回调,传入是否完整观看 onClose?: (isEnded: boolean) => void; // 广告关闭回调,传入是否完整观看
} }
// 创建平台广告实例 // 创建平台广告实例
const factory = createPlatformFactory()
export function useAd(options?: UseAdOptions): UseAdReturn { export function useAd(options?: UseAdOptions): UseAdReturn {
const factory = createPlatformFactory()
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [adAvailable, setAdAvailable] = useState(false); const [adAvailable, setAdAvailable] = useState(false);
const [adLoaded, setAdLoaded] = useState(false); // 广告是否已加载完成 const [adLoaded, setAdLoaded] = useState(false); // 广告是否已加载完成

View File

@ -1,6 +1,10 @@
import { MediaTT, RewardedVideoAdTT, UserInfoTT } from "./tt"; import { MediaTT, RewardedVideoAdTT, UserInfoTT } from "./tt";
import { Media, RewardedVideoAd, UserInfo } from "./core"; import { Media, RewardedVideoAd, UserInfo } from "./core";
import { MediaWeApp, RewardedVideoAdWeApp, UserInfoWeApp } from "./weapp"; import { MediaWeApp, RewardedVideoAdWeApp, UserInfoWeApp } from "./weapp";
import { Authorize } from "./types/index";
import { H5Authorize } from "./h5/index";
import { TtAuthorize } from "./tt/index";
import { WeappAuthorize } from "./weapp/index";
/** /**
* *
@ -97,6 +101,17 @@ export class PlatformFactory {
} }
} }
createAuthorize(): Authorize {
switch (this.platform) {
case 'bytedance':
return new TtAuthorize()
case 'wechat':
return new WeappAuthorize()
default:
return new H5Authorize()
}
}
/** /**
* *
* @returns Platform * @returns Platform
@ -111,7 +126,7 @@ export class PlatformFactory {
* @returns boolean * @returns boolean
*/ */
static isSupportedPlatform(platform: string): platform is Platform { static isSupportedPlatform(platform: string): platform is Platform {
return ['tt', 'weapp', 'alipay', 'swan', 'qq'].includes(platform); return ['tt', 'weapp', 'alipay', 'swan', 'qq', 'h5'].includes(platform);
} }
/** /**

View File

@ -0,0 +1,47 @@
import { Authorize } from "../types/index";
import { useServerSdk } from "../../hooks/index";
export class H5Authorize extends Authorize {
async checkLogin(): Promise<boolean> {
// 检查URL中是否有OAuth回调参数
const urlParams = new URLSearchParams(window.location.search);
const accessToken = urlParams.get('access_token');
const userId = urlParams.get('user_id');
if (accessToken && userId) {
// 处理OAuth回调
try {
const serverSdk = useServerSdk();
serverSdk.setAccessToken(accessToken, userId);
// 清理URL参数避免重复处理
const newUrl = window.location.pathname;
window.history.replaceState(null, '', newUrl);
console.log('OAuth登录成功token已保存');
return true;
} catch (error) {
console.error('处理OAuth回调失败:', error);
return false;
}
}
// 如果没有回调参数检查本地存储的token
try {
const serverSdk = useServerSdk();
const profile = await serverSdk.profile();
return !!profile;
} catch (error) {
console.log('本地token无效或已过期');
return false;
}
}
async login(): Promise<void> {
const { hostname, protocol, port } = window.location;
let baseUrl = `${protocol}//${hostname}`
if (hostname === 'localhost') {
baseUrl = `${protocol}//${hostname}:${port}`
}
window.location.href = `https://mixvideo-workflow.bowong.cc/auth/google/authorize?redirect_url=${baseUrl}`
}
}

View File

@ -0,0 +1 @@
export * from './authorize';

View File

@ -0,0 +1,40 @@
import Taro from "@tarojs/taro";
import { Authorize } from "../types/index";
import { useServerSdk } from "../../hooks/index";
export class TtAuthorize extends Authorize {
async checkLogin(): Promise<boolean> {
try {
const serverSdk = useServerSdk();
const profile = await serverSdk.profile();
return !!profile;
} catch (error) {
console.log('字节跳动小程序token无效或已过期');
return false;
}
}
async login(): Promise<void> {
return new Promise<void>((resolve, reject) => {
const serverSdk = useServerSdk()
Taro.login({
success: async (res) => {
try {
const login = await serverSdk.login({ ...res })
// 获取accessToken然后存储到本地
if (login?.tokens?.accessToken) {
serverSdk.setAccessToken(login.tokens.accessToken, login.user.id)
resolve()
return;
}
reject(new Error(`登录失败, access token为空`))
} catch (error) {
reject(error)
}
},
fail: (error) => {
reject(error)
}
})
})
}
}

View File

@ -0,0 +1 @@
export * from './authorize'

View File

@ -0,0 +1,4 @@
export abstract class Authorize {
abstract login(): Promise<void>;
abstract checkLogin(): Promise<boolean>;
}

View File

@ -0,0 +1 @@
export * from './authorize'

View File

@ -291,7 +291,6 @@ export class UserInfoWeApp extends UserInfo {
} }
} }
export class MediaWeApp extends Media { export class MediaWeApp extends Media {
downloadFile(url: string): Promise<IDownloadFile> { downloadFile(url: string): Promise<IDownloadFile> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@ -0,0 +1,40 @@
import Taro from "@tarojs/taro";
import { Authorize } from "../types/index";
import { useServerSdk } from "../../hooks/index";
export class WeappAuthorize extends Authorize {
async checkLogin(): Promise<boolean> {
try {
const serverSdk = useServerSdk();
const profile = await serverSdk.profile();
return !!profile;
} catch (error) {
console.log('微信小程序token无效或已过期');
return false;
}
}
async login(): Promise<void> {
return new Promise<void>((resolve, reject) => {
const serverSdk = useServerSdk()
Taro.login({
success: async (res) => {
try {
const login = await serverSdk.login({ ...res })
// 获取accessToken然后存储到本地
if (login?.tokens?.accessToken) {
serverSdk.setAccessToken(login.tokens.accessToken, login.user.id)
resolve()
return;
}
reject(new Error(`登录失败, access token为空`))
} catch (error) {
reject(error)
}
},
fail: (error) => {
reject(error)
}
})
})
}
}

View File

@ -0,0 +1 @@
export * from './authorize'

View File

@ -15,8 +15,8 @@ export interface Template {
description: string; // 模板详细描述 description: string; // 模板详细描述
creditCost: number; // 积分消耗 creditCost: number; // 积分消耗
version: string; // 版本号 version: string; // 版本号
inputExample: string; // 原始图片 inputExampleUrl: string; // 原始图片
outputExample: string; // 输出图片 outputExampleUrl: string; // 输出图片
tags: string[]; // 标签数组 tags: string[]; // 标签数组
templateType: string; // 模板类型 templateType: string; // 模板类型
} }
@ -57,7 +57,7 @@ export class SdkServer {
private readonly baseUrl: string; private readonly baseUrl: string;
private readonly timeout: number; private readonly timeout: number;
constructor(url: string = `https://sd2s2bl25ni4n75na2bog.apigateway-cn-beijing.volceapi.com`, timeout: number = 5 * 60 * 1000) { constructor(url: string = `http://127.0.0.1:8787`, timeout: number = 5 * 60 * 1000) {
this.baseUrl = url.endsWith('/') ? url.slice(0, -1) : url; this.baseUrl = url.endsWith('/') ? url.slice(0, -1) : url;
this.timeout = timeout; this.timeout = timeout;
} }
@ -167,8 +167,8 @@ export class SdkServer {
* GET / * GET /
*/ */
async getAllTemplates(): Promise<Template[]> { async getAllTemplates(): Promise<Template[]> {
const response = await this.request<Template[]>('/api/v1/templates'); const response = await this.request<{ templates: Template[] }>('/templates');
return response.data || []; return response.data.templates || [];
} }
/** /**
@ -177,7 +177,7 @@ export class SdkServer {
*/ */
async getTemplate(templateCode: string): Promise<Template | null> { async getTemplate(templateCode: string): Promise<Template | null> {
try { try {
const response = await this.request<Template | null>(`/api/v1/templates/${templateCode}`); const response = await this.request<Template | null>(`/templates/${templateCode}`);
return response.data; return response.data;
} catch (error) { } catch (error) {
console.warn(`Template ${templateCode} not found:`, error); console.warn(`Template ${templateCode} not found:`, error);
@ -191,7 +191,7 @@ export class SdkServer {
*/ */
async executeTemplate(params: ExecuteTemplateParams): Promise<string | null> { async executeTemplate(params: ExecuteTemplateParams): Promise<string | null> {
try { try {
const response = await this.request<string | any | null>(`/api/v1/templates/code/${params.templateCode}/execute`, 'POST', { const response = await this.request<string | any | null>(`/templates/code/${params.templateCode}/execute`, 'POST', {
imageUrl: params.imageUrl, imageUrl: params.imageUrl,
}); });
const data = response.data; const data = response.data;
@ -212,7 +212,7 @@ export class SdkServer {
async login(params: any): Promise<{ tokens: IToken, user: IUser }> { async login(params: any): Promise<{ tokens: IToken, user: IUser }> {
try { try {
const platform = createPlatformFactory() const platform = createPlatformFactory()
const response = await this.request<{ tokens: IToken, user: IUser }>(`/api/v1/users/login`, 'POST', { const response = await this.request<{ tokens: IToken, user: IUser }>(`/login`, 'POST', {
platform: platform.getPlatform(), platform: platform.getPlatform(),
code: params.code, code: params.code,
encryptedData: params.encryptedData, encryptedData: params.encryptedData,
@ -231,7 +231,7 @@ export class SdkServer {
*/ */
async profile() { async profile() {
try { try {
const response = await this.request<{ tokens: IToken }>(`/api/v1/users/profile`, 'GET', {}); const response = await this.request<{ tokens: IToken }>(`/auth/google/userinfo`, 'GET', {});
return response.data return response.data
} catch (error) { } catch (error) {
throw error; throw error;
@ -243,7 +243,6 @@ export class SdkServer {
*/ */
async getTemplatesByCodes(templateCodes: string[]): Promise<Template[]> { async getTemplatesByCodes(templateCodes: string[]): Promise<Template[]> {
const templates: Template[] = []; const templates: Template[] = [];
for (const code of templateCodes) { for (const code of templateCodes) {
try { try {
const template = await this.getTemplate(code); const template = await this.getTemplate(code);
@ -254,7 +253,6 @@ export class SdkServer {
console.warn(`Failed to get template ${code}:`, error); console.warn(`Failed to get template ${code}:`, error);
} }
} }
return templates; return templates;
} }
@ -266,7 +264,7 @@ export class SdkServer {
async getTaskProgress(taskId: string) { async getTaskProgress(taskId: string) {
try { try {
console.log(`task id is : ${taskId}`) console.log(`task id is : ${taskId}`)
const response = await this.request<any>(`/api/v1/templates/execution/${taskId}/progress`, 'GET', {}); const response = await this.request<any>(`/templates/execution/${taskId}/progress`, 'GET', {});
console.log(`getTaskProgress response:`, response.data) console.log(`getTaskProgress response:`, response.data)
return response.data return response.data
} catch (error) { } catch (error) {
@ -279,8 +277,8 @@ export class SdkServer {
async getMineLogs() { async getMineLogs() {
try { try {
const userId = Taro.getStorageSync(TOKEN_UID_KEY); const userId = Taro.getStorageSync(TOKEN_UID_KEY);
const response = await this.request<any[]>(`/api/v1/templates/executions/user/${userId}`, 'GET', {}); const response = await this.request<{ executions: any[] }>(`/templates/executions/user/${userId}`, 'GET', {});
return response.data return response.data.executions
} catch (error) { } catch (error) {
throw error; throw error;
} }