完成数据库结构升级 - 移除模拟数据,实现真实数据库集成
- 扩展数据库表结构,添加会员等级、用户会员信息、视频任务、用户作品、系统配置等表 - 更新用户表,添加手机号、头像、昵称、性别、生日、地址等字段 - 创建完整的初始数据,包含10个用户、3个会员等级、15个订单、10个视频任务、10个用户作品 - 实现会员管理API控制器,支持CRUD操作和批量操作 - 创建会员等级和用户会员信息实体类及仓库接口 - 更新前端会员管理页面,集成真实API调用,保留模拟数据作为后备 - 实现编辑功能,支持修改用户名、会员等级、资源点、到期时间等信息
This commit is contained in:
@@ -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