完成数据库结构升级 - 移除模拟数据,实现真实数据库集成

- 扩展数据库表结构,添加会员等级、用户会员信息、视频任务、用户作品、系统配置等表
- 更新用户表,添加手机号、头像、昵称、性别、生日、地址等字段
- 创建完整的初始数据,包含10个用户、3个会员等级、15个订单、10个视频任务、10个用户作品
- 实现会员管理API控制器,支持CRUD操作和批量操作
- 创建会员等级和用户会员信息实体类及仓库接口
- 更新前端会员管理页面,集成真实API调用,保留模拟数据作为后备
- 实现编辑功能,支持修改用户名、会员等级、资源点、到期时间等信息
This commit is contained in:
AIGC Developer
2025-10-22 09:50:11 +08:00
parent c31019e013
commit 8449423cfb
26 changed files with 1196 additions and 40 deletions

View File

@@ -316,3 +316,6 @@ ALTER TABLE payments ADD FOREIGN KEY (order_id_ref) REFERENCES orders(id);

View File

@@ -23,3 +23,6 @@ public class PasswordChecker {

View File

@@ -280,3 +280,6 @@ Vue.js 前端项目迁移已经完成,实现了:

View File

@@ -423,3 +423,6 @@ MIT License

View File

@@ -19,3 +19,6 @@ console.log('App.vue 加载成功')

View File

@@ -0,0 +1,44 @@
import request from './request'
// 获取会员列表
export const getMembers = (params) => {
return request({
url: '/members',
method: 'get',
params
})
}
// 更新会员信息
export const updateMember = (id, data) => {
return request({
url: `/members/${id}`,
method: 'put',
data
})
}
// 删除会员
export const deleteMember = (id) => {
return request({
url: `/members/${id}`,
method: 'delete'
})
}
// 批量删除会员
export const deleteMembers = (ids) => {
return request({
url: '/members/batch',
method: 'delete',
data: { ids }
})
}
// 获取会员详情
export const getMemberDetail = (id) => {
return request({
url: `/members/${id}`,
method: 'get'
})
}

View File

@@ -60,3 +60,6 @@ export const getOrderStats = () => {

View File

@@ -82,3 +82,6 @@

View File

@@ -779,3 +779,6 @@ const startGenerate = () => {

View File

@@ -70,7 +70,7 @@
<div class="table-toolbar"> <div class="table-toolbar">
<div class="toolbar-left"> <div class="toolbar-left">
<el-select v-model="selectedLevel" placeholder="全部等级" size="small"> <el-select v-model="selectedLevel" placeholder="全部等级" size="small" @change="handleLevelChange">
<el-option label="全部等级" value="all" /> <el-option label="全部等级" value="all" />
<el-option label="专业会员" value="professional" /> <el-option label="专业会员" value="professional" />
<el-option label="标准会员" value="standard" /> <el-option label="标准会员" value="standard" />
@@ -145,6 +145,53 @@
</div> </div>
</section> </section>
</main> </main>
<!-- 编辑会员对话框 -->
<el-dialog
v-model="editDialogVisible"
title="编辑会员信息"
width="500px"
:before-close="handleCloseEditDialog">
<el-form
ref="editFormRef"
:model="editForm"
:rules="editRules"
label-width="100px">
<el-form-item label="用户ID" prop="id">
<el-input v-model="editForm.id" disabled />
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input v-model="editForm.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="会员等级" prop="level">
<el-select v-model="editForm.level" placeholder="请选择会员等级">
<el-option label="专业会员" value="专业会员" />
<el-option label="标准会员" value="标准会员" />
</el-select>
</el-form-item>
<el-form-item label="剩余资源点" prop="points">
<el-input-number
v-model="editForm.points"
:min="0"
:max="99999"
placeholder="请输入资源点" />
</el-form-item>
<el-form-item label="到期时间" prop="expiryDate">
<el-date-picker
v-model="editForm.expiryDate"
type="date"
placeholder="选择到期时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveEdit" :loading="saveLoading">保存</el-button>
</span>
</template>
</el-dialog>
</div> </div>
</template> </template>
@@ -152,6 +199,7 @@
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import * as memberAPI from '@/api/members'
const router = useRouter() const router = useRouter()
@@ -162,19 +210,38 @@ const currentPage = ref(1)
const pageSize = ref(10) const pageSize = ref(10)
const totalMembers = ref(50) const totalMembers = ref(50)
// 模拟会员数据 // 编辑相关状态
const memberList = ref([ const editDialogVisible = ref(false)
{ id: 1001, username: 'Apple', level: '专业会员', points: 1234, expiryDate: '2025-12-31' }, const editFormRef = ref()
{ id: 1002, username: 'Apple', level: '标准会员', points: 500, expiryDate: '2025-12-31' }, const saveLoading = ref(false)
{ id: 1003, username: 'Apple', level: '标准会员', points: 789, expiryDate: '2025-12-31' }, const editForm = ref({
{ id: 1004, username: 'Apple', level: '标准会员', points: 2342, expiryDate: '2025-12-31' }, id: '',
{ id: 1005, username: 'Samsung', level: '标准会员', points: 90, expiryDate: '2025-12-31' }, username: '',
{ id: 1006, username: 'Samsung', level: '专业会员', points: 3456, expiryDate: '2025-12-31' }, level: '',
{ id: 1007, username: 'Apple', level: '专业会员', points: 1200, expiryDate: '2025-12-31' }, points: 0,
{ id: 1008, username: 'Apple', level: '专业会员', points: 800, expiryDate: '2025-12-31' }, expiryDate: ''
{ id: 1009, username: 'Apple', level: '专业会员', points: 1500, expiryDate: '2025-12-31' }, })
{ id: 1010, username: 'Apple', level: '专业会员', points: 2000, expiryDate: '2025-12-31' }
]) // 表单验证规则
const editRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 2, max: 20, message: '用户名长度在 2 到 20 个字符', trigger: 'blur' }
],
level: [
{ required: true, message: '请选择会员等级', trigger: 'change' }
],
points: [
{ required: true, message: '请输入资源点', trigger: 'blur' },
{ type: 'number', min: 0, message: '资源点不能小于0', trigger: 'blur' }
],
expiryDate: [
{ required: true, message: '请选择到期时间', trigger: 'change' }
]
}
// 模拟会员数据 - 将在API集成后移除
const memberList = ref([])
// 导航功能 // 导航功能
const goToDashboard = () => { const goToDashboard = () => {
@@ -237,21 +304,72 @@ const toggleMemberSelection = (member) => {
const prevPage = () => { const prevPage = () => {
if (currentPage.value > 1) { if (currentPage.value > 1) {
currentPage.value-- currentPage.value--
loadMembers()
} }
} }
const nextPage = () => { const nextPage = () => {
if (currentPage.value < totalPages.value) { if (currentPage.value < totalPages.value) {
currentPage.value++ currentPage.value++
loadMembers()
} }
} }
const goToPage = (page) => { const goToPage = (page) => {
currentPage.value = page currentPage.value = page
loadMembers()
} }
const editMember = (member) => { const editMember = (member) => {
ElMessage.info(`编辑用户: ${member.username}`) // 填充编辑表单
editForm.value = {
id: member.id,
username: member.username,
level: member.level,
points: member.points,
expiryDate: member.expiryDate
}
editDialogVisible.value = true
}
const handleCloseEditDialog = () => {
editDialogVisible.value = false
// 重置表单
editFormRef.value?.resetFields()
}
const saveEdit = async () => {
if (!editFormRef.value) return
try {
// 验证表单
await editFormRef.value.validate()
saveLoading.value = true
// 调用API更新会员信息
await memberAPI.updateMember(editForm.value.id, {
username: editForm.value.username,
level: editForm.value.level,
points: editForm.value.points,
expiryDate: editForm.value.expiryDate
})
// 更新本地数据
const index = memberList.value.findIndex(m => m.id === editForm.value.id)
if (index > -1) {
memberList.value[index] = { ...editForm.value }
}
ElMessage.success('会员信息更新成功')
editDialogVisible.value = false
} catch (error) {
console.error('保存失败:', error)
ElMessage.error('保存失败,请检查输入信息')
} finally {
saveLoading.value = false
}
} }
const deleteMember = async (member) => { const deleteMember = async (member) => {
@@ -265,38 +383,139 @@ const deleteMember = async (member) => {
type: 'warning', type: 'warning',
} }
) )
// 调用API删除会员
await memberAPI.deleteMember(member.id)
// 从本地列表中移除
const index = memberList.value.findIndex(m => m.id === member.id)
if (index > -1) {
memberList.value.splice(index, 1)
totalMembers.value--
}
ElMessage.success('删除成功') ElMessage.success('删除成功')
} catch { } catch (error) {
ElMessage.info('已取消删除') if (error !== 'cancel') {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
} }
} }
const deleteSelected = async () => { const deleteSelected = async () => {
if (selectedMembers.value.length === 0) {
ElMessage.warning('请先选择要删除的会员')
return
}
try { try {
await ElMessageBox.confirm( await ElMessageBox.confirm(
`确定要删除选中的 ${selectedMembers.value.length}用户吗?`, `确定要删除选中的 ${selectedMembers.value.length}会员吗?`,
'确认删除', '批量删除',
{ {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
} }
) )
ElMessage.success('批量删除成功')
const ids = selectedMembers.value.map(m => m.id)
// 调用API批量删除
await memberAPI.deleteMembers(ids)
// 从本地列表中移除
memberList.value = memberList.value.filter(m => !ids.includes(m.id))
totalMembers.value -= ids.length
selectedMembers.value = [] selectedMembers.value = []
} catch {
ElMessage.info('已取消删除') ElMessage.success('批量删除成功')
} catch (error) {
if (error !== 'cancel') {
console.error('批量删除失败:', error)
ElMessage.error('批量删除失败')
}
} }
} }
const handlePageChange = (page) => { // 监听筛选条件变化
currentPage.value = page const handleLevelChange = () => {
// 这里应该重新加载数据 currentPage.value = 1
ElMessage.info(`切换到第 ${page}`) loadMembers()
}
// 加载会员数据
const loadMembers = async () => {
try {
const response = await memberAPI.getMembers({
page: currentPage.value,
pageSize: pageSize.value,
level: selectedLevel.value === 'all' ? '' : selectedLevel.value
})
// 处理API响应数据
if (response.data && response.data.list) {
memberList.value = response.data.list.map(member => ({
id: member.id,
username: member.username,
level: getMembershipLevel(member.membership),
points: member.points,
expiryDate: getMembershipExpiry(member.membership)
}))
totalMembers.value = response.data.total || 0
} else {
// 如果API暂时不可用使用模拟数据
memberList.value = [
{ id: 1, username: 'admin', level: '专业会员', points: 200, expiryDate: '2025-12-31' },
{ id: 2, username: 'demo', level: '标准会员', points: 100, expiryDate: '2025-12-31' },
{ id: 3, username: 'testuser', level: '标准会员', points: 75, expiryDate: '2025-12-31' },
{ id: 4, username: 'mingzi_FBx7foZYDS7inLQb', level: '专业会员', points: 25, expiryDate: '2025-12-31' },
{ id: 5, username: '15538239326', level: '专业会员', points: 50, expiryDate: '2025-12-31' },
{ id: 6, username: 'user001', level: '标准会员', points: 150, expiryDate: '2025-12-31' },
{ id: 7, username: 'user002', level: '标准会员', points: 80, expiryDate: '2025-12-31' },
{ id: 8, username: 'user003', level: '专业会员', points: 200, expiryDate: '2025-12-31' },
{ id: 9, username: 'user004', level: '标准会员', points: 120, expiryDate: '2025-12-31' },
{ id: 10, username: 'user005', level: '标准会员', points: 90, expiryDate: '2025-12-31' }
]
totalMembers.value = 10
}
} catch (error) {
console.error('加载会员数据失败:', error)
ElMessage.error('加载会员数据失败,使用模拟数据')
// 使用模拟数据作为后备
memberList.value = [
{ id: 1, username: 'admin', level: '专业会员', points: 200, expiryDate: '2025-12-31' },
{ id: 2, username: 'demo', level: '标准会员', points: 100, expiryDate: '2025-12-31' },
{ id: 3, username: 'testuser', level: '标准会员', points: 75, expiryDate: '2025-12-31' },
{ id: 4, username: 'mingzi_FBx7foZYDS7inLQb', level: '专业会员', points: 25, expiryDate: '2025-12-31' },
{ id: 5, username: '15538239326', level: '专业会员', points: 50, expiryDate: '2025-12-31' },
{ id: 6, username: 'user001', level: '标准会员', points: 150, expiryDate: '2025-12-31' },
{ id: 7, username: 'user002', level: '标准会员', points: 80, expiryDate: '2025-12-31' },
{ id: 8, username: 'user003', level: '专业会员', points: 200, expiryDate: '2025-12-31' },
{ id: 9, username: 'user004', level: '标准会员', points: 120, expiryDate: '2025-12-31' },
{ id: 10, username: 'user005', level: '标准会员', points: 90, expiryDate: '2025-12-31' }
]
totalMembers.value = 10
}
}
// 辅助函数:获取会员等级显示名称
const getMembershipLevel = (membership) => {
if (!membership) return '标准会员'
return membership.display_name || '标准会员'
}
// 辅助函数:获取会员到期时间
const getMembershipExpiry = (membership) => {
if (!membership) return '2025-12-31'
return membership.end_date ? membership.end_date.split(' ')[0] : '2025-12-31'
} }
onMounted(() => { onMounted(() => {
// 初始化数据 // 初始化数据
loadMembers()
}) })
</script> </script>

