deepseek4j-demo快速入门

deepseek4j-demo快速入门

deepseek4j 是面向 DeepSeek 推出的 Java 开发 SDK,支持 DeepSeek R1 和 V3 全系列模型。提供对话推理、函数调用、JSON结构化输出、以及基于 OpenAI 兼容 API 协议的嵌入向量生成能力。通过 Spring Boot Starter 模块,开发者可以快速为 Spring Boot 2.x/3.x 以及 Solon 等主流 Java Web 框架集成 AI 能力,提供开箱即用的配置体系、自动装配的客户端实例,以及便捷的流式响应支持。

1. 目标

实现ai对话

2.上代码

2.1.Maven 依赖

在你的 pom.xml 中添加以下依赖:

<dependency>
  <groupId>io.github.pig-mesh.ai</groupId>
  <artifactId>deepseek-spring-boot-starter</artifactId>
  <version>1.4.3</version>
</dependency>

2.2.基础配置

application.ymlapplication.properties 中添加必要的配置:

deepseek:
  api-key: your-api-key-here  # 必填项:你的 API 密钥
  model: deepseek-reasoner
  base-url: https://api.deepseek.com  # 可选,默认为官方 API 地址

2.3. 基础使用示例

2.3.1. 流式返回示例
@Autowired
private DeepSeekClient deepSeekClient;

@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ChatCompletionResponse> chat(String prompt) {
    return deepSeekClient.chatFluxCompletion(prompt);
}
2.3.2. 进阶配置示例
  • ChatCompletionRequest 配置构造
