完成数据库结构升级 - 移除模拟数据,实现真实数据库集成
- 扩展数据库表结构,添加会员等级、用户会员信息、视频任务、用户作品、系统配置等表 - 更新用户表,添加手机号、头像、昵称、性别、生日、地址等字段 - 创建完整的初始数据,包含10个用户、3个会员等级、15个订单、10个视频任务、10个用户作品 - 实现会员管理API控制器,支持CRUD操作和批量操作 - 创建会员等级和用户会员信息实体类及仓库接口 - 更新前端会员管理页面,集成真实API调用,保留模拟数据作为后备 - 实现编辑功能,支持修改用户名、会员等级、资源点、到期时间等信息
This commit is contained in:
@@ -316,3 +316,6 @@ ALTER TABLE payments ADD FOREIGN KEY (order_id_ref) REFERENCES orders(id);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -23,3 +23,6 @@ public class PasswordChecker {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -280,3 +280,6 @@ Vue.js 前端项目迁移已经完成,实现了:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -423,3 +423,6 @@ MIT License
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -19,3 +19,6 @@ console.log('App.vue 加载成功')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
44
demo/frontend/src/api/members.js
Normal file
44
demo/frontend/src/api/members.js
Normal 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'
|
||||
})
|
||||
}
|
||||
@@ -60,3 +60,6 @@ export const getOrderStats = () => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -82,3 +82,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -779,3 +779,6 @@ const startGenerate = () => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
<div class="table-toolbar">
|
||||
<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="professional" />
|
||||
<el-option label="标准会员" value="standard" />
|
||||
@@ -145,6 +145,53 @@
|
||||
</div>
|
||||
</section>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
@@ -152,6 +199,7 @@
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import * as memberAPI from '@/api/members'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -162,19 +210,38 @@ const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const totalMembers = ref(50)
|
||||
|
||||
// 模拟会员数据
|
||||
const memberList = ref([
|
||||
{ id: 1001, username: 'Apple', level: '专业会员', points: 1234, expiryDate: '2025-12-31' },
|
||||
{ id: 1002, username: 'Apple', level: '标准会员', points: 500, expiryDate: '2025-12-31' },
|
||||
{ id: 1003, username: 'Apple', level: '标准会员', points: 789, expiryDate: '2025-12-31' },
|
||||
{ id: 1004, username: 'Apple', level: '标准会员', points: 2342, expiryDate: '2025-12-31' },
|
||||
{ id: 1005, username: 'Samsung', level: '标准会员', points: 90, expiryDate: '2025-12-31' },
|
||||
{ id: 1006, username: 'Samsung', level: '专业会员', points: 3456, expiryDate: '2025-12-31' },
|
||||
{ id: 1007, username: 'Apple', level: '专业会员', points: 1200, expiryDate: '2025-12-31' },
|
||||
{ id: 1008, username: 'Apple', level: '专业会员', points: 800, expiryDate: '2025-12-31' },
|
||||
{ id: 1009, username: 'Apple', level: '专业会员', points: 1500, expiryDate: '2025-12-31' },
|
||||
{ id: 1010, username: 'Apple', level: '专业会员', points: 2000, expiryDate: '2025-12-31' }
|
||||
])
|
||||
// 编辑相关状态
|
||||
const editDialogVisible = ref(false)
|
||||
const editFormRef = ref()
|
||||
const saveLoading = ref(false)
|
||||
const editForm = ref({
|
||||
id: '',
|
||||
username: '',
|
||||
level: '',
|
||||
points: 0,
|
||||
expiryDate: ''
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
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 = () => {
|
||||
@@ -237,21 +304,72 @@ const toggleMemberSelection = (member) => {
|
||||
const prevPage = () => {
|
||||
if (currentPage.value > 1) {
|
||||
currentPage.value--
|
||||
loadMembers()
|
||||
}
|
||||
}
|
||||
|
||||
const nextPage = () => {
|
||||
if (currentPage.value < totalPages.value) {
|
||||
currentPage.value++
|
||||
loadMembers()
|
||||
}
|
||||
}
|
||||
|
||||
const goToPage = (page) => {
|
||||
currentPage.value = page
|
||||
loadMembers()
|
||||
}
|
||||
|
||||
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) => {
|
||||
@@ -265,38 +383,139 @@ const deleteMember = async (member) => {
|
||||
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('删除成功')
|
||||
} catch {
|
||||
ElMessage.info('已取消删除')
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除失败:', error)
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const deleteSelected = async () => {
|
||||
if (selectedMembers.value.length === 0) {
|
||||
ElMessage.warning('请先选择要删除的会员')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除选中的 ${selectedMembers.value.length} 个用户吗?`,
|
||||
'确认删除',
|
||||
`确定要删除选中的 ${selectedMembers.value.length} 个会员吗?`,
|
||||
'批量删除',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
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 = []
|
||||
} catch {
|
||||
ElMessage.info('已取消删除')
|
||||
|
||||
ElMessage.success('批量删除成功')
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('批量删除失败:', error)
|
||||
ElMessage.error('批量删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handlePageChange = (page) => {
|
||||
currentPage.value = page
|
||||
// 这里应该重新加载数据
|
||||
ElMessage.info(`切换到第 ${page} 页`)
|
||||
// 监听筛选条件变化
|
||||
const handleLevelChange = () => {
|
||||
currentPage.value = 1
|
||||
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(() => {
|
||||
// 初始化数据
|
||||
loadMembers()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -199,3 +199,6 @@ onMounted(async () => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -252,3 +252,6 @@ const handleSubmit = async () => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -11,3 +11,6 @@ console.log('测试页面加载成功')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -730,3 +730,6 @@ const startGenerate = () => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
145
demo/src/main/java/com/example/demo/model/MembershipLevel.java
Normal file
145
demo/src/main/java/com/example/demo/model/MembershipLevel.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -44,9 +44,36 @@ public class User {
|
||||
@Column(nullable = false)
|
||||
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)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = LocalDateTime.now();
|
||||
@@ -107,6 +134,78 @@ public class User {
|
||||
public void setCreatedAt(LocalDateTime 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
118
demo/src/main/java/com/example/demo/model/UserMembership.java
Normal file
118
demo/src/main/java/com/example/demo/model/UserMembership.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -25,3 +25,6 @@ public class PlainTextPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,19 +1,124 @@
|
||||
-- 示例用户数据
|
||||
-- 用户名: demo, 密码: demo
|
||||
INSERT IGNORE INTO users (username, email, password_hash, role, points) VALUES ('demo', 'demo@example.com', 'demo', 'ROLE_USER', 100);
|
||||
-- 用户数据
|
||||
INSERT IGNORE INTO users (username, email, password_hash, role, points, phone, nickname, gender, birthday, address) VALUES
|
||||
('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);
|
||||
@@ -5,7 +5,16 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
password_hash VARCHAR(100) NOT NULL,
|
||||
role VARCHAR(30) NOT NULL DEFAULT 'ROLE_USER',
|
||||
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 (
|
||||
@@ -62,3 +71,84 @@ CREATE TABLE IF NOT EXISTS order_items (
|
||||
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
|
||||
);
|
||||
|
||||
|
||||
@@ -561,3 +561,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -477,3 +477,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -516,3 +516,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user