更新功能和文档: 增强支付系统、任务队列管理、用户作品管理等功能
This commit is contained in:
@@ -286,3 +286,5 @@ grep "img2vid_abc123def456" logs/application.log
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -287,3 +287,5 @@ public TaskQueue addTextToVideoTask(String username, String taskId) {
|
||||
5. **监控告警**: 监控积分冻结系统的运行状态
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -32,3 +32,5 @@ public class PasswordChecker {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -296,3 +296,5 @@ const startPolling = (taskId) => {
|
||||
如有问题,请联系开发团队或查看系统日志获取详细错误信息。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -279,3 +279,5 @@ POST /api/text-to-video/tasks/{id}/cancel # 取消任务
|
||||
**文生视频API已成功实现并可以投入使用!** 🎉
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -166,3 +166,5 @@ const updateWork = async (workId, updateData) => {
|
||||
5. **用户体验**: 提供友好的作品管理界面
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -432,3 +432,5 @@ MIT License
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -28,3 +28,5 @@ console.log('App.vue 加载成功')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@ export const login = (credentials) => {
|
||||
return api.post('/auth/login', credentials)
|
||||
}
|
||||
|
||||
// 邮箱验证码登录
|
||||
export const loginWithEmail = (credentials) => {
|
||||
return api.post('/auth/login/email', credentials)
|
||||
}
|
||||
|
||||
export const register = (userData) => {
|
||||
return api.post('/auth/register', userData)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export const cleanupApi = {
|
||||
// 获取清理统计信息(原始fetch方式,用于测试)
|
||||
async getCleanupStatsRaw() {
|
||||
try {
|
||||
const response = await fetch('/api/cleanup/cleanup-stats')
|
||||
const response = await fetch('http://localhost:8080/api/cleanup/cleanup-stats')
|
||||
if (response.ok) {
|
||||
return await response.json()
|
||||
} else {
|
||||
@@ -44,7 +44,7 @@ export const cleanupApi = {
|
||||
// 执行完整清理(原始fetch方式,用于测试)
|
||||
async performFullCleanupRaw() {
|
||||
try {
|
||||
const response = await fetch('/api/cleanup/full-cleanup', {
|
||||
const response = await fetch('http://localhost:8080/api/cleanup/full-cleanup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
||||
58
demo/frontend/src/api/userWorks.js
Normal file
58
demo/frontend/src/api/userWorks.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import request from './request'
|
||||
|
||||
// 获取我的作品列表
|
||||
export const getMyWorks = (params = {}) => {
|
||||
return request({
|
||||
url: '/works/my-works',
|
||||
method: 'GET',
|
||||
params: {
|
||||
page: params.page || 0,
|
||||
size: params.size || 10
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取作品详情
|
||||
export const getWorkDetail = (workId) => {
|
||||
return request({
|
||||
url: `/works/${workId}`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
// 删除作品
|
||||
export const deleteWork = (workId) => {
|
||||
return request({
|
||||
url: `/works/${workId}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
// 批量删除作品
|
||||
export const batchDeleteWorks = (workIds) => {
|
||||
return request({
|
||||
url: '/works/batch-delete',
|
||||
method: 'POST',
|
||||
data: {
|
||||
workIds: workIds
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 更新作品信息
|
||||
export const updateWork = (workId, data) => {
|
||||
return request({
|
||||
url: `/works/${workId}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取作品统计信息
|
||||
export const getWorkStats = () => {
|
||||
return request({
|
||||
url: '/works/stats',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -91,3 +91,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
502
demo/frontend/src/components/PaymentModal.vue
Normal file
502
demo/frontend/src/components/PaymentModal.vue
Normal file
@@ -0,0 +1,502 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
width="500px"
|
||||
class="payment-modal"
|
||||
:modal="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="true"
|
||||
@close="handleClose"
|
||||
center
|
||||
>
|
||||
<div class="payment-content">
|
||||
<!-- 支付方式选择 -->
|
||||
<div class="payment-methods">
|
||||
<div
|
||||
class="payment-method"
|
||||
:class="{ active: selectedMethod === 'alipay' }"
|
||||
@click="selectMethod('alipay')"
|
||||
>
|
||||
<div class="method-icon alipay-icon">
|
||||
<el-icon><CreditCard /></el-icon>
|
||||
</div>
|
||||
<span>Alipay扫码支付</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="payment-method"
|
||||
:class="{ active: selectedMethod === 'paypal' }"
|
||||
@click="selectMethod('paypal')"
|
||||
>
|
||||
<div class="method-icon paypal-icon">
|
||||
<el-icon><CreditCard /></el-icon>
|
||||
</div>
|
||||
<span>PayPal支付</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 金额显示 -->
|
||||
<div class="amount-section">
|
||||
<div class="amount-label">金额</div>
|
||||
<div class="amount-value">${{ amount }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 二维码区域 -->
|
||||
<div class="qr-section">
|
||||
<div class="qr-code">
|
||||
<img id="qr-code-img" style="display: none; width: 200px; height: 200px;" alt="支付二维码" />
|
||||
<div class="qr-placeholder">
|
||||
<div class="qr-grid">
|
||||
<div class="qr-dot" v-for="i in 64" :key="i"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="qr-tip">支付前请阅读《XX 付费服务协议》</div>
|
||||
</div>
|
||||
|
||||
<!-- 支付提示 -->
|
||||
<div class="action-section">
|
||||
<div class="pay-tip">
|
||||
<p>请使用支付宝扫描上方二维码完成支付</p>
|
||||
<p class="tip-small">支付完成后页面将自动更新</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部链接 -->
|
||||
<div class="footer-link">
|
||||
<a href="#" @click.prevent="showAgreement">《XX 付费服务协议》</a>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { CreditCard } from '@element-plus/icons-vue'
|
||||
import { createPayment, createAlipayPayment } from '@/api/payments'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '标准版会员'
|
||||
},
|
||||
amount: {
|
||||
type: [String, Number],
|
||||
default: '32.00'
|
||||
},
|
||||
orderId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'pay-success', 'pay-error'])
|
||||
|
||||
const visible = ref(false)
|
||||
const selectedMethod = ref('alipay')
|
||||
const loading = ref(false)
|
||||
|
||||
// 监听 modelValue 变化
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
visible.value = newVal
|
||||
})
|
||||
|
||||
// 监听 visible 变化
|
||||
watch(visible, (newVal) => {
|
||||
emit('update:modelValue', newVal)
|
||||
})
|
||||
|
||||
// 选择支付方式
|
||||
const selectMethod = (method) => {
|
||||
selectedMethod.value = method
|
||||
}
|
||||
|
||||
// 处理支付
|
||||
const handlePay = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
ElMessage.info('正在创建支付订单...')
|
||||
|
||||
// 创建支付订单数据
|
||||
const paymentData = {
|
||||
orderId: props.orderId,
|
||||
amount: props.amount.toString(),
|
||||
method: selectedMethod.value.toUpperCase(),
|
||||
description: `${props.title} - ${selectedMethod.value === 'alipay' ? '支付宝' : 'PayPal'}支付`
|
||||
}
|
||||
|
||||
console.log('=== 开始支付流程 ===')
|
||||
console.log('支付数据:', paymentData)
|
||||
|
||||
// 先创建支付记录
|
||||
console.log('1. 创建支付订单...')
|
||||
const createResponse = await createPayment(paymentData)
|
||||
console.log('创建支付订单响应:', createResponse)
|
||||
|
||||
if (createResponse.data && createResponse.data.success) {
|
||||
const paymentId = createResponse.data.data.id
|
||||
console.log('2. 支付订单创建成功,ID:', paymentId)
|
||||
|
||||
if (selectedMethod.value === 'alipay') {
|
||||
ElMessage.info('正在生成支付宝二维码...')
|
||||
|
||||
console.log('3. 创建支付宝支付...')
|
||||
// 创建支付宝支付
|
||||
const alipayResponse = await createAlipayPayment({ paymentId })
|
||||
console.log('支付宝支付响应:', alipayResponse)
|
||||
console.log('支付宝支付响应数据:', alipayResponse.data)
|
||||
console.log('支付宝支付响应数据详情:', JSON.stringify(alipayResponse.data, null, 2))
|
||||
|
||||
if (alipayResponse.data && alipayResponse.data.success) {
|
||||
// 显示二维码
|
||||
const qrCode = alipayResponse.data.data.qrCode
|
||||
console.log('4. 支付宝二维码:', qrCode)
|
||||
|
||||
// 更新二维码显示
|
||||
const qrCodeElement = document.querySelector('#qr-code-img')
|
||||
if (qrCodeElement) {
|
||||
qrCodeElement.src = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(qrCode)}`
|
||||
qrCodeElement.style.display = 'block'
|
||||
console.log('5. 二维码图片已设置')
|
||||
}
|
||||
|
||||
// 隐藏模拟二维码
|
||||
const qrPlaceholder = document.querySelector('.qr-placeholder')
|
||||
if (qrPlaceholder) {
|
||||
qrPlaceholder.style.display = 'none'
|
||||
console.log('6. 模拟二维码已隐藏')
|
||||
}
|
||||
|
||||
ElMessage.success('二维码已生成,请使用支付宝扫码支付')
|
||||
console.log('=== 支付流程完成 ===')
|
||||
|
||||
} else {
|
||||
console.error('支付宝响应失败:', alipayResponse)
|
||||
ElMessage.error(alipayResponse.data?.message || '生成二维码失败')
|
||||
emit('pay-error', new Error(alipayResponse.data?.message || '生成二维码失败'))
|
||||
}
|
||||
} else {
|
||||
// PayPal支付处理
|
||||
ElMessage.info('PayPal支付功能开发中...')
|
||||
emit('pay-error', new Error('PayPal支付功能暂未开放'))
|
||||
}
|
||||
|
||||
} else {
|
||||
console.error('创建支付订单失败:', createResponse)
|
||||
ElMessage.error(createResponse.data?.message || '创建支付订单失败')
|
||||
emit('pay-error', new Error(createResponse.data?.message || '创建支付订单失败'))
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('=== 支付流程出错 ===')
|
||||
console.error('错误详情:', error)
|
||||
console.error('错误响应:', error.response)
|
||||
console.error('错误状态:', error.response?.status)
|
||||
console.error('错误数据:', error.response?.data)
|
||||
|
||||
ElMessage.error(`支付失败:${error.message || '请重试'}`)
|
||||
emit('pay-error', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭模态框
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
// 显示协议
|
||||
const showAgreement = () => {
|
||||
ElMessage.info('服务协议页面')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.payment-modal {
|
||||
background: #0a0a0a;
|
||||
}
|
||||
|
||||
.payment-modal :deep(.el-dialog) {
|
||||
background: #0a0a0a;
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.payment-modal :deep(.el-dialog__header) {
|
||||
background: #0a0a0a;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
.payment-modal :deep(.el-dialog__title) {
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.payment-modal :deep(.el-dialog__headerbtn) {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.payment-modal :deep(.el-dialog__headerbtn:hover) {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.payment-content {
|
||||
padding: 24px;
|
||||
background: #0a0a0a;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 支付方式选择 */
|
||||
.payment-methods {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.payment-method {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
background: #1a1a1a;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.payment-method:hover {
|
||||
background: #2a2a2a;
|
||||
box-shadow: 0 4px 16px rgba(74, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.payment-method.active {
|
||||
background: linear-gradient(135deg, #4a9eff 0%, #3a8bdf 100%);
|
||||
color: white;
|
||||
box-shadow: 0 4px 16px rgba(74, 158, 255, 0.3);
|
||||
}
|
||||
|
||||
.method-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #1677FF;
|
||||
}
|
||||
|
||||
.payment-method.active .method-icon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.payment-method span {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 金额显示 */
|
||||
.amount-section {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.amount-label {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #e0e0e0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 二维码区域 */
|
||||
.qr-section {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
margin: 0 auto 16px;
|
||||
background: #1a1a1a;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.qr-placeholder {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.qr-placeholder::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border: 3px solid #4a9eff;
|
||||
border-radius: 8px;
|
||||
transform: translate(-50%, -50%);
|
||||
opacity: 0.6;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 0.6;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
transform: translate(-50%, -50%) scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.qr-placeholder::after {
|
||||
content: '扫码支付';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, calc(-50% + 40px));
|
||||
color: #4a9eff;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.qr-tip {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pay-button {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
background: #4a9eff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.pay-button:hover {
|
||||
background: #3a8bdf;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.pay-button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.pay-button:disabled {
|
||||
background: #666;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.pay-button:disabled:hover {
|
||||
background: #666;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* 支付提示 */
|
||||
.pay-tip {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background: #1a1a1a;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.pay-tip p {
|
||||
margin: 8px 0;
|
||||
color: #e0e0e0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tip-small {
|
||||
color: #999 !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
/* 底部链接 */
|
||||
.footer-link {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-link a {
|
||||
color: #4a9eff;
|
||||
text-decoration: none;
|
||||
font-size: 12px;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-link a:hover {
|
||||
color: #3a8bdf;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.payment-modal :deep(.el-dialog) {
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.payment-methods {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.qr-placeholder {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -167,7 +167,7 @@ const testGetStats = async () => {
|
||||
statsError.value = null
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/cleanup/cleanup-stats', {
|
||||
const response = await fetch('http://localhost:8080/api/cleanup/cleanup-stats', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...getAuthHeaders()
|
||||
@@ -193,7 +193,7 @@ const testFullCleanup = async () => {
|
||||
cleanupError.value = null
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/cleanup/full-cleanup', {
|
||||
const response = await fetch('http://localhost:8080/api/cleanup/full-cleanup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -252,7 +252,7 @@ const testQueueStatus = async () => {
|
||||
queueError.value = null
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/diagnostic/queue-status')
|
||||
const response = await fetch('http://localhost:8080/api/diagnostic/queue-status')
|
||||
if (response.ok) {
|
||||
queueResult.value = await response.json()
|
||||
ElMessage.success('获取队列状态成功')
|
||||
|
||||
@@ -201,11 +201,11 @@ const goToMyWorks = () => {
|
||||
}
|
||||
|
||||
const goToTextToVideo = () => {
|
||||
router.push('/text-to-video')
|
||||
router.push('/text-to-video/create')
|
||||
}
|
||||
|
||||
const goToStoryboardVideo = () => {
|
||||
router.push('/storyboard-video')
|
||||
router.push('/storyboard-video/create')
|
||||
}
|
||||
|
||||
const goToCreate = (work) => {
|
||||
|
||||
@@ -148,7 +148,7 @@ const getEmailCode = async () => {
|
||||
|
||||
try {
|
||||
// 调用后端API发送邮箱验证码
|
||||
const response = await fetch('/api/verification/email/send', {
|
||||
const response = await fetch('http://localhost:8080/api/verification/email/send', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -176,7 +176,7 @@ const getEmailCode = async () => {
|
||||
|
||||
// 开发模式:将验证码同步到后端
|
||||
try {
|
||||
await fetch('/api/verification/email/dev-set', {
|
||||
await fetch('http://localhost:8080/api/verification/email/dev-set', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -234,7 +234,7 @@ const handleLogin = async () => {
|
||||
|
||||
// 邮箱验证码登录
|
||||
try {
|
||||
const response = await fetch('/api/auth/login/email', {
|
||||
const response = await fetch('http://localhost:8080/api/auth/login/email', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
||||
@@ -315,6 +315,7 @@ import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Star, User, Compass, Document, VideoPlay, Picture, Film, Bell, Setting, Search } from '@element-plus/icons-vue'
|
||||
import { getMyWorks } from '@/api/userWorks'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -343,12 +344,20 @@ const items = ref([])
|
||||
const loadList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await api.get('/user-works')
|
||||
const response = await getMyWorks({
|
||||
page: page.value - 1, // 后端使用0-based分页
|
||||
size: pageSize.value
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
const data = response.data.data || []
|
||||
|
||||
if (page.value === 1) items.value = []
|
||||
items.value = items.value.concat(data)
|
||||
hasMore.value = data.length < pageSize.value
|
||||
hasMore.value = data.length === pageSize.value
|
||||
} else {
|
||||
throw new Error(response.data.message || '获取作品列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载作品列表失败:', error)
|
||||
ElMessage.error('加载作品列表失败')
|
||||
@@ -512,18 +521,18 @@ const goToSubscription = () => {
|
||||
}
|
||||
|
||||
const goToTextToVideo = () => {
|
||||
console.log('导航到文生视频')
|
||||
router.push('/text-to-video')
|
||||
console.log('导航到文生视频创作')
|
||||
router.push('/text-to-video/create')
|
||||
}
|
||||
|
||||
const goToImageToVideo = () => {
|
||||
console.log('导航到图生视频')
|
||||
router.push('/image-to-video')
|
||||
console.log('导航到图生视频创作')
|
||||
router.push('/image-to-video/create')
|
||||
}
|
||||
|
||||
const goToStoryboardVideo = () => {
|
||||
console.log('导航到分镜视频')
|
||||
router.push('/storyboard-video')
|
||||
console.log('导航到分镜视频创作')
|
||||
router.push('/storyboard-video/create')
|
||||
}
|
||||
|
||||
// 重置筛选器
|
||||
|
||||
@@ -179,15 +179,15 @@ const goToMyWorks = () => {
|
||||
}
|
||||
|
||||
const goToTextToVideo = () => {
|
||||
router.push('/text-to-video')
|
||||
router.push('/text-to-video/create')
|
||||
}
|
||||
|
||||
const goToImageToVideo = () => {
|
||||
router.push('/image-to-video')
|
||||
router.push('/image-to-video/create')
|
||||
}
|
||||
|
||||
const goToStoryboardVideo = () => {
|
||||
router.push('/storyboard-video')
|
||||
router.push('/storyboard-video/create')
|
||||
}
|
||||
|
||||
// 跳转到数据仪表盘
|
||||
|
||||
@@ -201,11 +201,11 @@ const goToMyWorks = () => {
|
||||
}
|
||||
|
||||
const goToTextToVideo = () => {
|
||||
router.push('/text-to-video')
|
||||
router.push('/text-to-video/create')
|
||||
}
|
||||
|
||||
const goToImageToVideo = () => {
|
||||
router.push('/image-to-video')
|
||||
router.push('/image-to-video/create')
|
||||
}
|
||||
|
||||
const goToCreate = (work) => {
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
</div>
|
||||
<div class="package-price">$59/月</div>
|
||||
<div class="points-box">每月200积分</div>
|
||||
<button class="package-button subscribe">立即订阅</button>
|
||||
<button class="package-button subscribe" @click.stop="handleSubscribe('standard')">立即订阅</button>
|
||||
<div class="package-features">
|
||||
<div class="feature-item">
|
||||
<el-icon class="check-icon"><Check /></el-icon>
|
||||
@@ -154,7 +154,7 @@
|
||||
</div>
|
||||
<div class="package-price">$259/月</div>
|
||||
<div class="points-box">每月1000积分</div>
|
||||
<button class="package-button premium">立即订阅</button>
|
||||
<button class="package-button premium" @click.stop="handleSubscribe('premium')">立即订阅</button>
|
||||
<div class="package-features">
|
||||
<div class="feature-item">
|
||||
<el-icon class="check-icon"><Check /></el-icon>
|
||||
@@ -226,12 +226,25 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 支付模态框 -->
|
||||
<PaymentModal
|
||||
v-model="paymentModalVisible"
|
||||
:title="currentPaymentData.title"
|
||||
:amount="currentPaymentData.amount"
|
||||
:order-id="currentPaymentData.orderId"
|
||||
@pay-success="handlePaymentSuccess"
|
||||
@pay-error="handlePaymentError"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import MyWorks from '@/views/MyWorks.vue'
|
||||
import PaymentModal from '@/components/PaymentModal.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { createPayment, createAlipayPayment } from '@/api/payments'
|
||||
import {
|
||||
User,
|
||||
Document,
|
||||
@@ -257,19 +270,21 @@ const goToMyWorks = () => {
|
||||
}
|
||||
|
||||
const goToTextToVideo = () => {
|
||||
router.push('/text-to-video')
|
||||
router.push('/text-to-video/create')
|
||||
}
|
||||
|
||||
const goToImageToVideo = () => {
|
||||
router.push('/image-to-video')
|
||||
router.push('/image-to-video/create')
|
||||
}
|
||||
|
||||
const goToStoryboardVideo = () => {
|
||||
router.push('/storyboard-video')
|
||||
router.push('/storyboard-video/create')
|
||||
}
|
||||
|
||||
// 订单模态框相关
|
||||
const orderDialogVisible = ref(false)
|
||||
const paymentModalVisible = ref(false)
|
||||
const currentPaymentData = ref({})
|
||||
const orders = ref([
|
||||
{
|
||||
id: 'ORD-2024-001',
|
||||
@@ -327,6 +342,216 @@ const selectedPlan = ref('free')
|
||||
const selectPlan = (plan) => {
|
||||
selectedPlan.value = plan
|
||||
}
|
||||
|
||||
// 处理订阅
|
||||
const handleSubscribe = async (planType) => {
|
||||
console.log('handleSubscribe 被调用,planType:', planType)
|
||||
try {
|
||||
console.log('开始处理订阅...')
|
||||
const planInfo = getPlanInfo(planType)
|
||||
console.log('获取到的套餐信息:', planInfo)
|
||||
|
||||
// 设置支付数据
|
||||
currentPaymentData.value = {
|
||||
title: `${planInfo.name}会员`,
|
||||
amount: planInfo.price,
|
||||
orderId: `SUB_${planType}_${Date.now()}`,
|
||||
planType: planType,
|
||||
planInfo: planInfo
|
||||
}
|
||||
|
||||
console.log('支付数据设置完成:', currentPaymentData.value)
|
||||
|
||||
// 显示支付模态框
|
||||
paymentModalVisible.value = true
|
||||
console.log('支付模态框应该已显示')
|
||||
|
||||
// 直接生成二维码
|
||||
ElMessage.info('正在生成支付二维码...')
|
||||
await generateQRCode(planType, planInfo)
|
||||
|
||||
} catch (error) {
|
||||
console.error('订阅处理失败:', error)
|
||||
ElMessage.error('订阅处理失败,请重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 生成二维码
|
||||
const generateQRCode = async (planType, planInfo) => {
|
||||
try {
|
||||
console.log('=== 开始生成二维码 ===')
|
||||
|
||||
// 创建支付订单数据
|
||||
const paymentData = {
|
||||
orderId: `SUB_${planType}_${Date.now()}`,
|
||||
amount: planInfo.price.toString(),
|
||||
method: 'ALIPAY',
|
||||
description: `${planInfo.name}会员 - 支付宝支付`
|
||||
}
|
||||
|
||||
console.log('1. 创建支付订单,数据:', paymentData)
|
||||
const createResponse = await createPayment(paymentData)
|
||||
console.log('创建支付订单响应:', createResponse)
|
||||
|
||||
if (createResponse.data && createResponse.data.success) {
|
||||
const paymentId = createResponse.data.data.id
|
||||
console.log('2. 支付订单创建成功,ID:', paymentId)
|
||||
|
||||
console.log('3. 创建支付宝支付...')
|
||||
const alipayResponse = await createAlipayPayment({ paymentId })
|
||||
console.log('支付宝支付响应:', alipayResponse)
|
||||
|
||||
if (alipayResponse.data && alipayResponse.data.success) {
|
||||
const qrCode = alipayResponse.data.data.qrCode
|
||||
console.log('4. 支付宝二维码:', qrCode)
|
||||
|
||||
if (qrCode) {
|
||||
// 更新二维码显示
|
||||
const qrCodeElement = document.querySelector('#qr-code-img')
|
||||
if (qrCodeElement) {
|
||||
qrCodeElement.src = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(qrCode)}`
|
||||
qrCodeElement.style.display = 'block'
|
||||
console.log('5. 二维码图片已设置')
|
||||
}
|
||||
|
||||
// 隐藏模拟二维码
|
||||
const qrPlaceholder = document.querySelector('.qr-placeholder')
|
||||
if (qrPlaceholder) {
|
||||
qrPlaceholder.style.display = 'none'
|
||||
console.log('6. 模拟二维码已隐藏')
|
||||
}
|
||||
|
||||
ElMessage.success('二维码已生成,请使用支付宝扫码支付')
|
||||
console.log('=== 二维码生成完成 ===')
|
||||
} else {
|
||||
ElMessage.error('二维码生成失败:二维码为空')
|
||||
}
|
||||
} else {
|
||||
console.error('支付宝响应失败:', alipayResponse)
|
||||
ElMessage.error(alipayResponse.data?.message || '生成二维码失败')
|
||||
}
|
||||
} else {
|
||||
console.error('创建支付订单失败:', createResponse)
|
||||
ElMessage.error(createResponse.data?.message || '创建支付订单失败')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('=== 二维码生成出错 ===')
|
||||
console.error('错误详情:', error)
|
||||
ElMessage.error(`二维码生成失败:${error.message || '请重试'}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取套餐信息
|
||||
const getPlanInfo = (planType) => {
|
||||
const plans = {
|
||||
standard: {
|
||||
name: '标准版',
|
||||
price: 59,
|
||||
points: 200,
|
||||
description: '标准版订阅 - 每月200积分'
|
||||
},
|
||||
premium: {
|
||||
name: '专业版',
|
||||
price: 259,
|
||||
points: 1000,
|
||||
description: '专业版订阅 - 每月1000积分'
|
||||
}
|
||||
}
|
||||
return plans[planType]
|
||||
}
|
||||
|
||||
// 支付成功处理
|
||||
const handlePaymentSuccess = async (paymentData) => {
|
||||
try {
|
||||
ElMessage.success('支付成功!正在处理订单...')
|
||||
|
||||
// 这里可以添加支付成功后的处理逻辑
|
||||
// 比如更新用户状态、发送确认邮件等
|
||||
|
||||
// 关闭支付模态框
|
||||
paymentModalVisible.value = false
|
||||
|
||||
// 刷新页面或更新用户信息
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
}, 2000)
|
||||
|
||||
} catch (error) {
|
||||
console.error('支付成功处理失败:', error)
|
||||
ElMessage.error('支付成功但处理订单失败,请联系客服')
|
||||
}
|
||||
}
|
||||
|
||||
// 支付失败处理
|
||||
const handlePaymentError = (error) => {
|
||||
console.error('支付失败:', error)
|
||||
ElMessage.error('支付失败,请重试')
|
||||
}
|
||||
|
||||
// 创建订阅订单
|
||||
const createSubscriptionOrder = async (planType, planInfo) => {
|
||||
try {
|
||||
ElMessage.info('正在创建订单...')
|
||||
|
||||
// 生成订单号
|
||||
const orderId = `SUB_${planType}_${Date.now()}`
|
||||
|
||||
// 创建支付订单数据
|
||||
const paymentData = {
|
||||
orderId: orderId,
|
||||
amount: planInfo.price.toString(),
|
||||
method: 'ALIPAY',
|
||||
description: planInfo.description,
|
||||
orderType: 'SUBSCRIPTION',
|
||||
planType: planType
|
||||
}
|
||||
|
||||
// 先创建支付记录
|
||||
const createResponse = await createPayment(paymentData)
|
||||
|
||||
if (createResponse.data.success) {
|
||||
const paymentId = createResponse.data.data.id
|
||||
|
||||
// 然后创建支付宝支付
|
||||
const alipayResponse = await createAlipayPayment({ paymentId })
|
||||
|
||||
if (alipayResponse.data.success) {
|
||||
// 跳转到支付宝支付页面
|
||||
const paymentForm = alipayResponse.data.data
|
||||
// 创建临时div来显示支付表单
|
||||
const tempDiv = document.createElement('div')
|
||||
tempDiv.innerHTML = paymentForm
|
||||
document.body.appendChild(tempDiv)
|
||||
|
||||
// 自动提交表单
|
||||
const form = tempDiv.querySelector('form')
|
||||
if (form) {
|
||||
form.submit()
|
||||
} else {
|
||||
ElMessage.error('支付页面加载失败')
|
||||
}
|
||||
|
||||
// 清理临时元素
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(tempDiv)) {
|
||||
document.body.removeChild(tempDiv)
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
} else {
|
||||
ElMessage.error(alipayResponse.data.message || '创建支付宝支付失败')
|
||||
}
|
||||
|
||||
} else {
|
||||
ElMessage.error(createResponse.data.message || '创建支付订单失败')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建支付订单失败:', error)
|
||||
ElMessage.error('创建支付订单失败,请重试')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -507,7 +507,7 @@ const getAuthHeaders = () => {
|
||||
const refreshStats = async () => {
|
||||
loadingStats.value = true
|
||||
try {
|
||||
const response = await fetch('/api/cleanup/cleanup-stats', {
|
||||
const response = await fetch('http://localhost:8080/api/cleanup/cleanup-stats', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...getAuthHeaders()
|
||||
@@ -530,7 +530,7 @@ const refreshStats = async () => {
|
||||
const performFullCleanup = async () => {
|
||||
loadingCleanup.value = true
|
||||
try {
|
||||
const response = await fetch('/api/cleanup/full-cleanup', {
|
||||
const response = await fetch('http://localhost:8080/api/cleanup/full-cleanup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -201,11 +201,11 @@ const goToMyWorks = () => {
|
||||
}
|
||||
|
||||
const goToImageToVideo = () => {
|
||||
router.push('/image-to-video')
|
||||
router.push('/image-to-video/create')
|
||||
}
|
||||
|
||||
const goToStoryboardVideo = () => {
|
||||
router.push('/storyboard-video')
|
||||
router.push('/storyboard-video/create')
|
||||
}
|
||||
|
||||
const goToCreate = (work) => {
|
||||
|
||||
@@ -91,9 +91,7 @@ public class SecurityConfig {
|
||||
"http://localhost:3000",
|
||||
"http://127.0.0.1:3000",
|
||||
"http://localhost:5173",
|
||||
"http://127.0.0.1:5173",
|
||||
"https://*.ngrok.io",
|
||||
"https://*.ngrok-free.app"
|
||||
"http://127.0.0.1:5173"
|
||||
));
|
||||
configuration.setAllowedOriginPatterns(Arrays.asList(
|
||||
"https://*.ngrok.io",
|
||||
@@ -103,6 +101,7 @@ public class SecurityConfig {
|
||||
configuration.setAllowedHeaders(Arrays.asList("*"));
|
||||
configuration.setAllowCredentials(true);
|
||||
configuration.setExposedHeaders(Arrays.asList("Authorization"));
|
||||
configuration.setMaxAge(3600L); // 预检请求缓存时间
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.util.Locale;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
import com.example.demo.service.ApiResponseHandler;
|
||||
import com.example.demo.util.JwtUtils;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.example.demo.service.ApiResponseHandler;
|
||||
import com.example.demo.util.JwtUtils;
|
||||
|
||||
/**
|
||||
* API测试控制器
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.example.demo.model.User;
|
||||
import com.example.demo.service.UserService;
|
||||
import com.example.demo.service.VerificationCodeService;
|
||||
import com.example.demo.util.JwtUtils;
|
||||
import jakarta.validation.Valid;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@@ -114,11 +121,23 @@ public class AuthApiController {
|
||||
try {
|
||||
// 从邮箱生成用户名(去掉@符号及后面的部分)
|
||||
String username = email.split("@")[0];
|
||||
logger.info("邮箱验证码登录 - 原始邮箱: '{}', 生成的用户名: '{}'", email, username);
|
||||
|
||||
// 确保用户名长度不超过50个字符
|
||||
if (username.length() > 50) {
|
||||
username = username.substring(0, 50);
|
||||
logger.info("邮箱验证码登录 - 用户名过长,截断为: '{}'", username);
|
||||
}
|
||||
// 确保用户名唯一
|
||||
String originalUsername = username;
|
||||
int counter = 1;
|
||||
while (userService.findByUsername(username) != null) {
|
||||
username = originalUsername + counter;
|
||||
while (userService.findByUsernameOrNull(username) != null) {
|
||||
// 如果用户名过长,需要重新截断
|
||||
String newUsername = originalUsername + counter;
|
||||
if (newUsername.length() > 50) {
|
||||
newUsername = newUsername.substring(0, 50);
|
||||
}
|
||||
username = newUsername;
|
||||
counter++;
|
||||
}
|
||||
|
||||
@@ -145,6 +164,7 @@ public class AuthApiController {
|
||||
|
||||
// 生成JWT Token
|
||||
String token = jwtUtils.generateToken(user.getUsername(), user.getRole(), user.getId());
|
||||
logger.info("邮箱验证码登录 - 生成JWT token,用户名: '{}', 用户ID: {}", user.getUsername(), user.getId());
|
||||
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("success", true);
|
||||
|
||||
@@ -136,7 +136,7 @@ public class PaymentApiController {
|
||||
}
|
||||
|
||||
String orderId = (String) paymentData.get("orderId");
|
||||
String amountStr = (String) paymentData.get("amount");
|
||||
String amountStr = paymentData.get("amount") != null ? paymentData.get("amount").toString() : null;
|
||||
String method = (String) paymentData.get("method");
|
||||
|
||||
if (orderId == null || amountStr == null || method == null) {
|
||||
@@ -306,7 +306,7 @@ public class PaymentApiController {
|
||||
.body(createErrorResponse("请先登录后再创建支付记录"));
|
||||
}
|
||||
|
||||
String amountStr = (String) paymentData.get("amount");
|
||||
String amountStr = paymentData.get("amount") != null ? paymentData.get("amount").toString() : null;
|
||||
String method = (String) paymentData.get("method");
|
||||
|
||||
if (amountStr == null || method == null) {
|
||||
@@ -373,31 +373,42 @@ public class PaymentApiController {
|
||||
@RequestBody Map<String, Object> paymentData,
|
||||
Authentication authentication) {
|
||||
try {
|
||||
logger.info("收到创建支付宝支付请求,数据:{}", paymentData);
|
||||
|
||||
String username;
|
||||
if (authentication != null && authentication.isAuthenticated()) {
|
||||
username = authentication.getName();
|
||||
logger.info("用户已认证:{}", username);
|
||||
} else {
|
||||
logger.warn("用户未认证,拒绝支付请求");
|
||||
return ResponseEntity.badRequest()
|
||||
.body(createErrorResponse("请先登录后再创建支付"));
|
||||
}
|
||||
|
||||
Long paymentId = Long.valueOf(paymentData.get("paymentId").toString());
|
||||
logger.info("查找支付记录,ID:{}", paymentId);
|
||||
|
||||
Payment payment = paymentService.findById(paymentId)
|
||||
.orElseThrow(() -> new RuntimeException("支付记录不存在"));
|
||||
|
||||
// 检查权限
|
||||
if (!payment.getUser().getUsername().equals(username)) {
|
||||
logger.warn("用户{}无权限操作支付记录{}", username, paymentId);
|
||||
return ResponseEntity.status(403)
|
||||
.body(createErrorResponse("无权限操作此支付记录"));
|
||||
}
|
||||
|
||||
logger.info("开始调用支付宝服务创建支付,订单号:{},金额:{}", payment.getOrderId(), payment.getAmount());
|
||||
|
||||
// 调用支付宝接口创建支付
|
||||
String paymentUrl = alipayService.createPayment(payment);
|
||||
Map<String, Object> paymentResult = alipayService.createPayment(payment);
|
||||
|
||||
logger.info("支付宝二维码生成成功,订单号:{}", payment.getOrderId());
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", true);
|
||||
response.put("message", "支付宝支付创建成功");
|
||||
response.put("data", Map.of("paymentUrl", paymentUrl));
|
||||
response.put("message", "支付宝二维码生成成功");
|
||||
response.put("data", paymentResult);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
@@ -449,47 +460,7 @@ public class PaymentApiController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝异步通知
|
||||
*/
|
||||
@PostMapping("/alipay/notify")
|
||||
public ResponseEntity<String> handleAlipayNotify(@RequestParam Map<String, String> params) {
|
||||
try {
|
||||
logger.info("收到支付宝异步通知:{}", params);
|
||||
|
||||
boolean success = alipayService.handleNotify(params);
|
||||
|
||||
if (success) {
|
||||
return ResponseEntity.ok("success");
|
||||
} else {
|
||||
return ResponseEntity.ok("fail");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("处理支付宝异步通知失败:", e);
|
||||
return ResponseEntity.ok("fail");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝同步返回
|
||||
*/
|
||||
@GetMapping("/alipay/return")
|
||||
public ResponseEntity<String> handleAlipayReturn(@RequestParam Map<String, String> params) {
|
||||
try {
|
||||
logger.info("收到支付宝同步返回:{}", params);
|
||||
|
||||
boolean success = alipayService.handleReturn(params);
|
||||
|
||||
if (success) {
|
||||
return ResponseEntity.ok("支付成功,正在跳转...");
|
||||
} else {
|
||||
return ResponseEntity.ok("支付验证失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("处理支付宝同步返回失败:", e);
|
||||
return ResponseEntity.ok("支付处理异常");
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> createErrorResponse(String message) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
import com.example.demo.model.Payment;
|
||||
import com.example.demo.model.PaymentMethod;
|
||||
import com.example.demo.model.PaymentStatus;
|
||||
import com.example.demo.model.User;
|
||||
import com.example.demo.service.AlipayService;
|
||||
import com.example.demo.service.PayPalService;
|
||||
import com.example.demo.service.PaymentService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -16,10 +10,23 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.example.demo.model.Payment;
|
||||
import com.example.demo.model.PaymentMethod;
|
||||
import com.example.demo.model.User;
|
||||
import com.example.demo.service.AlipayService;
|
||||
import com.example.demo.service.PayPalService;
|
||||
import com.example.demo.service.PaymentService;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/payment")
|
||||
@@ -73,8 +80,12 @@ public class PaymentController {
|
||||
// 根据支付方式创建支付
|
||||
String redirectUrl;
|
||||
if (payment.getPaymentMethod() == PaymentMethod.ALIPAY) {
|
||||
redirectUrl = alipayService.createPayment(payment);
|
||||
return "redirect:" + redirectUrl;
|
||||
Map<String, Object> paymentResult = alipayService.createPayment(payment);
|
||||
if (paymentResult.containsKey("qrCode")) {
|
||||
// 对于二维码支付,重定向到支付页面显示二维码
|
||||
return "redirect:/payment/qr?qrCode=" + paymentResult.get("qrCode");
|
||||
}
|
||||
return "redirect:/payment/error";
|
||||
} else if (payment.getPaymentMethod() == PaymentMethod.PAYPAL) {
|
||||
redirectUrl = payPalService.createPayment(payment);
|
||||
return "redirect:" + redirectUrl;
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
import com.example.demo.model.TaskQueue;
|
||||
import com.example.demo.service.TaskQueueService;
|
||||
import com.example.demo.util.JwtUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.example.demo.model.TaskQueue;
|
||||
import com.example.demo.service.TaskQueueService;
|
||||
import com.example.demo.util.JwtUtils;
|
||||
|
||||
/**
|
||||
* 任务队列API控制器
|
||||
*/
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
import com.example.demo.model.TextToVideoTask;
|
||||
import com.example.demo.service.TextToVideoService;
|
||||
import com.example.demo.util.JwtUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.example.demo.model.TextToVideoTask;
|
||||
import com.example.demo.service.TextToVideoService;
|
||||
import com.example.demo.util.JwtUtils;
|
||||
|
||||
/**
|
||||
* 文生视频API控制器
|
||||
*/
|
||||
|
||||
@@ -195,3 +195,5 @@ public class PointsFreezeRecord {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -263,3 +263,5 @@ public class TaskQueue {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -255,3 +255,5 @@ public class TaskStatus {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package com.example.demo.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
@@ -31,9 +32,7 @@ public class User {
|
||||
@Column(nullable = false, unique = true, length = 100)
|
||||
private String email;
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 6, max = 100)
|
||||
@Column(nullable = false)
|
||||
@Column(nullable = true)
|
||||
private String passwordHash;
|
||||
|
||||
@NotBlank
|
||||
|
||||
@@ -63,3 +63,5 @@ public interface TaskStatusRepository extends JpaRepository<TaskStatus, Long> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.example.demo.repository;
|
||||
|
||||
import com.example.demo.model.UserWork;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
@@ -9,9 +12,7 @@ import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import com.example.demo.model.UserWork;
|
||||
|
||||
/**
|
||||
* 用户作品仓库接口
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.example.demo.scheduler;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -8,7 +10,6 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import com.example.demo.service.TaskCleanupService;
|
||||
import com.example.demo.service.TaskQueueService;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 任务队列定时调度器
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
package com.example.demo.security;
|
||||
|
||||
import com.example.demo.model.User;
|
||||
import com.example.demo.service.UserService;
|
||||
import com.example.demo.util.JwtUtils;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.lang.NonNull;
|
||||
import com.example.demo.model.User;
|
||||
import com.example.demo.service.UserService;
|
||||
import com.example.demo.util.JwtUtils;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
@@ -41,7 +41,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
logger.debug("JWT过滤器处理请求: {}", request.getRequestURI());
|
||||
logger.info("JWT过滤器处理请求: {}", request.getRequestURI());
|
||||
|
||||
try {
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
@@ -50,13 +50,14 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
String token = jwtUtils.extractTokenFromHeader(authHeader);
|
||||
logger.debug("提取的token: {}", token != null ? "存在" : "不存在");
|
||||
|
||||
if (token != null) {
|
||||
if (token != null && !token.equals("null") && !token.trim().isEmpty()) {
|
||||
String username = jwtUtils.getUsernameFromToken(token);
|
||||
logger.debug("从token获取用户名: {}", username);
|
||||
|
||||
if (username != null && jwtUtils.validateToken(token, username)) {
|
||||
logger.info("JWT认证 - 从token获取的用户名: '{}'", username);
|
||||
User user = userService.findByUsername(username);
|
||||
logger.debug("查找用户: {}", user != null ? user.getUsername() : "未找到");
|
||||
logger.info("JWT认证 - 数据库查找结果: {}", user != null ? "找到用户 '" + user.getUsername() + "'" : "未找到用户");
|
||||
|
||||
if (user != null) {
|
||||
// 创建用户权限列表
|
||||
@@ -64,7 +65,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
authorities.add(new SimpleGrantedAuthority(user.getRole()));
|
||||
|
||||
UsernamePasswordAuthenticationToken authToken =
|
||||
new UsernamePasswordAuthenticationToken(user, null, authorities);
|
||||
new UsernamePasswordAuthenticationToken(user.getUsername(), null, authorities);
|
||||
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||
logger.debug("JWT认证成功,用户: {}, 角色: {}", username, user.getRole());
|
||||
|
||||
@@ -34,3 +34,5 @@ public class PlainTextPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.AlipayClient;
|
||||
import com.alipay.api.DefaultAlipayClient;
|
||||
import com.alipay.api.domain.AlipayTradePagePayModel;
|
||||
import com.alipay.api.internal.util.AlipaySignature;
|
||||
import com.alipay.api.request.AlipayTradePagePayRequest;
|
||||
import com.alipay.api.response.AlipayTradePagePayResponse;
|
||||
import com.example.demo.model.Payment;
|
||||
import com.example.demo.model.PaymentMethod;
|
||||
import com.example.demo.model.PaymentStatus;
|
||||
import com.example.demo.repository.PaymentRepository;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import com.alipay.api.AlipayClient;
|
||||
import com.alipay.api.DefaultAlipayClient;
|
||||
import com.alipay.api.domain.AlipayTradePrecreateModel;
|
||||
import com.alipay.api.internal.util.AlipaySignature;
|
||||
import com.alipay.api.request.AlipayTradePrecreateRequest;
|
||||
import com.alipay.api.response.AlipayTradePrecreateResponse;
|
||||
import com.example.demo.model.Payment;
|
||||
import com.example.demo.model.PaymentMethod;
|
||||
import com.example.demo.model.PaymentStatus;
|
||||
import com.example.demo.repository.PaymentRepository;
|
||||
|
||||
@Service
|
||||
public class AlipayService {
|
||||
@@ -56,60 +57,117 @@ public class AlipayService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建支付宝支付订单
|
||||
* 创建支付宝支付订单并生成二维码
|
||||
*/
|
||||
public String createPayment(Payment payment) {
|
||||
public Map<String, Object> createPayment(Payment payment) {
|
||||
try {
|
||||
logger.info("开始创建支付宝支付订单,订单号:{},金额:{}", payment.getOrderId(), payment.getAmount());
|
||||
|
||||
// 设置支付状态
|
||||
payment.setStatus(PaymentStatus.PENDING);
|
||||
payment.setPaymentMethod(PaymentMethod.ALIPAY);
|
||||
if (payment.getOrderId() == null || payment.getOrderId().isEmpty()) {
|
||||
payment.setOrderId(generateOrderId());
|
||||
}
|
||||
payment.setCallbackUrl(notifyUrl);
|
||||
payment.setReturnUrl(returnUrl);
|
||||
|
||||
// 保存支付记录
|
||||
paymentRepository.save(payment);
|
||||
logger.info("支付记录已保存,ID:{}", payment.getId());
|
||||
|
||||
// 创建支付宝客户端
|
||||
AlipayClient alipayClient = new DefaultAlipayClient(
|
||||
gatewayUrl, appId, privateKey, "json", charset, publicKey, signType);
|
||||
// 调用真实的支付宝API
|
||||
return callRealAlipayAPI(payment);
|
||||
|
||||
// 设置请求参数
|
||||
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
|
||||
request.setReturnUrl(returnUrl);
|
||||
request.setNotifyUrl(notifyUrl);
|
||||
|
||||
// 设置业务参数
|
||||
AlipayTradePagePayModel model = new AlipayTradePagePayModel();
|
||||
model.setOutTradeNo(payment.getOrderId());
|
||||
model.setTotalAmount(payment.getAmount().toString());
|
||||
model.setSubject(payment.getDescription() != null ? payment.getDescription() : "商品支付");
|
||||
model.setBody(payment.getDescription());
|
||||
model.setProductCode("FAST_INSTANT_TRADE_PAY");
|
||||
|
||||
request.setBizModel(model);
|
||||
|
||||
// 调用API
|
||||
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
|
||||
|
||||
if (response.isSuccess()) {
|
||||
logger.info("支付宝支付订单创建成功,订单号:{}", payment.getOrderId());
|
||||
return response.getBody();
|
||||
} else {
|
||||
logger.error("支付宝支付订单创建失败:{}", response.getMsg());
|
||||
payment.setStatus(PaymentStatus.FAILED);
|
||||
paymentRepository.save(payment);
|
||||
throw new RuntimeException("支付订单创建失败:" + response.getMsg());
|
||||
}
|
||||
|
||||
} catch (AlipayApiException e) {
|
||||
logger.error("支付宝API调用异常:", e);
|
||||
} catch (Exception e) {
|
||||
logger.error("创建支付订单时发生异常,订单号:{},错误:{}", payment.getOrderId(), e.getMessage(), e);
|
||||
payment.setStatus(PaymentStatus.FAILED);
|
||||
paymentRepository.save(payment);
|
||||
throw new RuntimeException("支付服务异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用真实的支付宝API
|
||||
*/
|
||||
private Map<String, Object> callRealAlipayAPI(Payment payment) throws Exception {
|
||||
// 创建支付宝客户端,增加超时时间
|
||||
AlipayClient alipayClient = new DefaultAlipayClient(
|
||||
gatewayUrl, appId, privateKey, "json", charset, publicKey, signType);
|
||||
|
||||
// 设置连接和读取超时时间(30秒)
|
||||
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");
|
||||
System.setProperty("sun.net.client.defaultReadTimeout", "30000");
|
||||
|
||||
// 使用预创建API生成二维码
|
||||
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
|
||||
request.setNotifyUrl(notifyUrl);
|
||||
|
||||
// 设置业务参数
|
||||
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
|
||||
model.setOutTradeNo(payment.getOrderId());
|
||||
model.setTotalAmount(payment.getAmount().toString());
|
||||
model.setSubject(payment.getDescription() != null ? payment.getDescription() : "AIGC会员订阅");
|
||||
model.setBody(payment.getDescription() != null ? payment.getDescription() : "AIGC平台会员订阅服务");
|
||||
|
||||
request.setBizModel(model);
|
||||
|
||||
logger.info("调用支付宝预创建API,订单号:{},金额:{},商品名称:{}",
|
||||
model.getOutTradeNo(), model.getTotalAmount(), model.getSubject());
|
||||
|
||||
// 调用API,增加重试机制
|
||||
AlipayTradePrecreateResponse response = null;
|
||||
int maxRetries = 3;
|
||||
int retryCount = 0;
|
||||
|
||||
while (retryCount < maxRetries) {
|
||||
try {
|
||||
logger.info("正在调用支付宝API... (第{}次尝试)", retryCount + 1);
|
||||
response = alipayClient.execute(request);
|
||||
break; // 成功则跳出循环
|
||||
} catch (Exception e) {
|
||||
retryCount++;
|
||||
logger.warn("支付宝API调用失败,第{}次重试,错误:{}", retryCount, e.getMessage());
|
||||
if (retryCount >= maxRetries) {
|
||||
logger.error("支付宝API调用失败,已达到最大重试次数");
|
||||
throw new RuntimeException("支付宝API调用失败:" + e.getMessage());
|
||||
}
|
||||
try {
|
||||
Thread.sleep(2000); // 等待2秒后重试
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("重试被中断", ie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("支付宝API响应状态:{}", response.isSuccess());
|
||||
logger.info("支付宝API响应码:{}", response.getCode());
|
||||
logger.info("支付宝API响应消息:{}", response.getMsg());
|
||||
logger.info("支付宝API响应子码:{}", response.getSubCode());
|
||||
logger.info("支付宝API响应子消息:{}", response.getSubMsg());
|
||||
|
||||
if (response.isSuccess()) {
|
||||
String qrCode = response.getQrCode();
|
||||
logger.info("支付宝二维码生成成功,订单号:{},二维码:{}", payment.getOrderId(), qrCode);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("qrCode", qrCode);
|
||||
result.put("outTradeNo", response.getOutTradeNo());
|
||||
result.put("success", true);
|
||||
|
||||
return result;
|
||||
} else {
|
||||
logger.error("支付宝二维码生成失败,订单号:{},错误信息:{},子码:{},子消息:{}",
|
||||
payment.getOrderId(), response.getMsg(), response.getSubCode(), response.getSubMsg());
|
||||
payment.setStatus(PaymentStatus.FAILED);
|
||||
paymentRepository.save(payment);
|
||||
throw new RuntimeException("二维码生成失败:" + response.getMsg() + " - " + response.getSubMsg());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 处理支付宝异步通知
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.example.demo.model.ImageToVideoTask;
|
||||
import com.example.demo.repository.ImageToVideoTaskRepository;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -14,15 +22,8 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import com.example.demo.model.ImageToVideoTask;
|
||||
import com.example.demo.repository.ImageToVideoTaskRepository;
|
||||
|
||||
/**
|
||||
* 图生视频服务类
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.example.demo.model.*;
|
||||
import com.example.demo.repository.PaymentRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
@@ -15,6 +7,22 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.example.demo.model.Order;
|
||||
import com.example.demo.model.OrderItem;
|
||||
import com.example.demo.model.OrderStatus;
|
||||
import com.example.demo.model.OrderType;
|
||||
import com.example.demo.model.Payment;
|
||||
import com.example.demo.model.PaymentMethod;
|
||||
import com.example.demo.model.PaymentStatus;
|
||||
import com.example.demo.model.User;
|
||||
import com.example.demo.repository.PaymentRepository;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class PaymentService {
|
||||
@@ -291,11 +299,15 @@ public class PaymentService {
|
||||
*/
|
||||
public Payment createPayment(String username, String orderId, String amountStr, String method) {
|
||||
try {
|
||||
logger.info("创建支付 - 用户名: {}, 订单ID: {}, 金额: {}, 支付方式: {}", username, orderId, amountStr, method);
|
||||
logger.info("创建支付 - 用户名: '{}', 订单ID: {}, 金额: {}, 支付方式: {}", username, orderId, amountStr, method);
|
||||
|
||||
User user = userService.findByUsername(username);
|
||||
if (user == null) {
|
||||
logger.error("用户不存在: {}", username);
|
||||
User user;
|
||||
try {
|
||||
logger.info("PaymentService - 尝试查找用户: '{}'", username);
|
||||
user = userService.findByUsername(username);
|
||||
logger.info("PaymentService - 用户查找结果: {}", user != null ? "找到用户 '" + user.getUsername() + "'" : "未找到用户");
|
||||
} catch (Exception e) {
|
||||
logger.error("PaymentService - 用户查找异常: {}", username, e);
|
||||
// 如果是匿名用户,创建一个临时用户记录
|
||||
if (username.startsWith("anonymous_")) {
|
||||
user = createAnonymousUser(username);
|
||||
@@ -315,6 +327,7 @@ public class PaymentService {
|
||||
payment.setUser(user);
|
||||
payment.setOrderId(orderId);
|
||||
payment.setAmount(amount);
|
||||
payment.setCurrency("CNY"); // 设置默认货币为人民币
|
||||
payment.setPaymentMethod(paymentMethod);
|
||||
payment.setStatus(PaymentStatus.PENDING);
|
||||
payment.setCreatedAt(LocalDateTime.now());
|
||||
@@ -325,10 +338,12 @@ public class PaymentService {
|
||||
// 根据支付方式调用相应的支付服务
|
||||
if (paymentMethod == PaymentMethod.ALIPAY) {
|
||||
try {
|
||||
String paymentUrl = alipayService.createPayment(savedPayment);
|
||||
savedPayment.setPaymentUrl(paymentUrl);
|
||||
Map<String, Object> paymentResult = alipayService.createPayment(savedPayment);
|
||||
if (paymentResult.containsKey("qrCode")) {
|
||||
savedPayment.setPaymentUrl(paymentResult.get("qrCode").toString());
|
||||
}
|
||||
save(savedPayment);
|
||||
logger.info("支付宝支付URL生成成功: {}", paymentUrl);
|
||||
logger.info("支付宝二维码生成成功: {}", paymentResult.get("qrCode"));
|
||||
} catch (Exception e) {
|
||||
logger.error("调用支付宝支付接口失败:", e);
|
||||
// 不抛出异常,让前端处理
|
||||
|
||||
@@ -86,9 +86,9 @@ public class TaskStatusPollingService {
|
||||
|
||||
try {
|
||||
// 调用外部API查询状态
|
||||
HttpResponse<String> response = Unirest.post(apiBaseUrl + "/v1/videos")
|
||||
HttpResponse<String> response = Unirest.get(apiBaseUrl + "/v1/videos")
|
||||
.header("Authorization", "Bearer " + apiKey)
|
||||
.field("task_id", task.getExternalTaskId())
|
||||
.queryString("task_id", task.getExternalTaskId())
|
||||
.asString();
|
||||
|
||||
if (response.getStatus() == 200) {
|
||||
@@ -113,19 +113,41 @@ public class TaskStatusPollingService {
|
||||
*/
|
||||
private void updateTaskStatus(TaskStatus task, JsonNode responseJson) {
|
||||
try {
|
||||
// 检查base_resp状态
|
||||
JsonNode baseResp = responseJson.path("base_resp");
|
||||
if (!baseResp.isMissingNode() && baseResp.path("status_code").asInt() != 0) {
|
||||
String errorMsg = baseResp.path("status_msg").asText("Unknown error");
|
||||
task.markAsFailed(errorMsg);
|
||||
logger.warn("API返回错误: taskId={}, error={}", task.getTaskId(), errorMsg);
|
||||
taskStatusRepository.save(task);
|
||||
return;
|
||||
}
|
||||
|
||||
String status = responseJson.path("status").asText();
|
||||
int progress = responseJson.path("progress").asInt(0);
|
||||
String resultUrl = responseJson.path("result_url").asText();
|
||||
String resultUrl = null;
|
||||
String errorMessage = responseJson.path("error_message").asText();
|
||||
|
||||
task.incrementPollCount();
|
||||
task.setProgress(progress);
|
||||
|
||||
switch (status.toLowerCase()) {
|
||||
case "completed":
|
||||
case "success":
|
||||
// 获取file_id并获取视频URL
|
||||
String fileId = responseJson.path("file_id").asText();
|
||||
if (!fileId.isEmpty()) {
|
||||
resultUrl = getVideoUrlFromFileId(fileId);
|
||||
if (resultUrl != null) {
|
||||
task.markAsCompleted(resultUrl);
|
||||
logger.info("任务完成: taskId={}, resultUrl={}", task.getTaskId(), resultUrl);
|
||||
} else {
|
||||
task.markAsFailed("无法获取视频URL");
|
||||
logger.warn("任务完成但无法获取视频URL: taskId={}, fileId={}", task.getTaskId(), fileId);
|
||||
}
|
||||
} else {
|
||||
task.markAsFailed("任务完成但未返回文件ID");
|
||||
logger.warn("任务完成但未返回文件ID: taskId={}", task.getTaskId());
|
||||
}
|
||||
break;
|
||||
|
||||
case "failed":
|
||||
@@ -136,6 +158,7 @@ public class TaskStatusPollingService {
|
||||
|
||||
case "processing":
|
||||
case "in_progress":
|
||||
case "pending":
|
||||
task.setStatus(TaskStatus.Status.PROCESSING);
|
||||
logger.info("任务处理中: taskId={}, progress={}%", task.getTaskId(), progress);
|
||||
break;
|
||||
@@ -152,6 +175,38 @@ public class TaskStatusPollingService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据file_id获取视频URL
|
||||
*/
|
||||
private String getVideoUrlFromFileId(String fileId) {
|
||||
try {
|
||||
HttpResponse<String> response = Unirest.get(apiBaseUrl + "/minimax/v1/files/retrieve")
|
||||
.header("Authorization", "Bearer " + apiKey)
|
||||
.queryString("file_id", fileId)
|
||||
.asString();
|
||||
|
||||
if (response.getStatus() == 200) {
|
||||
JsonNode responseJson = objectMapper.readTree(response.getBody());
|
||||
JsonNode fileNode = responseJson.path("file");
|
||||
if (!fileNode.isMissingNode()) {
|
||||
String downloadUrl = fileNode.path("download_url").asText();
|
||||
if (!downloadUrl.isEmpty()) {
|
||||
logger.info("成功获取视频URL: fileId={}, url={}", fileId, downloadUrl);
|
||||
return downloadUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn("获取视频URL失败: fileId={}, status={}, response={}",
|
||||
fileId, response.getStatus(), response.getBody());
|
||||
return null;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("获取视频URL时发生错误: fileId={}, error={}", fileId, e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理超时任务
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.example.demo.model.TextToVideoTask;
|
||||
import com.example.demo.repository.TextToVideoTaskRepository;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -13,11 +17,8 @@ import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import com.example.demo.model.TextToVideoTask;
|
||||
import com.example.demo.repository.TextToVideoTaskRepository;
|
||||
|
||||
/**
|
||||
* 文生视频服务类
|
||||
|
||||
@@ -58,6 +58,11 @@ public class UserService {
|
||||
return userRepository.findByUsername(username).orElseThrow(() -> new IllegalArgumentException("用户不存在"));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public User findByUsernameOrNull(String username) {
|
||||
return userRepository.findByUsername(username).orElse(null);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public User create(String username, String email, String rawPassword) {
|
||||
return register(username, email, rawPassword);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.example.demo.model.User;
|
||||
import com.example.demo.model.UserWork;
|
||||
import com.example.demo.model.TextToVideoTask;
|
||||
import com.example.demo.model.ImageToVideoTask;
|
||||
import com.example.demo.repository.UserWorkRepository;
|
||||
import com.example.demo.repository.TextToVideoTaskRepository;
|
||||
import com.example.demo.repository.ImageToVideoTaskRepository;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -16,10 +14,13 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.example.demo.model.ImageToVideoTask;
|
||||
import com.example.demo.model.TextToVideoTask;
|
||||
import com.example.demo.model.User;
|
||||
import com.example.demo.model.UserWork;
|
||||
import com.example.demo.repository.ImageToVideoTaskRepository;
|
||||
import com.example.demo.repository.TextToVideoTaskRepository;
|
||||
import com.example.demo.repository.UserWorkRepository;
|
||||
|
||||
/**
|
||||
* 用户作品服务类
|
||||
@@ -314,13 +315,33 @@ public class UserWorkService {
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public Map<String, Object> getUserWorkStats(String username) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
Object[] stats = userWorkRepository.getUserWorkStats(username);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
// 安全地处理查询结果
|
||||
if (stats != null && stats.length >= 4) {
|
||||
result.put("completedCount", stats[0] != null ? stats[0] : 0);
|
||||
result.put("processingCount", stats[1] != null ? stats[1] : 0);
|
||||
result.put("failedCount", stats[2] != null ? stats[2] : 0);
|
||||
result.put("totalPointsCost", stats[3] != null ? stats[3] : 0);
|
||||
} else {
|
||||
// 如果查询结果格式不正确,使用默认值
|
||||
result.put("completedCount", 0);
|
||||
result.put("processingCount", 0);
|
||||
result.put("failedCount", 0);
|
||||
result.put("totalPointsCost", 0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 如果查询失败,使用默认值
|
||||
logger.warn("获取用户作品统计失败,使用默认值: " + e.getMessage());
|
||||
result.put("completedCount", 0);
|
||||
result.put("processingCount", 0);
|
||||
result.put("failedCount", 0);
|
||||
result.put("totalPointsCost", 0);
|
||||
}
|
||||
|
||||
result.put("totalCount", userWorkRepository.countByUsername(username));
|
||||
result.put("publicCount", userWorkRepository.countPublicWorksByUsername(username));
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package com.example.demo.util;
|
||||
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
|
||||
@Component
|
||||
public class JwtUtils {
|
||||
|
||||
@@ -120,17 +124,7 @@ public class JwtUtils {
|
||||
* 获取签名密钥
|
||||
*/
|
||||
private SecretKey getSigningKey() {
|
||||
// 确保密钥长度至少为256位(32字节)
|
||||
byte[] keyBytes = secret.getBytes();
|
||||
if (keyBytes.length < 32) {
|
||||
// 如果密钥太短,使用SHA-256哈希扩展
|
||||
try {
|
||||
java.security.MessageDigest sha = java.security.MessageDigest.getInstance("SHA-256");
|
||||
keyBytes = sha.digest(keyBytes);
|
||||
} catch (java.security.NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("SHA-256 algorithm not available", e);
|
||||
}
|
||||
}
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +1,40 @@
|
||||
# Server Configuration
|
||||
server.port=8080
|
||||
|
||||
# MySQL DataSource (DEV) - 使用环境变量
|
||||
spring.datasource.url=${DB_URL:jdbc:mysql://localhost:3306/aigc?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true}
|
||||
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
|
||||
spring.datasource.username=${DB_USERNAME:root}
|
||||
spring.datasource.password=${DB_PASSWORD:177615}
|
||||
|
||||
# 数据库连接池配置
|
||||
spring.datasource.hikari.maximum-pool-size=20
|
||||
spring.datasource.hikari.minimum-idle=5
|
||||
spring.datasource.hikari.idle-timeout=300000
|
||||
spring.datasource.hikari.max-lifetime=1200000
|
||||
spring.datasource.hikari.connection-timeout=20000
|
||||
spring.datasource.hikari.leak-detection-threshold=60000
|
||||
# 数据库配置
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/aigc_platform?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=177615
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
|
||||
# JPA配置
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
|
||||
# 初始化脚本仅在开发环境开启(与JPA DDL冲突,暂时禁用)
|
||||
# spring.sql.init.mode=always
|
||||
# spring.sql.init.platform=mysql
|
||||
# 服务器配置
|
||||
server.port=8080
|
||||
|
||||
# 日志配置
|
||||
logging.level.com.example.demo=DEBUG
|
||||
logging.level.org.springframework.security=DEBUG
|
||||
|
||||
# JWT配置
|
||||
jwt.secret=mySecretKey123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
jwt.expiration=86400000
|
||||
|
||||
# 腾讯云SES配置
|
||||
tencent.ses.secret-id=AKIDz8krbsJ5yKBZQpn74WFkmLPx3gnPhESA
|
||||
tencent.ses.secret-key=Gu5t9xGARNpqDXcd3I4WZkFpAALPVZ7pKbNScfFsj
|
||||
tencent.ses.region=ap-beijing
|
||||
tencent.ses.from-email=noreply@vionow.com
|
||||
tencent.ses.from-name=AIGC平台
|
||||
|
||||
# 支付宝配置 (开发环境 - 沙箱测试)
|
||||
# 请替换为您的实际配置
|
||||
alipay.app-id=您的APPID
|
||||
alipay.private-key=您的应用私钥
|
||||
alipay.public-key=支付宝公钥
|
||||
alipay.app-id=9021000157616562
|
||||
alipay.private-key=MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCH7wPeptkJlJuoKwDqxvfJJLTOAWVkHa/TLh+wiy1tEtmwcrOwEU3GuqfkUlhij71WJIZi8KBytCwbax1QGZA/oLXvTCGJJrYrsEL624X5gGCCPKWwHRDhewsQ5W8jFxaaMXxth8GKlSW61PZD2cOQClRVEm2xnWFZ+6/7WBI7082g7ayzGCD2eowXsJyWyuEBCUSbHXkSgxVhqj5wUGIXhr8ly+pdUlJmDX5K8UG2rjJYx+0AU5UZJbOAND7d3iyDsOulHDvth50t8MOWDnDCVJ2aAgUB5FZKtOFxOmzNTsMjvzYldFztF0khbypeeMVL2cxgioIgTvjBkUwd55hZAgMBAAECggEAUjk3pARUoEDt7skkYt87nsW/QCUECY0Tf7AUpxtovON8Hgkju8qbuyvIxokwwV2k72hkiZB33Soyy9r8/iiYYoR5yGfKmUV7R+30df03ivYmamD48BCE138v8GZ31Ufv+hEY7MADSCpzihGrbNtaOdSlslfVVmyWKHHfvy9EyD6yHJGYswLpHXC/QX1TuLRRxk6Uup8qENOG/6zjGWMfxoRZFwTt80ml1mKy32YZGyJqDaQpJcdYwAHOPcnJl1emw4E+oVjiLyksl643npuTkgnZXs1iWcWSS8ojF1w/0kVDzcNh9toLg+HDuQlIHOis01VQ7lYcG4oiMOnhX1QHIQKBgQC9fgBuILjBhuCI9fHvLRdzoNC9heD54YK7xGvEV/mv90k8xcmNx+Yg5C57ASaMRtOq3b7muPiCv5wOtMT4tUCcMIwSrTNlcBM6EoTagnaGfpzOMaHGMXO4vbaw+MIynHnvXFj1rZjG1lzkV/9K36LAaHD9ZKVJaBQ9mK+0CIq/3QKBgQC3pL5GbvXj6/4ahTraXzNDQQpPGVgbHxcOioEXL4ibaOPC58puTW8HDbRvVuhl/4EEOBRVX81BSgkN8XHwTSiZdih2iOqByg+o9kixs7nlFn3Iw9BBP2/g+Wqiyi2N+9g17kfWXXVOKYz/eMXLBeOo4KhQE9wqNGyZldYzX2ywrQKBgApJmvBfqmgnUG1fHOFlS06lvm9ro0ktqxFSmp8wP4gEHt/DxSuDXMUQXk2jRFp9ReSS4VhZVnSSvoA15DO0c2uHXzNsX8v0B7cxZjEOwCyRFyZCn4vJB4VSF2cIOlLRF/Wcx9+eqxqwbJ6hAGUqOwXDJc879ZVEp0So03EsvYupAoGAAnI+Wp/VxLB7FQ1bSFdmTmoKYh1bUBks7HOp3o4yiqduCUWfK7L6XKSxF56Xv+wUYuMAWlbJXCpJTpc9xk6w0MKDLXkLbqkrZjvJohxbyJJxIICDQKtAqUWJRxvcWXzWV3mSGWfrTRw+lZSdReQRMUm01EQ/dYx3OeCGFu8Zeo0CgYAlH5YSYdJxZSoDCJeoTrkxUlFoOg8UQ7SrsaLYLwpwcwpuiWJaTrg6jwFocj+XhjQ9RtRbSBHz2wKSLdl+pXbTbqECKk85zMFl6zG3etXtTJU/dD750Ty4i8zt3+JGhvglPrQBY1CfItgml2oXa/VUVMnLCUS0WSZuPRmPYZD8dg==
|
||||
alipay.public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAksEwzuR3ASrKtTzaANqdQYKOoD44itA1TWG/6onvQr8PHNEMgcguLuJNrdeuT2PDg23byzZ9qKfEM2D5U4zbpt0/uCYLfZQyAnAWWyMvnKPoSIgrtBjnxYK6HE6fuQV3geJTcZxvP/z8dGZB0V0s6a53rzbKSLh0p4w0hWfVXlQihq3Xh4vSKB+ojdhEkIblhpWPT42NPbjVNdwPzIhUGpRy3/nsgNqVBu+ZacQ5/rCvzXU1RE0allBbjcvjymKQTS7bAE0i1Mgo1eX8njvElsfQUv5P7xQdrvZagqtIuTdP19cmsSNGdIC9Z5Po3j0z3KWPR7MrKgDuJfzkWtJR4wIDAQAB
|
||||
alipay.gateway-url=https://openapi.alipaydev.com/gateway.do
|
||||
alipay.charset=UTF-8
|
||||
alipay.sign-type=RSA2
|
||||
alipay.notify-url=http://您的域名:8080/api/payments/alipay/notify
|
||||
alipay.return-url=http://您的域名:8080/api/payments/alipay/return
|
||||
|
||||
# PayPal配置 (开发环境 - 沙箱模式)
|
||||
paypal.client-id=your_paypal_sandbox_client_id
|
||||
paypal.client-secret=your_paypal_sandbox_client_secret
|
||||
paypal.mode=sandbox
|
||||
paypal.return-url=http://localhost:8080/api/payments/paypal/return
|
||||
paypal.cancel-url=http://localhost:8080/api/payments/paypal/cancel
|
||||
|
||||
# JWT配置 - 使用环境变量
|
||||
jwt.secret=${JWT_SECRET:aigc-demo-secret-key-for-jwt-token-generation-very-long-secret-key}
|
||||
jwt.expiration=${JWT_EXPIRATION:604800000}
|
||||
|
||||
# AI API配置
|
||||
ai.api.base-url=http://116.62.4.26:8081
|
||||
ai.api.key=ak_5f13ec469e6047d5b8155c3cc91350e2
|
||||
|
||||
# 任务清理配置
|
||||
task.cleanup.retention-days=30
|
||||
task.cleanup.archive-retention-days=365
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
alipay.notify-url=https://curtly-aphorismatic-ginger.ngrok-free.dev/api/payments/alipay/notify
|
||||
alipay.return-url=https://curtly-aphorismatic-ginger.ngrok-free.dev/api/payments/alipay/return
|
||||
@@ -22,3 +22,5 @@ CREATE TABLE IF NOT EXISTS task_queue (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='任务队列表';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -21,3 +21,5 @@ CREATE TABLE IF NOT EXISTS points_freeze_records (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='积分冻结记录表';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -24,3 +24,5 @@ CREATE TABLE task_status (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务状态表';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -570,3 +570,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -486,3 +486,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -525,3 +525,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package com.example.demo.test;
|
||||
|
||||
import com.example.demo.model.TaskQueue;
|
||||
import com.example.demo.service.TaskQueueService;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import java.util.List;
|
||||
import com.example.demo.model.TaskQueue;
|
||||
import com.example.demo.service.TaskQueueService;
|
||||
|
||||
/**
|
||||
* 任务队列功能测试
|
||||
|
||||
@@ -25,3 +25,5 @@ echo 按任意键退出...
|
||||
pause > nul
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -52,3 +52,5 @@ public class TestApiConnection {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
15
demo/test_api.py
Normal file
15
demo/test_api.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import requests
|
||||
|
||||
# 测试作品列表API
|
||||
url = "http://localhost:8080/api/works/my-works"
|
||||
headers = {
|
||||
"Authorization": "Bearer eyJhbGciOiJIUzUxMiJ9.eyJyb2xlIjoiUk9MRV9VU0VSIiwidXNlcklkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwic3ViIjoiYWRtaW4iLCJpYXQiOjE3NjE2MzYyNDQsImV4cCI6MTc2MTcyMjY0NH0.qZxHDkgSoSRvmMHBFfRdzZYtC55eCKba3VN07lTsFzKXn1hYbupv7boBJDKNOUrRYaH5ougHLFTI5xm059434g"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers, params={"page": 0, "size": 10})
|
||||
print(f"Status Code: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
23
demo/test_jwt.py
Normal file
23
demo/test_jwt.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
# 测试JWT解析
|
||||
token = "eyJhbGciOiJIUzUxMiJ9.eyJyb2xlIjoiUk9MRV9VU0VSIiwidXNlcklkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwic3ViIjoiYWRtaW4iLCJpYXQiOjE3NjE2MzYyNDQsImV4cCI6MTc2MTcyMjY0NH0.qZxHDkgSoSRvmMHBFfRdzZYtC55eCKba3VN07lTsFzKXn1hYbupv7boBJDKNOUrRYaH5ougHLFTI5xm059434g"
|
||||
|
||||
# 解码JWT payload
|
||||
import base64
|
||||
import json
|
||||
|
||||
# JWT由三部分组成:header.payload.signature
|
||||
parts = token.split('.')
|
||||
if len(parts) == 3:
|
||||
# 解码payload (第二部分)
|
||||
payload = parts[1]
|
||||
# 添加padding
|
||||
payload += '=' * (4 - len(payload) % 4)
|
||||
decoded = base64.urlsafe_b64decode(payload)
|
||||
payload_data = json.loads(decoded.decode('utf-8'))
|
||||
print("JWT Payload:", payload_data)
|
||||
else:
|
||||
print("Invalid JWT format")
|
||||
|
||||
59
demo/test_payment.py
Normal file
59
demo/test_payment.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
# 测试支付API
|
||||
def test_payment_api():
|
||||
# 首先创建支付记录
|
||||
create_payment_url = "http://localhost:8080/api/payments/create"
|
||||
create_payment_data = {
|
||||
"amount": 59.00,
|
||||
"description": "标准版会员 - 支付宝支付",
|
||||
"paymentMethod": "ALIPAY"
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": "Bearer eyJhbGciOiJIUzUxMiJ9.eyJyb2xlIjoiUk9MRV9VU0VSIiwidXNlcklkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwic3ViIjoiYWRtaW4iLCJpYXQiOjE3NjE2MzYyNDQsImV4cCI6MTc2MTcyMjY0NH0.qZxHDkgSoSRvmMHBFfRdzZYtC55eCKba3VN07lTsFzKXn1hYbupv7boBJDKNOUrRYaH5ougHLFTI5xm059434g",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
try:
|
||||
print("1. 创建支付记录...")
|
||||
response = requests.post(create_payment_url, json=create_payment_data, headers=headers)
|
||||
print(f"创建支付记录状态码: {response.status_code}")
|
||||
print(f"创建支付记录响应: {response.text}")
|
||||
|
||||
if response.status_code == 200:
|
||||
payment_data = response.json()
|
||||
if payment_data.get('success'):
|
||||
payment_id = payment_data['data']['id']
|
||||
print(f"支付记录创建成功,ID: {payment_id}")
|
||||
|
||||
# 然后创建支付宝支付
|
||||
alipay_url = "http://localhost:8080/api/payments/alipay/create"
|
||||
alipay_data = {"paymentId": payment_id}
|
||||
|
||||
print("2. 创建支付宝支付...")
|
||||
alipay_response = requests.post(alipay_url, json=alipay_data, headers=headers)
|
||||
print(f"支付宝支付状态码: {alipay_response.status_code}")
|
||||
print(f"支付宝支付响应: {alipay_response.text}")
|
||||
|
||||
if alipay_response.status_code == 200:
|
||||
alipay_data = alipay_response.json()
|
||||
if alipay_data.get('success'):
|
||||
qr_code = alipay_data['data'].get('qrCode')
|
||||
print(f"支付宝二维码生成成功: {qr_code}")
|
||||
else:
|
||||
print(f"支付宝支付失败: {alipay_data.get('message')}")
|
||||
else:
|
||||
print(f"支付宝支付请求失败: {alipay_response.text}")
|
||||
else:
|
||||
print(f"支付记录创建失败: {payment_data.get('message')}")
|
||||
else:
|
||||
print(f"创建支付记录请求失败: {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"测试失败: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_payment_api()
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 286 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 286 KiB |
80
test_alipay_connection.py
Normal file
80
test_alipay_connection.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import requests
|
||||
import time
|
||||
|
||||
def test_alipay_connection():
|
||||
"""测试支付宝沙箱API连接"""
|
||||
print("=== 测试支付宝沙箱API连接 ===")
|
||||
|
||||
# 支付宝沙箱网关地址
|
||||
gateway_url = "https://openapi.alipaydev.com/gateway.do"
|
||||
|
||||
# 测试连接
|
||||
try:
|
||||
print(f"正在测试连接到: {gateway_url}")
|
||||
response = requests.get(gateway_url, timeout=10)
|
||||
print(f"连接成功! 状态码: {response.status_code}")
|
||||
print(f"响应头: {dict(response.headers)}")
|
||||
return True
|
||||
except requests.exceptions.Timeout:
|
||||
print("❌ 连接超时 - 可能是网络问题")
|
||||
return False
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
print(f"❌ 连接错误: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 其他错误: {e}")
|
||||
return False
|
||||
|
||||
def test_alternative_gateway():
|
||||
"""测试备用网关"""
|
||||
print("\n=== 测试备用网关 ===")
|
||||
|
||||
# 尝试不同的网关地址
|
||||
gateways = [
|
||||
"https://openapi.alipaydev.com/gateway.do",
|
||||
"https://openapi.alipay.com/gateway.do",
|
||||
"https://mapi.alipay.com/gateway.do"
|
||||
]
|
||||
|
||||
for gateway in gateways:
|
||||
try:
|
||||
print(f"测试网关: {gateway}")
|
||||
response = requests.get(gateway, timeout=5)
|
||||
print(f"✅ {gateway} - 状态码: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ {gateway} - 错误: {e}")
|
||||
|
||||
def test_network_connectivity():
|
||||
"""测试基本网络连接"""
|
||||
print("\n=== 测试基本网络连接 ===")
|
||||
|
||||
test_urls = [
|
||||
"https://www.baidu.com",
|
||||
"https://www.google.com",
|
||||
"https://openapi.alipaydev.com"
|
||||
]
|
||||
|
||||
for url in test_urls:
|
||||
try:
|
||||
response = requests.get(url, timeout=5)
|
||||
print(f"✅ {url} - 状态码: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ {url} - 错误: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("开始网络连接测试...")
|
||||
|
||||
# 测试基本网络连接
|
||||
test_network_connectivity()
|
||||
|
||||
# 测试支付宝连接
|
||||
alipay_ok = test_alipay_connection()
|
||||
|
||||
# 测试备用网关
|
||||
test_alternative_gateway()
|
||||
|
||||
if alipay_ok:
|
||||
print("\n✅ 支付宝沙箱API连接正常")
|
||||
else:
|
||||
print("\n❌ 支付宝沙箱API连接有问题,建议检查网络或使用模拟支付")
|
||||
|
||||
15
test_api.py
Normal file
15
test_api.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import requests
|
||||
|
||||
# 测试作品列表API
|
||||
url = "http://localhost:8080/api/works/my-works"
|
||||
headers = {
|
||||
"Authorization": "Bearer eyJhbGciOiJIUzUxMiJ9.eyJyb2xlIjoiUk9MRV9VU0VSIiwidXNlcklkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwic3ViIjoiYWRtaW4iLCJpYXQiOjE3NjE2MzYyNDQsImV4cCI6MTc2MTcyMjY0NH0.qZxHDkgSoSRvmMHBFfRdzZYtC55eCKba3VN07lTsFzKXn1hYbupv7boBJDKNOUrRYaH5ougHLFTI5xm059434g"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers, params={"page": 0, "size": 10})
|
||||
print(f"Status Code: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
Reference in New Issue
Block a user