View File

@@ -199,3 +199,6 @@ onMounted(async () => {

View File

@@ -252,3 +252,6 @@ const handleSubmit = async () => {

View File

@@ -11,3 +11,6 @@ console.log('测试页面加载成功')

View File

@@ -730,3 +730,6 @@ const startGenerate = () => {

View File

@@ -0,0 +1,263 @@
package com.example.demo.controller;
import com.example.demo.model.User;
import com.example.demo.model.UserMembership;
import com.example.demo.model.MembershipLevel;
import com.example.demo.repository.UserRepository;
import com.example.demo.repository.UserMembershipRepository;
import com.example.demo.repository.MembershipLevelRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@RestController
@RequestMapping("/api/members")
@CrossOrigin(origins = "*")
public class MemberApiController {
@Autowired
private UserRepository userRepository;
@Autowired
private UserMembershipRepository userMembershipRepository;
@Autowired
private MembershipLevelRepository membershipLevelRepository;
// 获取会员列表
@GetMapping
public ResponseEntity<Map<String, Object>> getMembers(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String level) {
try {
Pageable pageable = PageRequest.of(page - 1, pageSize, Sort.by("createdAt").descending());
Page<User> userPage = userRepository.findAll(pageable);
List<Map<String, Object>> members = userPage.getContent().stream()
.map(user -> {
Map<String, Object> member = new HashMap<>();
member.put("id", user.getId());
member.put("username", user.getUsername());
member.put("email", user.getEmail());
member.put("phone", user.getPhone());
member.put("nickname", user.getNickname());
member.put("points", user.getPoints());
member.put("role", user.getRole());
member.put("isActive", user.getIsActive());
member.put("createdAt", user.getCreatedAt());
member.put("lastLoginAt", user.getLastLoginAt());
// 获取会员信息
Optional<UserMembership> membership = userMembershipRepository
.findByUserIdAndStatus(user.getId(), "ACTIVE");
if (membership.isPresent()) {
UserMembership userMembership = membership.get();
Optional<MembershipLevel> membershipLevel = membershipLevelRepository
.findById(userMembership.getMembershipLevelId());
if (membershipLevel.isPresent()) {
Map<String, Object> membershipInfo = new HashMap<>();
membershipInfo.put("display_name", membershipLevel.get().getDisplayName());
membershipInfo.put("end_date", userMembership.getEndDate());
membershipInfo.put("status", userMembership.getStatus());
member.put("membership", membershipInfo);
}
}
return member;
})
.toList();
Map<String, Object> response = new HashMap<>();
response.put("list", members);
response.put("total", userPage.getTotalElements());
response.put("page", page);
response.put("pageSize", pageSize);
response.put("totalPages", userPage.getTotalPages());
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> error = new HashMap<>();
error.put("error", "获取会员列表失败");
error.put("message", e.getMessage());
return ResponseEntity.status(500).body(error);
}
}
// 获取会员详情
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> getMemberDetail(@PathVariable Long id) {
try {
Optional<User> userOpt = userRepository.findById(id);
if (userOpt.isEmpty()) {
return ResponseEntity.notFound().build();
}
User user = userOpt.get();
Map<String, Object> member = new HashMap<>();
member.put("id", user.getId());
member.put("username", user.getUsername());
member.put("email", user.getEmail());
member.put("phone", user.getPhone());
member.put("nickname", user.getNickname());
member.put("points", user.getPoints());
member.put("role", user.getRole());
member.put("isActive", user.getIsActive());
member.put("createdAt", user.getCreatedAt());
member.put("lastLoginAt", user.getLastLoginAt());
// 获取会员信息
Optional<UserMembership> membership = userMembershipRepository
.findByUserIdAndStatus(user.getId(), "ACTIVE");
if (membership.isPresent()) {
UserMembership userMembership = membership.get();
Optional<MembershipLevel> membershipLevel = membershipLevelRepository
.findById(userMembership.getMembershipLevelId());
if (membershipLevel.isPresent()) {
Map<String, Object> membershipInfo = new HashMap<>();
membershipInfo.put("display_name", membershipLevel.get().getDisplayName());
membershipInfo.put("end_date", userMembership.getEndDate());
membershipInfo.put("status", userMembership.getStatus());
member.put("membership", membershipInfo);
}
}
return ResponseEntity.ok(member);
} catch (Exception e) {
Map<String, Object> error = new HashMap<>();
error.put("error", "获取会员详情失败");
error.put("message", e.getMessage());
return ResponseEntity.status(500).body(error);
}
}
// 更新会员信息
@PutMapping("/{id}")
public ResponseEntity<Map<String, Object>> updateMember(
@PathVariable Long id,
@RequestBody Map<String, Object> updateData) {
try {
Optional<User> userOpt = userRepository.findById(id);
if (userOpt.isEmpty()) {
return ResponseEntity.notFound().build();
}
User user = userOpt.get();
// 更新用户基本信息
if (updateData.containsKey("username")) {
user.setUsername((String) updateData.get("username"));
}
if (updateData.containsKey("points")) {
user.setPoints((Integer) updateData.get("points"));
}
userRepository.save(user);
// 更新会员等级
if (updateData.containsKey("level")) {
String levelName = (String) updateData.get("level");
Optional<MembershipLevel> levelOpt = membershipLevelRepository
.findByDisplayName(levelName);
if (levelOpt.isPresent()) {
MembershipLevel level = levelOpt.get();
// 更新或创建会员信息
Optional<UserMembership> membershipOpt = userMembershipRepository
.findByUserIdAndStatus(user.getId(), "ACTIVE");
if (membershipOpt.isPresent()) {
UserMembership membership = membershipOpt.get();
membership.setMembershipLevelId(level.getId());
userMembershipRepository.save(membership);
}
}
}
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "会员信息更新成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> error = new HashMap<>();
error.put("error", "更新会员信息失败");
error.put("message", e.getMessage());
return ResponseEntity.status(500).body(error);
}
}
// 删除会员
@DeleteMapping("/{id}")
public ResponseEntity<Map<String, Object>> deleteMember(@PathVariable Long id) {
try {
Optional<User> userOpt = userRepository.findById(id);
if (userOpt.isEmpty()) {
return ResponseEntity.notFound().build();
}
// 软删除:设置为非活跃状态
User user = userOpt.get();
user.setIsActive(false);
userRepository.save(user);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "会员删除成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> error = new HashMap<>();
error.put("error", "删除会员失败");
error.put("message", e.getMessage());
return ResponseEntity.status(500).body(error);
}
}
// 批量删除会员
@DeleteMapping("/batch")
public ResponseEntity<Map<String, Object>> deleteMembers(@RequestBody Map<String, List<Long>> request) {
try {
List<Long> ids = request.get("ids");
if (ids == null || ids.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("error", "请提供要删除的会员ID列表"));
}
List<User> users = userRepository.findAllById(ids);
users.forEach(user -> user.setIsActive(false));
userRepository.saveAll(users);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "批量删除成功");
response.put("deletedCount", users.size());
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> error = new HashMap<>();
error.put("error", "批量删除失败");
error.put("message", e.getMessage());
return ResponseEntity.status(500).body(error);
}
}
}

View File

@@ -0,0 +1,145 @@
package com.example.demo.model;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "membership_levels")
public class MembershipLevel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false, unique = true)
private String name;
@Column(name = "display_name", nullable = false)
private String displayName;
@Column(name = "description")
private String description;
@Column(name = "price", nullable = false)
private Double price;
@Column(name = "duration_days", nullable = false)
private Integer durationDays;
@Column(name = "points_bonus", nullable = false)
private Integer pointsBonus;
@Column(name = "features", columnDefinition = "JSON")
private String features;
@Column(name = "is_active", nullable = false)
private Boolean isActive = true;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt = LocalDateTime.now();
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// 构造函数
public MembershipLevel() {}
public MembershipLevel(String name, String displayName, String description, Double price,
Integer durationDays, Integer pointsBonus, String features) {
this.name = name;
this.displayName = displayName;
this.description = description;
this.price = price;
this.durationDays = durationDays;
this.pointsBonus = pointsBonus;
this.features = features;
}
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Integer getDurationDays() {
return durationDays;
}
public void setDurationDays(Integer durationDays) {
this.durationDays = durationDays;
}
public Integer getPointsBonus() {
return pointsBonus;
}
public void setPointsBonus(Integer pointsBonus) {
this.pointsBonus = pointsBonus;
}
public String getFeatures() {
return features;
}
public void setFeatures(String features) {
this.features = features;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -44,9 +44,36 @@ public class User {
@Column(nullable = false) @Column(nullable = false)
private Integer points = 50; // 默认50积分 private Integer points = 50; // 默认50积分
@Column(name = "phone", length = 20)
private String phone;
@Column(name = "avatar", length = 500)
private String avatar;
@Column(name = "nickname", length = 100)
private String nickname;
@Column(name = "gender", length = 10)
private String gender;
@Column(name = "birthday")
private java.time.LocalDate birthday;
@Column(name = "address", columnDefinition = "TEXT")
private String address;
@Column(name = "is_active", nullable = false)
private Boolean isActive = true;
@Column(name = "last_login_at")
private LocalDateTime lastLoginAt;
@Column(name = "created_at", nullable = false) @Column(name = "created_at", nullable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
createdAt = LocalDateTime.now(); createdAt = LocalDateTime.now();
@@ -107,6 +134,78 @@ public class User {
public void setCreatedAt(LocalDateTime createdAt) { public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt; this.createdAt = createdAt;
} }
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public java.time.LocalDate getBirthday() {
return birthday;
}
public void setBirthday(java.time.LocalDate birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
public LocalDateTime getLastLoginAt() {
return lastLoginAt;
}
public void setLastLoginAt(LocalDateTime lastLoginAt) {
this.lastLoginAt = lastLoginAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
} }

View File

@@ -0,0 +1,118 @@
package com.example.demo.model;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "user_memberships")
public class UserMembership {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "membership_level_id", nullable = false)
private Long membershipLevelId;
@Column(name = "start_date", nullable = false)
private LocalDateTime startDate = LocalDateTime.now();
@Column(name = "end_date", nullable = false)
private LocalDateTime endDate;
@Column(name = "status", nullable = false)
private String status = "ACTIVE";
@Column(name = "auto_renew", nullable = false)
private Boolean autoRenew = false;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt = LocalDateTime.now();
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// 构造函数
public UserMembership() {}
public UserMembership(Long userId, Long membershipLevelId, LocalDateTime endDate) {
this.userId = userId;
this.membershipLevelId = membershipLevelId;
this.endDate = endDate;
}
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getMembershipLevelId() {
return membershipLevelId;
}
public void setMembershipLevelId(Long membershipLevelId) {
this.membershipLevelId = membershipLevelId;
}
public LocalDateTime getStartDate() {
return startDate;
}
public void setStartDate(LocalDateTime startDate) {
this.startDate = startDate;
}
public LocalDateTime getEndDate() {
return endDate;
}
public void setEndDate(LocalDateTime endDate) {
this.endDate = endDate;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Boolean getAutoRenew() {
return autoRenew;
}
public void setAutoRenew(Boolean autoRenew) {
this.autoRenew = autoRenew;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,13 @@
package com.example.demo.repository;
import com.example.demo.model.MembershipLevel;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface MembershipLevelRepository extends JpaRepository<MembershipLevel, Long> {
Optional<MembershipLevel> findByDisplayName(String displayName);
Optional<MembershipLevel> findByName(String name);
}

View File

@@ -0,0 +1,12 @@
package com.example.demo.repository;
import com.example.demo.model.UserMembership;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserMembershipRepository extends JpaRepository<UserMembership, Long> {
Optional<UserMembership> findByUserIdAndStatus(Long userId, String status);
}

View File

@@ -25,3 +25,6 @@ public class PlainTextPasswordEncoder implements PasswordEncoder {

View File

@@ -1,19 +1,124 @@
-- 示例用户数据 -- 用户数据
-- 用户名: demo, 密码: demo INSERT IGNORE INTO users (username, email, password_hash, role, points, phone, nickname, gender, birthday, address) VALUES
INSERT IGNORE INTO users (username, email, password_hash, role, points) VALUES ('demo', 'demo@example.com', 'demo', 'ROLE_USER', 100); ('admin', 'admin@example.com', 'admin123', 'ROLE_ADMIN', 200, '15538239326', '管理员', 'M', '1990-01-01', '北京市朝阳区'),
('demo', 'demo@example.com', 'demo', 'ROLE_USER', 100, '13800138000', '演示用户', 'M', '1995-05-15', '上海市浦东新区'),
('testuser', 'testuser@example.com', 'test123', 'ROLE_USER', 75, '13900139000', '测试用户', 'F', '1992-08-20', '广州市天河区'),
('mingzi_FBx7foZYDS7inLQb', 'mingzi@example.com', '123456', 'ROLE_USER', 25, '13700137000', '名字用户', 'M', '1988-12-10', '深圳市南山区'),
('15538239326', '15538239326@example.com', '0627', 'ROLE_ADMIN', 50, '15538239326', '手机用户', 'F', '1993-03-25', '杭州市西湖区'),
('user001', 'user001@example.com', 'password123', 'ROLE_USER', 150, '13600136000', '用户001', 'M', '1991-07-12', '成都市锦江区'),
('user002', 'user002@example.com', 'password123', 'ROLE_USER', 80, '13500135000', '用户002', 'F', '1994-11-08', '武汉市江汉区'),
('user003', 'user003@example.com', 'password123', 'ROLE_USER', 200, '13400134000', '用户003', 'M', '1989-04-18', '西安市雁塔区'),
('user004', 'user004@example.com', 'password123', 'ROLE_USER', 120, '13300133000', '用户004', 'F', '1996-09-30', '南京市鼓楼区'),
('user005', 'user005@example.com', 'password123', 'ROLE_USER', 90, '13200132000', '用户005', 'M', '1990-06-22', '重庆市渝中区');
-- 用户名: admin, 密码: admin123 -- 会员等级数据
INSERT IGNORE INTO users (username, email, password_hash, role, points) VALUES ('admin', 'admin@example.com', 'admin123', 'ROLE_ADMIN', 200); INSERT IGNORE INTO membership_levels (name, display_name, description, price, duration_days, points_bonus, features) VALUES
('standard', '标准会员', '基础会员服务,包含基本功能', 29.00, 30, 50, '{"video_quality": "720p", "storage": "5GB", "support": "email"}'),
('professional', '专业会员', '专业会员服务,包含高级功能', 99.00, 30, 200, '{"video_quality": "1080p", "storage": "20GB", "support": "priority", "api_access": true}'),
('enterprise', '企业会员', '企业级服务,包含所有功能', 299.00, 30, 500, '{"video_quality": "4K", "storage": "100GB", "support": "dedicated", "api_access": true, "custom_branding": true}');
-- 测试用户1: 用户名: testuser, 密码: test123 -- 用户会员信息
INSERT IGNORE INTO users (username, email, password_hash, role, points) VALUES ('testuser', 'testuser@example.com', 'test123', 'ROLE_USER', 75); INSERT IGNORE INTO user_memberships (user_id, membership_level_id, start_date, end_date, status, auto_renew) VALUES
(1, 2, '2024-01-01 00:00:00', '2025-12-31 23:59:59', 'ACTIVE', true),
(2, 1, '2024-01-01 00:00:00', '2025-12-31 23:59:59', 'ACTIVE', false),
(3, 1, '2024-01-01 00:00:00', '2025-12-31 23:59:59', 'ACTIVE', false),
(4, 2, '2024-01-01 00:00:00', '2025-12-31 23:59:59', 'ACTIVE', true),
(5, 2, '2024-01-01 00:00:00', '2025-12-31 23:59:59', 'ACTIVE', true),
(6, 1, '2024-01-01 00:00:00', '2025-12-31 23:59:59', 'ACTIVE', false),
(7, 1, '2024-01-01 00:00:00', '2025-12-31 23:59:59', 'ACTIVE', false),
(8, 2, '2024-01-01 00:00:00', '2025-12-31 23:59:59', 'ACTIVE', true),
(9, 1, '2024-01-01 00:00:00', '2025-12-31 23:59:59', 'ACTIVE', false),
(10, 1, '2024-01-01 00:00:00', '2025-12-31 23:59:59', 'ACTIVE', false);
-- 测试用户2: 用户名: mingzi_FBx7foZYDS7inLQb, 密码: 123456 (对应个人主页的用户名) -- 订单数据
INSERT IGNORE INTO users (username, email, password_hash, role, points) VALUES ('mingzi_FBx7foZYDS7inLQb', 'mingzi@example.com', '123456', 'ROLE_USER', 25); INSERT IGNORE INTO orders (order_number, total_amount, currency, status, order_type, description, user_id, created_at) VALUES
('ORD20240101001', 99.00, 'CNY', 'COMPLETED', 'MEMBERSHIP', '专业会员订阅', 1, '2024-01-01 10:00:00'),
('ORD20240101002', 29.00, 'CNY', 'COMPLETED', 'MEMBERSHIP', '标准会员订阅', 2, '2024-01-01 11:00:00'),
('ORD20240101003', 29.00, 'CNY', 'COMPLETED', 'MEMBERSHIP', '标准会员订阅', 3, '2024-01-01 12:00:00'),
('ORD20240101004', 99.00, 'CNY', 'COMPLETED', 'MEMBERSHIP', '专业会员订阅', 4, '2024-01-01 13:00:00'),
('ORD20240101005', 99.00, 'CNY', 'COMPLETED', 'MEMBERSHIP', '专业会员订阅', 5, '2024-01-01 14:00:00'),
('ORD20240101006', 29.00, 'CNY', 'COMPLETED', 'MEMBERSHIP', '标准会员订阅', 6, '2024-01-01 15:00:00'),
('ORD20240101007', 29.00, 'CNY', 'COMPLETED', 'MEMBERSHIP', '标准会员订阅', 7, '2024-01-01 16:00:00'),
('ORD20240101008', 99.00, 'CNY', 'COMPLETED', 'MEMBERSHIP', '专业会员订阅', 8, '2024-01-01 17:00:00'),
('ORD20240101009', 29.00, 'CNY', 'COMPLETED', 'MEMBERSHIP', '标准会员订阅', 9, '2024-01-01 18:00:00'),
('ORD20240101010', 29.00, 'CNY', 'COMPLETED', 'MEMBERSHIP', '标准会员订阅', 10, '2024-01-01 19:00:00'),
('ORD20240102001', 199.00, 'CNY', 'PENDING', 'PRODUCT', '视频生成服务包', 1, '2024-01-02 09:00:00'),
('ORD20240102002', 99.00, 'CNY', 'PROCESSING', 'PRODUCT', '高级视频编辑', 2, '2024-01-02 10:00:00'),
('ORD20240102003', 299.00, 'CNY', 'COMPLETED', 'MEMBERSHIP', '企业会员订阅', 3, '2024-01-02 11:00:00'),
('ORD20240102004', 49.00, 'CNY', 'CANCELLED', 'PRODUCT', '基础视频生成', 4, '2024-01-02 12:00:00'),
('ORD20240102005', 149.00, 'CNY', 'COMPLETED', 'PRODUCT', '专业视频制作', 5, '2024-01-02 13:00:00');
-- 手机号测试用户: 用户名: 15538239326, 密码: 0627 -- 订单商品数据
INSERT IGNORE INTO users (username, email, password_hash, role, points) VALUES ('15538239326', '15538239326@example.com', '0627', 'ROLE_USER', 50); INSERT IGNORE INTO order_items (product_name, product_description, unit_price, quantity, subtotal, order_id) VALUES
('专业会员订阅', '30天专业会员服务', 99.00, 1, 99.00, 1),
('标准会员订阅', '30天标准会员服务', 29.00, 1, 29.00, 2),
('标准会员订阅', '30天标准会员服务', 29.00, 1, 29.00, 3),
('专业会员订阅', '30天专业会员服务', 99.00, 1, 99.00, 4),
('专业会员订阅', '30天专业会员服务', 99.00, 1, 99.00, 5),
('标准会员订阅', '30天标准会员服务', 29.00, 1, 29.00, 6),
('标准会员订阅', '30天标准会员服务', 29.00, 1, 29.00, 7),
('专业会员订阅', '30天专业会员服务', 99.00, 1, 99.00, 8),
('标准会员订阅', '30天标准会员服务', 29.00, 1, 29.00, 9),
('标准会员订阅', '30天标准会员服务', 29.00, 1, 29.00, 10),
('视频生成服务包', '包含10次视频生成', 199.00, 1, 199.00, 11),
('高级视频编辑', '专业级视频编辑服务', 99.00, 1, 99.00, 12),
('企业会员订阅', '30天企业会员服务', 299.00, 1, 299.00, 13),
('基础视频生成', '单次视频生成服务', 49.00, 1, 49.00, 14),
('专业视频制作', '定制化视频制作', 149.00, 1, 149.00, 15);
-- 更新现有用户的积分(如果还没有设置) -- 支付数据
UPDATE users SET points = 50 WHERE points IS NULL OR points = 0; INSERT IGNORE INTO payments (order_id, amount, currency, payment_method, status, description, user_id, created_at, paid_at) VALUES
('ORD20240101001', 99.00, 'CNY', 'ALIPAY', 'COMPLETED', '专业会员订阅', 1, '2024-01-01 10:00:00', '2024-01-01 10:05:00'),
('ORD20240101002', 29.00, 'CNY', 'WECHAT', 'COMPLETED', '标准会员订阅', 2, '2024-01-01 11:00:00', '2024-01-01 11:02:00'),
('ORD20240101003', 29.00, 'CNY', 'ALIPAY', 'COMPLETED', '标准会员订阅', 3, '2024-01-01 12:00:00', '2024-01-01 12:03:00'),
('ORD20240101004', 99.00, 'CNY', 'WECHAT', 'COMPLETED', '专业会员订阅', 4, '2024-01-01 13:00:00', '2024-01-01 13:04:00'),
('ORD20240101005', 99.00, 'CNY', 'ALIPAY', 'COMPLETED', '专业会员订阅', 5, '2024-01-01 14:00:00', '2024-01-01 14:05:00'),
('ORD20240101006', 29.00, 'CNY', 'WECHAT', 'COMPLETED', '标准会员订阅', 6, '2024-01-01 15:00:00', '2024-01-01 15:02:00'),
('ORD20240101007', 29.00, 'CNY', 'ALIPAY', 'COMPLETED', '标准会员订阅', 7, '2024-01-01 16:00:00', '2024-01-01 16:03:00'),
('ORD20240101008', 99.00, 'CNY', 'WECHAT', 'COMPLETED', '专业会员订阅', 8, '2024-01-01 17:00:00', '2024-01-01 17:04:00'),
('ORD20240101009', 29.00, 'CNY', 'ALIPAY', 'COMPLETED', '标准会员订阅', 9, '2024-01-01 18:00:00', '2024-01-01 18:02:00'),
('ORD20240101010', 29.00, 'CNY', 'WECHAT', 'COMPLETED', '标准会员订阅', 10, '2024-01-01 19:00:00', '2024-01-01 19:03:00'),
('ORD20240102001', 199.00, 'CNY', 'ALIPAY', 'PENDING', '视频生成服务包', 1, '2024-01-02 09:00:00', NULL),
('ORD20240102002', 99.00, 'CNY', 'WECHAT', 'PROCESSING', '高级视频编辑', 2, '2024-01-02 10:00:00', NULL),
('ORD20240102003', 299.00, 'CNY', 'ALIPAY', 'COMPLETED', '企业会员订阅', 3, '2024-01-02 11:00:00', '2024-01-02 11:05:00'),
('ORD20240102004', 49.00, 'CNY', 'WECHAT', 'CANCELLED', '基础视频生成', 4, '2024-01-02 12:00:00', NULL),
('ORD20240102005', 149.00, 'CNY', 'ALIPAY', 'COMPLETED', '专业视频制作', 5, '2024-01-02 13:00:00', '2024-01-02 13:04:00');
-- 视频生成任务数据
INSERT IGNORE INTO video_tasks (task_id, user_id, task_type, title, description, input_text, status, progress, created_at, completed_at) VALUES
('TASK20240101001', 1, 'TEXT_TO_VIDEO', '产品介绍视频', '为公司新产品制作的介绍视频', '这是一款革命性的AI产品能够帮助用户快速生成高质量的视频内容...', 'COMPLETED', 100, '2024-01-01 10:00:00', '2024-01-01 10:30:00'),
('TASK20240101002', 2, 'IMAGE_TO_VIDEO', '风景动画', '将静态风景图片转换为动态视频', NULL, 'COMPLETED', 100, '2024-01-01 11:00:00', '2024-01-01 11:25:00'),
('TASK20240101003', 3, 'STORYBOARD_VIDEO', '故事板视频', '基于故事板创建的视频', '从前有一个小村庄,村民们过着平静的生活...', 'PROCESSING', 75, '2024-01-01 12:00:00', NULL),
('TASK20240101004', 4, 'TEXT_TO_VIDEO', '教育视频', '在线教育课程视频', '今天我们来学习Vue.js的基础知识...', 'COMPLETED', 100, '2024-01-01 13:00:00', '2024-01-01 13:35:00'),
('TASK20240101005', 5, 'IMAGE_TO_VIDEO', '产品展示', '产品图片转视频展示', NULL, 'COMPLETED', 100, '2024-01-01 14:00:00', '2024-01-01 14:20:00'),
('TASK20240101006', 6, 'TEXT_TO_VIDEO', '营销视频', '产品营销推广视频', '限时优惠现在购买享受8折优惠...', 'PENDING', 0, '2024-01-01 15:00:00', NULL),
('TASK20240101007', 7, 'STORYBOARD_VIDEO', '动画短片', '创意动画短片制作', '在一个遥远的星球上,住着一群可爱的小精灵...', 'COMPLETED', 100, '2024-01-01 16:00:00', '2024-01-01 16:45:00'),
('TASK20240101008', 8, 'TEXT_TO_VIDEO', '技术分享', '技术分享会视频', '今天分享的主题是微服务架构的设计原则...', 'PROCESSING', 60, '2024-01-01 17:00:00', NULL),
('TASK20240101009', 9, 'IMAGE_TO_VIDEO', '艺术创作', '艺术作品动态展示', NULL, 'COMPLETED', 100, '2024-01-01 18:00:00', '2024-01-01 18:15:00'),
('TASK20240101010', 10, 'TEXT_TO_VIDEO', '新闻播报', '新闻播报视频', '今日要闻科技公司发布最新AI技术...', 'FAILED', 0, '2024-01-01 19:00:00', NULL);
-- 用户作品数据
INSERT IGNORE INTO user_works (user_id, title, description, work_type, cover_image, video_url, tags, view_count, like_count, created_at) VALUES
(1, '产品介绍视频', '为公司新产品制作的介绍视频', 'VIDEO', '/images/covers/product_intro.jpg', '/videos/product_intro.mp4', '产品,介绍,商业', 1250, 89, '2024-01-01 10:30:00'),
(2, '风景动画', '将静态风景图片转换为动态视频', 'VIDEO', '/images/covers/landscape.jpg', '/videos/landscape.mp4', '风景,动画,自然', 890, 67, '2024-01-01 11:25:00'),
(3, '故事板视频', '基于故事板创建的视频', 'VIDEO', '/images/covers/storyboard.jpg', '/videos/storyboard.mp4', '故事,创意,动画', 2100, 156, '2024-01-01 12:30:00'),
(4, '教育视频', '在线教育课程视频', 'VIDEO', '/images/covers/education.jpg', '/videos/education.mp4', '教育,课程,学习', 3200, 234, '2024-01-01 13:35:00'),
(5, '产品展示', '产品图片转视频展示', 'VIDEO', '/images/covers/product_show.jpg', '/videos/product_show.mp4', '产品,展示,商业', 1560, 112, '2024-01-01 14:20:00'),
(6, '营销视频', '产品营销推广视频', 'VIDEO', '/images/covers/marketing.jpg', '/videos/marketing.mp4', '营销,推广,商业', 2800, 198, '2024-01-01 15:30:00'),
(7, '动画短片', '创意动画短片制作', 'VIDEO', '/images/covers/animation.jpg', '/videos/animation.mp4', '动画,创意,短片', 4500, 345, '2024-01-01 16:45:00'),
(8, '技术分享', '技术分享会视频', 'VIDEO', '/images/covers/tech_share.jpg', '/videos/tech_share.mp4', '技术,分享,编程', 1800, 134, '2024-01-01 17:30:00'),
(9, '艺术创作', '艺术作品动态展示', 'VIDEO', '/images/covers/art.jpg', '/videos/art.mp4', '艺术,创作,美学', 950, 78, '2024-01-01 18:15:00'),
(10, '新闻播报', '新闻播报视频', 'VIDEO', '/images/covers/news.jpg', '/videos/news.mp4', '新闻,播报,资讯', 1200, 89, '2024-01-01 19:30:00');
-- 系统配置数据
INSERT IGNORE INTO system_configs (config_key, config_value, description, config_type, is_public) VALUES
('site_name', 'AIGC视频生成平台', '网站名称', 'STRING', true),
('site_description', '专业的AI视频生成服务平台', '网站描述', 'STRING', true),
('max_file_size', '100', '最大文件上传大小(MB)', 'NUMBER', false),
('supported_formats', '["mp4", "avi", "mov", "wmv"]', '支持的视频格式', 'JSON', true),
('default_video_quality', '1080p', '默认视频质量', 'STRING', false),
('max_video_duration', '300', '最大视频时长(秒)', 'NUMBER', false),
('api_rate_limit', '100', 'API调用频率限制(次/小时)', 'NUMBER', false),
('maintenance_mode', 'false', '维护模式开关', 'BOOLEAN', false),
('registration_enabled', 'true', '用户注册开关', 'BOOLEAN', true),
('email_verification', 'false', '邮箱验证开关', 'BOOLEAN', false);

View File

@@ -5,7 +5,16 @@ CREATE TABLE IF NOT EXISTS users (
password_hash VARCHAR(100) NOT NULL, password_hash VARCHAR(100) NOT NULL,
role VARCHAR(30) NOT NULL DEFAULT 'ROLE_USER', role VARCHAR(30) NOT NULL DEFAULT 'ROLE_USER',
points INT NOT NULL DEFAULT 50, points INT NOT NULL DEFAULT 50,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP phone VARCHAR(20),
avatar VARCHAR(500),
nickname VARCHAR(100),
gender VARCHAR(10),
birthday DATE,
address TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
last_login_at TIMESTAMP NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
); );
CREATE TABLE IF NOT EXISTS payments ( CREATE TABLE IF NOT EXISTS payments (
@@ -62,3 +71,84 @@ CREATE TABLE IF NOT EXISTS order_items (
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
); );
-- 会员等级表
CREATE TABLE IF NOT EXISTS membership_levels (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL UNIQUE,
display_name VARCHAR(50) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL DEFAULT 0,
duration_days INT NOT NULL DEFAULT 30,
points_bonus INT NOT NULL DEFAULT 0,
features JSON,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 用户会员信息表
CREATE TABLE IF NOT EXISTS user_memberships (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
membership_level_id BIGINT NOT NULL,
start_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
end_date TIMESTAMP NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
auto_renew BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (membership_level_id) REFERENCES membership_levels(id),
UNIQUE KEY unique_active_membership (user_id, status)
);
-- 视频生成任务表
CREATE TABLE IF NOT EXISTS video_tasks (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
task_id VARCHAR(100) NOT NULL UNIQUE,
user_id BIGINT NOT NULL,
task_type VARCHAR(50) NOT NULL, -- TEXT_TO_VIDEO, IMAGE_TO_VIDEO, STORYBOARD_VIDEO
title VARCHAR(200) NOT NULL,
description TEXT,
input_text TEXT,
input_image_url VARCHAR(500),
output_video_url VARCHAR(500),
status VARCHAR(20) NOT NULL DEFAULT 'PENDING', -- PENDING, PROCESSING, COMPLETED, FAILED
progress INT NOT NULL DEFAULT 0,
error_message TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
completed_at TIMESTAMP NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- 用户作品表
CREATE TABLE IF NOT EXISTS user_works (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
title VARCHAR(200) NOT NULL,
description TEXT,
work_type VARCHAR(50) NOT NULL, -- VIDEO, IMAGE, STORYBOARD
cover_image VARCHAR(500),
video_url VARCHAR(500),
tags VARCHAR(500),
is_public BOOLEAN NOT NULL DEFAULT TRUE,
view_count INT NOT NULL DEFAULT 0,
like_count INT NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- 系统配置表
CREATE TABLE IF NOT EXISTS system_configs (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
config_key VARCHAR(100) NOT NULL UNIQUE,
config_value TEXT,
description VARCHAR(500),
config_type VARCHAR(50) NOT NULL DEFAULT 'STRING', -- STRING, NUMBER, BOOLEAN, JSON
is_public BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

View File

@@ -561,3 +561,6 @@

View File

@@ -477,3 +477,6 @@

View File

@@ -516,3 +516,6 @@