@GetMapping(value = "/chat/advanced", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
   public Flux<ChatCompletionResponse> chatAdvanced(String prompt) {
   	ChatCompletionRequest request = ChatCompletionRequest.builder()
   			.addSystemMessage("你的名字叫土豆")
   			// 添加用户消息
   			.addUserMessage(prompt)
   			// 设置最大生成 token 数,默认 2048
   			.maxTokens(1000)
   			// 设置响应格式,支持 JSON 结构化输出
//				.responseFormat(...) // 可选
   	// function calling
//        .tools(...) // 可选
       .build();

   	return deepSeekClient.chatFluxCompletion(request);
   }
2.3.3. 多轮会话
public final static HashMap<String, String> cache = new HashMap<>();

	@GetMapping(value = "/chat/advanced", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
	public Flux<ChatCompletionResponse> chatAdvanced(String prompt, String cacheCode) {
		log.info("cacheCode {}", cacheCode);

		ChatCompletionRequest request = ChatCompletionRequest.builder()
				.addUserMessage(prompt)
				.addAssistantMessage(elt.apply(cache.getOrDefault(cacheCode, "")))
				.addSystemMessage("你是一个专业的助手").maxCompletionTokens(5000).build();
		log.info("request {}", Json.toJson(request));
		// 只保留上一次回答内容
		cache.remove(cacheCode);
		return deepSeekClient.chatFluxCompletion(request).doOnNext(i -> {
			String content = choicesProcess.apply(i.choices());
			// 其他ELT流程
			cache.merge(cacheCode, content, String::concat);
		}).doOnError(e -> log.error("/chat/advanced error:{}", e.getMessage()));
	}


	Function<List<ChatCompletionChoice>, String> choicesProcess = list -> list.stream().map(e -> e.delta().content())
			.collect(Collectors.joining());

	Function<String, String> elt = s -> s.replaceAll("<think>[\\s\\S]*?</think>", "").replaceAll("\n", "");

2.3.4. 同步输出 (非实时响应流)

不推荐使用同步阻塞调用方式,R1模型推理耗时较长易导致客户端连接超时,且响应延迟会影响用户体验

@GetMapping(value = "/sync/chat")
public ChatCompletionResponse syncChat(String prompt) {
  ChatCompletionRequest request = ChatCompletionRequest.builder()
      // 根据渠道模型名称动态修改这个参数
      .model(deepSeekProperties.getModel())
      .addUserMessage(prompt).build();

  return deepSeekClient.chatCompletion(request).execute();
}

2.4. 前端调试页面

see.html

<!DOCTYPE html>
<html lang="zh-CN" :data-theme="currentTheme">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>deepseek 调试</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.5.13/vue.global.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/marked/15.0.6/marked.min.js"></script>
    <!-- Tailwind CSS -->
    <link href="https://cdn.bootcdn.net/ajax/libs/daisyui/4.12.23/full.min.css" rel="stylesheet" type="text/css" />
    <script src="https://cdn.tailwindcss.com"></script>
    <script>
        tailwind.config = {
            daisyui: {
                themes: [{
                    light: {
                        "primary": "#570DF8",
                        "primary-focus": "#4506CB",
                        "secondary": "#F000B8",
                        "accent": "#37CDBE",
                        "neutral": "#3D4451",
                        "base-100": "#FFFFFF",
                        "base-200": "#F2F2F2",
                        "base-300": "#E5E6E6",
                        "base-content": "#1F2937",
                        "info": "#3ABFF8",
                        "success": "#36D399",
                        "warning": "#FBBD23",
                        "error": "#F87272"
                    },
                    dark: {
                        "primary": "#BB86FC",
                        "primary-focus": "#9965E3",
                        "secondary": "#03DAC6",
                        "accent": "#BB86FC",
                        "neutral": "#121212",
                        "base-100": "#1E1E1E",
                        "base-200": "#2C2C2C",
                        "base-300": "#242424",
                        "base-content": "#E1E1E1",
                        "info": "#0175C2",
                        "success": "#00C853",
                        "warning": "#FFB74D",
                        "error": "#CF6679",
                    }
                }],
            },
        }
    </script>
    <style>
        /* 暗色模式下的滚动条样式 */
        [data-theme='dark'] ::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }

        [data-theme='dark'] ::-webkit-scrollbar-track {
            background: #2C2C2C;
            border-radius: 4px;
        }

        [data-theme='dark'] ::-webkit-scrollbar-thumb {
            background: #424242;
            border-radius: 4px;
        }

        [data-theme='dark'] ::-webkit-scrollbar-thumb:hover {
            background: #505050;
        }
    </style>
</head>
<body class="h-screen transition-colors duration-200 flex flex-col overflow-hidden">
<div id="app">
    <!-- 导航栏 -->
    <div class="navbar bg-base-100 shadow-lg px-4 flex-none h-16">
        <div class="flex-1">
            <h1 class="text-xl font-bold">Deepseek 调试工具</h1>
        </div>
        <div class="flex-none gap-2">
            <div class="dropdown dropdown-end">
                <label class="swap swap-rotate btn btn-ghost btn-circle">
                    <input type="checkbox"
                           :checked="currentTheme === 'dark'"
                           @change="toggleTheme"
                           class="theme-controller"/>
                    <!-- sun icon -->
                    <svg class="swap-on fill-current w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z"/></svg>
                    <!-- moon icon -->
                    <svg class="swap-off fill-current w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z"/></svg>
                </label>
            </div>
        </div>
    </div>

    <main class="h-[calc(100vh-8rem)] container mx-auto px-4 py-2 overflow-hidden">
        <div class="flex gap-4 h-full">
            <!-- 左侧原始数据区域 -->
            <div class="w-1/2 flex flex-col">
                <!-- 头部控制区 -->
                <div class="bg-base-100 dark:bg-base-200 rounded-box shadow-lg dark:shadow-lg/20 p-4 mb-2 flex-none backdrop-blur-sm">
                    <div class="mb-4 flex items-center">
                        <label class="label w-20">
                            <span class="label-text dark:text-base-content/80">SSE地址</span>
                        </label>
                        <input
                                type="text"
                                v-model="sseUrl"
                                placeholder="请输入SSE地址"
                                class="input input-bordered w-full dark:bg-base-300 dark:border-base-content/10 dark:text-base-content/90 dark:placeholder-base-content/50"
                        >
                    </div>
                    <div class="flex space-x-2">
                        <button
                                @click="connect"
                                :disabled="isConnected"
                                class="btn btn-primary"
                        >
                            连接
                        </button>
                        <button
                                @click="disconnect"
                                :disabled="!isConnected"
                                class="btn btn-error"
                        >
                            断开
                        </button>
                        <button
                                @click="clearMessages"
                                class="btn btn-ghost"
                        >
                            清空
                        </button>
                    </div>
                </div>

                <!-- 消息展示区 -->
                <div class="flex-1 flex flex-col min-h-0">
                    <h2 class="text-xl font-bold mb-1 dark:text-base-content/90">原始数据</h2>
                    <div class="flex-1 overflow-y-auto min-h-0 messages-container">
                        <div class="mockup-window bg-base-300 dark:bg-base-300/50 h-full flex flex-col backdrop-blur-sm">
                            <div class="flex-1 px-6 py-4 bg-base-200 dark:bg-base-200/50 overflow-y-auto">
                                <div v-if="messages.length === 0" class="text-gray-400 dark:text-gray-500 text-center py-4">
                                    暂无消息
                                </div>
                                <div
                                        v-for="(message, index) in messages"
                                        :key="index"
                                        class="border-b border-base-200 last:border-0 py-3"
                                >
                                    <div class="text-xs opacity-70 mb-1">{{ message.time }}</div>
                                    <pre class="mockup-code mt-2">{{ message.data }}</pre>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <!-- 右侧推理过程和答案区域 -->
            <div class="w-1/2 flex flex-col gap-2 min-h-0">
                <div class="flex-1 flex flex-col min-h-0">
                    <h2 class="text-xl font-bold mb-1">推理过程</h2>
                    <div ref="reasoningRef" class="flex-1 overflow-y-auto min-h-0">
                        <div class="mockup-window bg-base-300 h-full flex flex-col">
                            <div class="flex-1 px-6 py-4 bg-base-200 overflow-y-auto">
                                {{ reasoningChain }}
                            </div>
                        </div>
                    </div>
                </div>

                <div class="flex-1 flex flex-col min-h-0">
                    <h2 class="text-xl font-bold mb-1">最终答案</h2>
                    <div ref="answerRef" class="flex-1 overflow-y-auto min-h-0">
                        <div class="mockup-window bg-base-300 h-full flex flex-col">
                            <div class="flex-1 px-6 py-4 bg-base-200 overflow-y-auto prose dark:prose-invert max-w-none" v-html="finalAnswer">
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </main>

    <!-- Footer -->
    <footer class="footer footer-center p-2 bg-base-300 dark:bg-base-300/50 text-base-content dark:text-base-content/70 flex-none h-12 backdrop-blur-sm">
        <aside class="flex items-center gap-2">
            <p>Powered by</p>
            <div class="flex items-center gap-1">
                <span class="font-semibold">Pig AI</span>
            </div>
        </aside>
    </footer>
</div>

<script>
    const { createApp, ref, computed, watch, nextTick, onMounted } = Vue

    createApp({
        setup() {
            const sseUrl = ref('http://127.0.0.1:8080/chat?prompt=你叫什么名字')
            const messages = ref([])
            const isConnected = ref(false)
            const currentTheme = ref('light')
            const isInThinkTag = ref(false)
            let eventSource = null
            const reasoningRef = ref(null)
            const answerRef = ref(null)

            // 计算推理过程(思考链)
            const reasoningChain = computed(() => {
                return messages.value
                        .filter(m => m.parsed?.reasoning_content)
                        .map(m => m.parsed.reasoning_content)
                        .join('')
            })

            // 计算最终答案
            const finalAnswer = computed(() => {
                const rawContent = messages.value
                        .filter(m => m.parsed?.content)
                        .map(m => m.parsed.content)
                        .join('')
                return marked.parse(rawContent)
            })

            // 初始化主题
            onMounted(() => {
                const savedTheme = localStorage.getItem('theme')
                currentTheme.value = savedTheme || 'light'
                document.documentElement.setAttribute('data-theme', currentTheme.value)
            })

            // 切换主题
            const toggleTheme = () => {
                currentTheme.value = currentTheme.value === 'light' ? 'dark' : 'light'
                localStorage.setItem('theme', currentTheme.value)
                document.documentElement.setAttribute('data-theme', currentTheme.value)
            }

            // 滚动到底部的函数
            const scrollToBottom = (element) => {
                if (element) {
                    // 获取实际的滚动容器(mockup-window 内的 overflow-y-auto 元素)
                    const scrollContainer = element.querySelector('.overflow-y-auto')
                    if (scrollContainer) {
                        scrollContainer.scrollTop = scrollContainer.scrollHeight
                    }
                }
            }

            // 监听消息变化,自动滚动
            watch(() => [messages.value.length, reasoningChain.value, finalAnswer.value], () => {
                nextTick(() => {
                    if (reasoningRef.value) {
                        scrollToBottom(reasoningRef.value)
                    }
                    if (answerRef.value) {
                        scrollToBottom(answerRef.value)
                    }
                    // 滚动原始数据区域
                    const messagesContainer = document.querySelector('.messages-container .overflow-y-auto')
                    if (messagesContainer) {
                        messagesContainer.scrollTop = messagesContainer.scrollHeight
                    }
                })
            }, { deep: true })

            const parseSSEData = (data) => {
                try {
                    const parsed = JSON.parse(data)

                    // 检查是否直接返回了 reasoning_content
                    const directReasoning = parsed.choices?.[0]?.delta?.reasoning_content
                    if (directReasoning) {
                        return {
                            id: parsed.id,
                            created: parsed.created,
                            model: parsed.model,
                            reasoning_content: directReasoning,
                            content: parsed.choices?.[0]?.delta?.content || ''
                        }
                    }

                    const content = parsed.choices?.[0]?.delta?.content || ''

                    // 处理 think 标签包裹的情况
                    if (content.includes('<think>')) {
                        isInThinkTag.value = true
                        const startIndex = content.indexOf('<think>') + '<think>'.length
                        return {
                            id: parsed.id,
                            created: parsed.created,
                            model: parsed.model,
                            reasoning_content: content.substring(startIndex),
                            content: content.substring(0, content.indexOf('<think>'))
                        }
                    }

                    if (content.includes('</think>')) {
                        isInThinkTag.value = false
                        const endIndex = content.indexOf('</think>')
                        return {
                            id: parsed.id,
                            created: parsed.created,
                            model: parsed.model,
                            reasoning_content: content.substring(0, endIndex),
                            content: content.substring(endIndex + '</think>'.length)
                        }
                    }

                    // 根据状态决定内容归属
                    return {
                        id: parsed.id,
                        created: parsed.created,
                        model: parsed.model,
                        reasoning_content: isInThinkTag.value ? content : '',
                        content: isInThinkTag.value ? '' : content
                    }
                } catch (e) {
                    console.error('解析JSON失败:', e)
                    return null
                }
            }

            const connect = () => {
                if (eventSource) {
                    eventSource.close()
                }

                try {
                    eventSource = new EventSource(sseUrl.value)
                    isConnected.value = true

                    eventSource.onmessage = (event) => {
                        const parsed = parseSSEData(event.data)
                        messages.value.push({
                            time: new Date().toLocaleTimeString(),
                            data: event.data,
                            parsed: parsed
                        })
                    }

                    eventSource.onerror = (error) => {
                        console.error('SSE Error:', error)
                        disconnect()
                    }
                } catch (error) {
                    console.error('Connection Error:', error)
                    disconnect()
                }
            }

            const disconnect = () => {
                if (eventSource) {
                    eventSource.close()
                    eventSource = null
                }
                isConnected.value = false
            }

            const clearMessages = () => {
                messages.value = []
            }

            return {
                sseUrl,
                messages,
                isConnected,
                currentTheme,
                toggleTheme,
                connect,
                disconnect,
                clearMessages,
                reasoningChain,
                finalAnswer,
                reasoningRef,
                answerRef
            }
        }
    }).mount('#app')
</script>
</body>
</html>

3. 测试

3.1. chat单元测试

	@Test
	public void chat(){
		Flux<ChatCompletionResponse> responseFlux = deepSeekController.chat("你叫什么名字");
		responseFlux
				.doOnNext(item ->
						{
							try {
								System.out.print(item.choices().get(0).delta().content());
							}catch (Exception e){}
						}
				)
				.doOnError(error -> log.error("Error: {}", error))
				.doOnComplete(() -> log.info("Completed"))
				.subscribe();
		System.out.println(responseFlux.collectList().block());
	}

在这里插入图片描述

3.2. chatAdvanced单元测试

	@Test
	public void chatAdvanced(){
		Flux<ChatCompletionResponse> responseFlux = deepSeekController.chatAdvanced("你叫什么名字");
		responseFlux
				.doOnNext(item ->
						{
							try {
								System.out.print(item.choices().get(0).delta().content());
							}catch (Exception e){}
						}
				)
				.doOnError(error -> log.error("Error: {}", error))
				.doOnComplete(() -> log.info("Completed"))
				.subscribe();
		System.out.println(responseFlux.collectList().block());
	}

在这里插入图片描述

3.3. see.html测试

点击连接

在这里插入图片描述

博客地址

代码下载

下面的deepseek4j-demo

<think>好的,用户正在寻找关于Vue3和DeepSeek Chat的演示项目或示例代码。首先,我需要确定用户的具体需求。他们可能想开发一个聊天应用,并希望参考现有的Vue3项目结构或者集成方法。 接下来,查看提供的引用内容。引用[2]提到了一个BasicVueChat组件,路径在/src/components/basic-vue-chat/,这可能是一个起点。引用[3]提到了使用axios和CometChat的JavaScript SDK,这可能与聊天功能相关。引用[5]提到使用yarn dev启动本地服务器,说明可能有现有的Vue项目配置可供参考。 用户可能需要代码示例,所以需要整理一个简单的Vue3组件结构。考虑到实时聊天可能需要WebSocket,但引用中没有提到,所以可能需要使用HTTP轮询或者第三方服务如CometChat。不过引用[3]提到了@cometchat-pro/chat的SDK,所以可能需要指导如何集成这个SDK到Vue3项目中。 另外,用户可能对如何调用DeepSeek的API感兴趣,需要结合axios发送请求。例如,在Vue3中使用setup语法,结合axios处理异步请求,并将消息列表和输入框绑定到响应式数据。 需要确保回答结构清晰,分步骤说明,比如安装依赖、配置SDK、创建组件、处理消息发送和接收。同时,提供代码示例时要注意使用正确的语法,比如Vue3的Composition API,以及正确的API调用方式。 还要考虑用户可能遇到的常见问题,比如跨域问题、身份验证、状态管理等,需要给出相应的提示或解决方案。例如,引用[1]提到VuePack可能包含代码片段,但不确定是否适用,但引用[2]中的组件结构可以作为参考。 最后,生成相关问题,帮助用户深入理解,比如如何优化性能、处理实时消息、集成第三方服务等。确保引用正确,比如引用[2]和[3]的相关内容。</think>以下是基于Vue3开发聊天应用的实现思路和示例代码框架: ### 一、基础项目结构 建议使用Vite创建Vue3项目: ```bash npm create vite@latest vue3-chat-demo --template vue ``` ### 二、核心组件实现 ```vue <!-- src/components/ChatWindow.vue --> <script setup> import { ref } from 'vue' import axios from 'axios' const messages = ref([]) const newMessage = ref('') const sendMessage = async () => { if (!newMessage.value.trim()) return try { // 调用DeepSeek API示例 const response = await axios.post('https://api.deepseek.com/chat', { message: newMessage.value, model: 'your-model-name' }, { headers: { 'Authorization': `Bearer ${import.meta.env.VITE_API_KEY}` } }) messages.value.push({ text: newMessage.value, isUser: true, timestamp: new Date() }) messages.value.push({ text: response.data.answer, isUser: false, timestamp: new Date() }) newMessage.value = '' } catch (error) { console.error('API请求失败:', error) } } </script> <template> <div class="chat-container"> <div class="message-list"> <div v-for="(msg, index) in messages" :key="index" :class="['message', msg.isUser ? 'user' : 'bot']"> {{ msg.text }} <span class="timestamp">{{ msg.timestamp.toLocaleTimeString() }}</span> </div> </div> <div class="input-area"> <input v-model="newMessage" @keyup.enter="sendMessage" /> <button @click="sendMessage">发送</button> </div> </div> </template> ``` ### 三、关键技术集成 1. **状态管理**(推荐Pinia): ```javascript // stores/chat.js import { defineStore } from 'pinia' export const useChatStore = defineStore('chat', { state: () => ({ history: [], apiConfig: { endpoint: 'https://api.deepseek.com/v1', model: 'deepseek-chat' } }), actions: { addMessage(message) { this.history.push(message) } } }) ``` 2. **实时通信增强**(参考CometChat集成[^3]): ```javascript import { CometChat } from "@cometchat-pro/chat" const initChatSDK = async () => { const appID = "YOUR_APP_ID" const region = "YOUR_REGION" const appSetting = new CometChat.AppSettingsBuilder() .subscribePresenceForAllUsers() .setRegion(region) .build() await CometChat.init(appID, appSetting) } ``` ### 四、运行配置 在`vite.config.js`中添加环境变量: ```javascript export default defineConfig({ plugins: [vue()], define: { 'import.meta.env.VITE_API_KEY': JSON.stringify(process.env.VITE_API_KEY) } }) ``` ### 五、部署说明 1. 安装依赖: ```bash yarn add axios @cometchat-pro/chat pinia ``` 2. 开发模式运行: ```bash yarn dev # 启动本地8080端口服务[^5] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liuhm~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值