📋 CanvasChain 第1周 - 基础架构搭建详细设计文档

📊 1. 项目架构总览

第一周-2e2d968c

🗂️ 2. Maven 多模块项目结构设计

canvas-chain/
│
├── canvas-chain-common/                    # 【模块1】通用模块
│   ├── pom.xml
│   ├── src/main/java/
│   │   ├── com/canvas/chain/
│   │   │   ├── entity/                    # ✅ 核心实体类
│   │   │   │   ├── User.java
│   │   │   │   ├── Artwork.java
│   │   │   │   ├── Auction.java
│   │   │   │   ├── Transaction.java
│   │   │   │   ├── Vote.java
│   │   │   │   ├── Wallet.java
│   │   │   │   ├── BlindBox.java
│   │   │   │   └── NFT.java
│   │   │   │
│   │   │   ├── dto/                      # ✅ 数据传输对象
│   │   │   │   ├── request/
│   │   │   │   │   ├── LoginRequest.java
│   │   │   │   │   ├── RegisterRequest.java
│   │   │   │   │   └── ...
│   │   │   │   └── response/
│   │   │   │       ├── LoginResponse.java
│   │   │   │       ├── UserResponse.java
│   │   │   │       └── ...
│   │   │   │
│   │   │   ├── enums/                    # ✅ 枚举类
│   │   │   │   ├── UserRole.java
│   │   │   │   ├── AuctionStatus.java
│   │   │   │   ├── TransactionStatus.java
│   │   │   │   └── ...
│   │   │   │
│   │   │   ├── util/                     # ✅ 工具类
│   │   │   │   ├── IdGenerator.java      # ID 生成
│   │   │   │   ├── JwtUtil.java          # JWT 工具
│   │   │   │   ├── EncryptUtil.java      # 加密工具
│   │   │   │   └── DateUtil.java
│   │   │   │
│   │   │   ├── exception/                # ✅ 异常定义
│   │   │   │   ├── BusinessException.java
│   │   │   │   ├── AuthException.java
│   │   │   │   └── ...
│   │   │   │
│   │   │   ├── config/                   # ✅ 公共配置
│   │   │   │   ├── JacksonConfig.java
│   │   │   │   └── RestTemplateConfig.java
│   │   │   │
│   │   │   └── constant/                 # ✅ 常量定义
│   │   │       ├── SystemConstant.java
│   │   │       └── ErrorCode.java
│   │   │
│   │   └── resources/
│   │       └── application-common.yml    # 通用配置
│   │
│   └── pom.xml
│
├── canvas-chain-gateway/                  # 【模块2】API 网关
│   ├── pom.xml
│   ├── src/main/java/
│   │   └── com/canvas/chain/gateway/
│   │       ├── config/
│   │       │   └── NacosConfig.java      # Nacos 动态配置
│   │       ├── filter/
│   │       │   ├── AuthFilter.java       # 认证过滤器
│   │       │   └── LoggingFilter.java    # 日志过滤器
│   │       ├── handler/
│   │       │   └── GlobalExceptionHandler.java
│   │       └── Application.java
│   │
│   ├── resources/
│   │   └── application.yml
│   │
│   └── Dockerfile
│
├── canvas-chain-user-service/             # 【模块3】用户服务
│   ├── pom.xml
│   ├── src/main/java/
│   │   └── com/canvas/chain/user/
│   │       ├── controller/
│   │       │   └── UserController.java   # ✅ 用户接口
│   │       ├── service/
│   │       │   ├── UserService.java      # 接口
│   │       │   └── impl/
│   │       │       └── UserServiceImpl.java # 实现
│   │       ├── mapper/
│   │       │   └── UserMapper.java       # MyBatis mapper
│   │       ├── entity/
│   │       │   └── User.java
│   │       ├── config/
│   │       │   ├── MybatisPlusConfig.java
│   │       │   └── DataSourceConfig.java
│   │       └── Application.java
│   │
│   ├── resources/
│   │   ├── application.yml
│   │   └── application-dev.yml
│   │
│   └── Dockerfile
│
├── canvas-chain-artwork-service/          # 【模块4】创意服务
│   ├── pom.xml
│   ├── src/main/java/
│   │   └── com/canvas/chain/artwork/
│   │       ├── controller/
│   │       ├── service/
│   │       ├── mapper/
│   │       ├── entity/
│   │       └── Application.java
│   │
│   ├── resources/
│   │   └── application.yml
│   │
│   └── Dockerfile
│
├── canvas-chain-auction-service/          # 【模块5】拍卖服务
│   ├── pom.xml
│   ├── src/main/java/
│   │   └── com/canvas/chain/auction/
│   │       ├── controller/
│   │       ├── service/
│   │       ├── mapper/
│   │       ├── entity/
│   │       └── Application.java
│   │
│   ├── resources/
│   │   └── application.yml
│   │
│   └── Dockerfile
│
├── docs/                                  # 文档
│   ├── database/
│   │   ├── schema.sql                    # ✅ 数据库设计
│   │   └── init.sql
│   ├── api/
│   │   └── openapi.yaml
│   └── architecture.md
│
├── docker-compose.yml                    # ✅ 容器编排
├── .gitignore
├── README.md
└── pom.xml                               # 父 pom

🏗️ 3. 核心 POM 文件设计

3.1 父 pom.xml - 版本统一管理

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.canvas.chain</groupId>
    <artifactId>canvas-chain</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>

    <name>CanvasChain - 创意交易所平台</name>
    <description>Web3 时代的创意交易平台</description>

    <!-- 子模块 -->
    <modules>
        <module>canvas-chain-common</module>
        <module>canvas-chain-gateway</module>
        <module>canvas-chain-user-service</module>
        <module>canvas-chain-artwork-service</module>
        <module>canvas-chain-auction-service</module>
    </modules>

    <!-- 版本管理 -->
    <properties>
        <java.version>11</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <!-- Spring Cloud 版本 -->
        <spring.cloud.version>2021.0.3</spring.cloud.version>
        <spring.boot.version>2.6.3</spring.boot.version>

        <!-- 阿里 -->
        <alibaba.cloud.version>2021.0.3.0</alibaba.cloud.version>
        <alibaba.nacos.version>2.0.4</alibaba.nacos.version>

        <!-- 数据库相关 -->
        <mybatis.plus.version>3.5.1</mybatis.plus.version>
        <mybatis.version>3.5.9</mybatis.version>
        <mysql.version>8.0.28</mysql.version>

        <!-- Redis -->
        <redis.version>2.6.3</redis.version>

        <!-- JWT -->
        <jjwt.version>0.11.5</jjwt.version>

        <!-- 工具类 -->
        <hutool.version>5.8.1</hutool.version>
        <lombok.version>1.18.22</lombok.version>

        <!-- 序列化 -->
        <fastjson.version>1.2.79</fastjson.version>
        <jackson.version>2.13.1</jackson.version>

        <!-- 日志 -->
        <log4j.version>2.17.1</log4j.version>
    </properties>

    <!-- 依赖版本管理 -->
    <dependencyManagement>
        <dependencies>
            <!-- Spring Cloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Boot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- 阿里 Cloud -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${alibaba.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Nacos -->
            <dependency>
                <groupId>com.alibaba.nacos</groupId>
                <artifactId>nacos-client</artifactId>
                <version>${alibaba.nacos.version}</version>
            </dependency>

            <!-- MyBatis Plus -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis.plus.version}</version>
            </dependency>

            <!-- MySQL -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>

            <!-- JWT -->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-api</artifactId>
                <version>${jjwt.version}</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-impl</artifactId>
                <version>${jjwt.version}</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-jackson</artifactId>
                <version>${jjwt.version}</version>
                <scope>runtime</scope>
            </dependency>

            <!-- Hutool -->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>

            <!-- Lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <optional>true</optional>
            </dependency>

            <!-- FastJSON -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <!-- 构建插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring.boot.version}</version>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

3.2 通用模块 pom.xml - canvas-chain-common

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.canvas.chain</groupId>
        <artifactId>canvas-chain</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>canvas-chain-common</artifactId>
    <name>canvas-chain-common</name>
    <description>通用模块 - 公共代码和工具</description>

    <dependencies>
        <!-- Spring Boot 基础 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Hutool 工具库 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>

        <!-- FastJSON -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

        <!-- JWT -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
</project>

3.3 用户服务 pom.xml - canvas-chain-user-service

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.canvas.chain</groupId>
        <artifactId>canvas-chain</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>canvas-chain-user-service</artifactId>
    <name>canvas-chain-user-service</name>
    <description>用户服务 - 用户管理和认证</description>

    <dependencies>
        <!-- 通用模块 -->
        <dependency>
            <groupId>com.canvas.chain</groupId>
            <artifactId>canvas-chain-common</artifactId>
            <version>1.0.0</version>
        </dependency>

        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Cloud Nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <!-- MyBatis Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <!-- MySQL -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

📊 4. 数据库设计

ER图1-31f94d2e

4.1 用户表 (user)

CREATE TABLE `user` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
  `username` VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
  `email` VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
  `password_hash` VARCHAR(255) NOT NULL COMMENT '密码哈希',
  `avatar_url` VARCHAR(255) COMMENT '头像URL',
  `bio` TEXT COMMENT '个人简介',
  `role` ENUM('CREATOR', 'COLLECTOR', 'ADMIN') DEFAULT 'COLLECTOR' COMMENT '角色',
  `status` ENUM('ACTIVE', 'INACTIVE', 'BANNED') DEFAULT 'ACTIVE' COMMENT '状态',
  `follower_count` BIGINT DEFAULT 0 COMMENT '粉丝数',
  `following_count` BIGINT DEFAULT 0 COMMENT '关注数',
  `historical_rating` DECIMAL(3,2) DEFAULT 5.00 COMMENT '历史评分',
  `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

  INDEX idx_username (username),
  INDEX idx_email (email),
  INDEX idx_status (status),
  INDEX idx_created_time (created_time)
) COMMENT='用户表';

4.2 创意作品表 (artwork)

CREATE TABLE `artwork` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '作品ID',
  `creator_id` BIGINT NOT NULL COMMENT '创作者ID',
  `title` VARCHAR(200) NOT NULL COMMENT '作品标题',
  `description` TEXT COMMENT '作品描述',
  `category` VARCHAR(50) NOT NULL COMMENT '分类',
  `cover_image_url` VARCHAR(255) COMMENT '封面图',
  `content_url` VARCHAR(255) COMMENT '内容URL(指向存储服务)',
  `content_type` ENUM('IMAGE', 'VIDEO', 'TEXT', 'CODE', 'DESIGN') COMMENT '内容类型',
  `price` DECIMAL(15,2) DEFAULT 0 COMMENT '定价',
  `status` ENUM('DRAFT', 'PUBLISHED', 'SOLD', 'REMOVED') DEFAULT 'DRAFT' COMMENT '状态',
  `view_count` BIGINT DEFAULT 0 COMMENT '浏览数',
  `like_count` BIGINT DEFAULT 0 COMMENT '点赞数',
  `rating_score` DECIMAL(3,2) DEFAULT 0 COMMENT '评分',
  `total_sales` DECIMAL(15,2) DEFAULT 0 COMMENT '总销售额',
  `settled_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '已结算收入',
  `unsettled_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '未结算收入',
  `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

  INDEX idx_creator_id (creator_id),
  INDEX idx_category (category),
  INDEX idx_status (status),
  INDEX idx_created_time (created_time),
  INDEX idx_view_count (view_count),
  FOREIGN KEY (creator_id) REFERENCES `user`(id)
) COMMENT='创意作品表';

4.3 拍卖表 (auction)

CREATE TABLE `auction` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '拍卖ID',
  `artwork_id` BIGINT NOT NULL COMMENT '作品ID',
  `start_price` DECIMAL(15,2) NOT NULL COMMENT '起价',
  `current_price` DECIMAL(15,2) DEFAULT 0 COMMENT '当前价格',
  `reserve_price` DECIMAL(15,2) COMMENT '底价',
  `highest_bidder_id` BIGINT COMMENT '最高出价者ID',
  `auction_type` ENUM('ENGLISH', 'DUTCH', 'SEALED_BID') DEFAULT 'ENGLISH' COMMENT '拍卖方式',
  `status` ENUM('PENDING', 'STARTED', 'ENDED', 'SETTLED') DEFAULT 'PENDING' COMMENT '拍卖状态',
  `start_time` TIMESTAMP COMMENT '开始时间',
  `end_time` TIMESTAMP COMMENT '结束时间',
  `bid_count` INT DEFAULT 0 COMMENT '出价次数',
  `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

  INDEX idx_artwork_id (artwork_id),
  INDEX idx_status (status),
  INDEX idx_end_time (end_time),
  INDEX idx_created_time (created_time),
  FOREIGN KEY (artwork_id) REFERENCES `artwork`(id),
  FOREIGN KEY (highest_bidder_id) REFERENCES `user`(id)
) COMMENT='拍卖表';

4.4 出价记录表 (bid)

CREATE TABLE `bid` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '出价ID',
  `auction_id` BIGINT NOT NULL COMMENT '拍卖ID',
  `bidder_id` BIGINT NOT NULL COMMENT '出价者ID',
  `bid_amount` DECIMAL(15,2) NOT NULL COMMENT '出价金额',
  `bid_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '出价时间',
  `is_highest` TINYINT(1) DEFAULT 0 COMMENT '是否最高价',

  INDEX idx_auction_id (auction_id),
  INDEX idx_bidder_id (bidder_id),
  INDEX idx_bid_time (bid_time),
  FOREIGN KEY (auction_id) REFERENCES `auction`(id),
  FOREIGN KEY (bidder_id) REFERENCES `user`(id)
) COMMENT='出价记录表';

4.5 交易表 (transaction)

CREATE TABLE `transaction` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '交易ID',
  `buyer_id` BIGINT NOT NULL COMMENT '买家ID',
  `seller_id` BIGINT NOT NULL COMMENT '卖家ID',
  `artwork_id` BIGINT NOT NULL COMMENT '作品ID',
  `transaction_type` ENUM('DIRECT_PURCHASE', 'AUCTION_WIN', 'BLINDBOX', 'COMBINED') COMMENT '交易类型',
  `transaction_amount` DECIMAL(15,2) NOT NULL COMMENT '交易金额',
  `platform_commission` DECIMAL(15,2) DEFAULT 0 COMMENT '平台抽成',
  `seller_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '卖家收入',
  `status` ENUM('PENDING', 'COMPLETED', 'FAILED', 'REFUNDED') DEFAULT 'PENDING' COMMENT '交易状态',
  `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `completed_time` TIMESTAMP COMMENT '完成时间',
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

  INDEX idx_buyer_id (buyer_id),
  INDEX idx_seller_id (seller_id),
  INDEX idx_artwork_id (artwork_id),
  INDEX idx_status (status),
  INDEX idx_created_time (created_time),
  FOREIGN KEY (buyer_id) REFERENCES `user`(id),
  FOREIGN KEY (seller_id) REFERENCES `user`(id),
  FOREIGN KEY (artwork_id) REFERENCES `artwork`(id)
) COMMENT='交易表';

4.6 钱包表 (wallet)

CREATE TABLE `wallet` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '钱包ID',
  `user_id` BIGINT NOT NULL UNIQUE COMMENT '用户ID',
  `balance` DECIMAL(15,2) DEFAULT 0 COMMENT '余额',
  `locked_balance` DECIMAL(15,2) DEFAULT 0 COMMENT '冻结余额',
  `total_recharge` DECIMAL(15,2) DEFAULT 0 COMMENT '总充值',
  `total_withdraw` DECIMAL(15,2) DEFAULT 0 COMMENT '总提现',
  `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

  INDEX idx_user_id (user_id),
  FOREIGN KEY (user_id) REFERENCES `user`(id)
) COMMENT='用户钱包表';

4.7 投票表 (vote)

CREATE TABLE `vote` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '投票ID',
  `voter_id` BIGINT NOT NULL COMMENT '投票者ID',
  `artwork_id` BIGINT NOT NULL COMMENT '作品ID',
  `vote_weight` DECIMAL(5,2) DEFAULT 1.00 COMMENT '投票权重',
  `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '投票时间',

  UNIQUE KEY uk_voter_artwork (voter_id, artwork_id),
  INDEX idx_artwork_id (artwork_id),
  INDEX idx_voter_id (voter_id),
  INDEX idx_created_time (created_time),
  FOREIGN KEY (voter_id) REFERENCES `user`(id),
  FOREIGN KEY (artwork_id) REFERENCES `artwork`(id)
) COMMENT='投票表';

4.8 盲盒表 (blind_box)

CREATE TABLE `blind_box` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '盲盒ID',
  `creator_id` BIGINT NOT NULL COMMENT '创作者ID',
  `title` VARCHAR(200) NOT NULL COMMENT '盲盒标题',
  `description` TEXT COMMENT '盲盒描述',
  `cover_image_url` VARCHAR(255) COMMENT '封面图',
  `box_price` DECIMAL(15,2) NOT NULL COMMENT '盲盒价格',
  `total_boxes` INT NOT NULL COMMENT '总盲盒数',
  `remaining_boxes` INT NOT NULL COMMENT '剩余盲盒数',
  `status` ENUM('CREATED', 'ACTIVE', 'SOLD_OUT', 'CLOSED') DEFAULT 'CREATED' COMMENT '状态',
  `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

  INDEX idx_creator_id (creator_id),
  INDEX idx_status (status),
  INDEX idx_created_time (created_time),
  FOREIGN KEY (creator_id) REFERENCES `user`(id)
) COMMENT='盲盒表';

4.9 盲盒奖品表 (blind_box_prize)

CREATE TABLE `blind_box_prize` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '奖品ID',
  `blind_box_id` BIGINT NOT NULL COMMENT '盲盒ID',
  `artwork_id` BIGINT NOT NULL COMMENT '作品ID',
  `rarity` ENUM('COMMON', 'RARE', 'EPIC', 'LEGENDARY') COMMENT '稀有度',
  `probability_weight` INT DEFAULT 100 COMMENT '概率权重',
  `quantity` INT DEFAULT 1 COMMENT '数量',
  `remaining_quantity` INT COMMENT '剩余数量',

  INDEX idx_blind_box_id (blind_box_id),
  INDEX idx_artwork_id (artwork_id),
  FOREIGN KEY (blind_box_id) REFERENCES `blind_box`(id),
  FOREIGN KEY (artwork_id) REFERENCES `artwork`(id)
) COMMENT='盲盒奖品表';

4.10 盲盒开箱记录表 (blind_box_opening)

CREATE TABLE `blind_box_opening` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '开箱记录ID',
  `blind_box_id` BIGINT NOT NULL COMMENT '盲盒ID',
  `user_id` BIGINT NOT NULL COMMENT '用户ID',
  `prize_id` BIGINT NOT NULL COMMENT '获得的奖品ID',
  `artwork_id` BIGINT NOT NULL COMMENT '获得的作品ID',
  `opened_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '开箱时间',

  INDEX idx_blind_box_id (blind_box_id),
  INDEX idx_user_id (user_id),
  INDEX idx_opened_time (opened_time),
  FOREIGN KEY (blind_box_id) REFERENCES `blind_box`(id),
  FOREIGN KEY (user_id) REFERENCES `user`(id),
  FOREIGN KEY (prize_id) REFERENCES `blind_box_prize`(id),
  FOREIGN KEY (artwork_id) REFERENCES `artwork`(id)
) COMMENT='盲盒开箱记录表';

4.11 NFT 虚拟资产表 (nft)

CREATE TABLE `nft` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'NFT ID',
  `nft_token_id` VARCHAR(100) NOT NULL UNIQUE COMMENT 'NFT Token ID',
  `artwork_id` BIGINT NOT NULL COMMENT '对应作品ID',
  `owner_id` BIGINT NOT NULL COMMENT '当前所有者ID',
  `creator_id` BIGINT NOT NULL COMMENT '创作者ID',
  `metadata_uri` VARCHAR(255) COMMENT '元数据URI',
  `minting_price` DECIMAL(15,2) COMMENT '铸造价格',
  `mint_time` TIMESTAMP COMMENT '铸造时间',
  `status` ENUM('MINTED', 'LISTED', 'TRANSFERRED', 'BURNED') DEFAULT 'MINTED' COMMENT '状态',
  `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

  INDEX idx_artwork_id (artwork_id),
  INDEX idx_owner_id (owner_id),
  INDEX idx_creator_id (creator_id),
  INDEX idx_status (status),
  FOREIGN KEY (artwork_id) REFERENCES `artwork`(id),
  FOREIGN KEY (owner_id) REFERENCES `user`(id),
  FOREIGN KEY (creator_id) REFERENCES `user`(id)
) COMMENT='NFT虚拟资产表';

4.12 聚合拍卖表 (combined_auction)

CREATE TABLE `combined_auction` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '聚合拍卖ID',
  `title` VARCHAR(200) NOT NULL COMMENT '拍卖标题',
  `description` TEXT COMMENT '描述',
  `start_price` DECIMAL(15,2) NOT NULL COMMENT '起价',
  `current_price` DECIMAL(15,2) DEFAULT 0 COMMENT '当前价格',
  `highest_bidder_id` BIGINT COMMENT '最高出价者ID',
  `status` ENUM('PENDING', 'STARTED', 'ENDED', 'SETTLED') DEFAULT 'PENDING' COMMENT '状态',
  `start_time` TIMESTAMP COMMENT '开始时间',
  `end_time` TIMESTAMP COMMENT '结束时间',
  `artwork_count` INT COMMENT '作品数量',
  `bid_count` INT DEFAULT 0 COMMENT '出价次数',
  `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

  INDEX idx_status (status),
  INDEX idx_end_time (end_time),
  INDEX idx_created_time (created_time),
  FOREIGN KEY (highest_bidder_id) REFERENCES `user`(id)
) COMMENT='聚合拍卖表';

4.13 聚合拍卖作品表 (combined_auction_item)

CREATE TABLE `combined_auction_item` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '项目ID',
  `combined_auction_id` BIGINT NOT NULL COMMENT '聚合拍卖ID',
  `artwork_id` BIGINT NOT NULL COMMENT '作品ID',
  `item_weight` INT DEFAULT 100 COMMENT '项目权重',

  INDEX idx_combined_auction_id (combined_auction_id),
  INDEX idx_artwork_id (artwork_id),
  FOREIGN KEY (combined_auction_id) REFERENCES `combined_auction`(id),
  FOREIGN KEY (artwork_id) REFERENCES `artwork`(id)
) COMMENT='聚合拍卖作品表';

4.14 平台账户表 (platform_account)

CREATE TABLE `platform_account` (
  `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '账户ID',
  `account_name` VARCHAR(100) NOT NULL UNIQUE COMMENT '账户名',
  `total_commission` DECIMAL(15,2) DEFAULT 0 COMMENT '总佣金',
  `total_platform_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '总收入',
  `settled_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '已结算收入',
  `unsettled_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '未结算收入',
  `created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) COMMENT='平台账户表';

4.15 初始化脚本 (init.sql)

-- 创建数据库
CREATE DATABASE IF NOT EXISTS canvas_chain CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE canvas_chain;

-- 创建所有表...
-- (上述所有表定义)

-- 初始化数据
INSERT INTO `platform_account` (account_name, total_commission, created_time)
VALUES ('platform_main', 0, NOW());

-- 插入平台管理员用户
INSERT INTO `user` (username, email, password_hash, role, status)
VALUES ('admin', 'admin@canvas-chain.com', '$2a$10$...hashed...', 'ADMIN', 'ACTIVE');

🔐 5. 核心实体类设计

5.1 User 实体 - canvas-chain-common

package com.canvas.chain.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 密码哈希
     */
    private String passwordHash;

    /**
     * 头像URL
     */
    private String avatarUrl;

    /**
     * 个人简介
     */
    private String bio;

    /**
     * 用户角色:CREATOR(创作者)、COLLECTOR(收藏家)、ADMIN(管理员)
     */
    private String role;

    /**
     * 账户状态:ACTIVE、INACTIVE、BANNED
     */
    private String status;

    /**
     * 粉丝数
     */
    private Long followerCount;

    /**
     * 关注数
     */
    private Long followingCount;

    /**
     * 历史评分(1-5分)
     */
    private BigDecimal historicalRating;

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdTime;

    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedTime;
}

5.2 Artwork 实体

package com.canvas.chain.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("artwork")
public class Artwork implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 作品ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 创作者ID
     */
    private Long creatorId;

    /**
     * 作品标题
     */
    private String title;

    /**
     * 作品描述
     */
    private String description;

    /**
     * 分类
     */
    private String category;

    /**
     * 封面图URL
     */
    private String coverImageUrl;

    /**
     * 内容URL
     */
    private String contentUrl;

    /**
     * 内容类型:IMAGE、VIDEO、TEXT、CODE、DESIGN
     */
    private String contentType;

    /**
     * 定价
     */
    private BigDecimal price;

    /**
     * 作品状态:DRAFT、PUBLISHED、SOLD、REMOVED
     */
    private String status;

    /**
     * 浏览数
     */
    private Long viewCount;

    /**
     * 点赞数
     */
    private Long likeCount;

    /**
     * 评分
     */
    private BigDecimal ratingScore;

    /**
     * 总销售额
     */
    private BigDecimal totalSales;

    /**
     * 已结算收入
     */
    private BigDecimal settledRevenue;

    /**
     * 未结算收入
     */
    private BigDecimal unsettledRevenue;

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdTime;

    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedTime;
}

5.3 Auction 实体

package com.canvas.chain.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("auction")
public class Auction implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 拍卖ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 作品ID
     */
    private Long artworkId;

    /**
     * 起价
     */
    private BigDecimal startPrice;

    /**
     * 当前价格
     */
    private BigDecimal currentPrice;

    /**
     * 底价
     */
    private BigDecimal reservePrice;

    /**
     * 最高出价者ID
     */
    private Long highestBidderId;

    /**
     * 拍卖方式:ENGLISH、DUTCH、SEALED_BID
     */
    private String auctionType;

    /**
     * 拍卖状态:PENDING、STARTED、ENDED、SETTLED
     */
    private String status;

    /**
     * 开始时间
     */
    private LocalDateTime startTime;

    /**
     * 结束时间
     */
    private LocalDateTime endTime;

    /**
     * 出价次数
     */
    private Integer bidCount;

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdTime;

    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedTime;
}

5.4 Transaction 实体

package com.canvas.chain.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("transaction")
public class Transaction implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 交易ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 买家ID
     */
    private Long buyerId;

    /**
     * 卖家ID
     */
    private Long sellerId;

    /**
     * 作品ID
     */
    private Long artworkId;

    /**
     * 交易类型:DIRECT_PURCHASE、AUCTION_WIN、BLINDBOX、COMBINED
     */
    private String transactionType;

    /**
     * 交易金额
     */
    private BigDecimal transactionAmount;

    /**
     * 平台抽成
     */
    private BigDecimal platformCommission;

    /**
     * 卖家收入
     */
    private BigDecimal sellerRevenue;

    /**
     * 交易状态:PENDING、COMPLETED、FAILED、REFUNDED
     */
    private String status;

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdTime;

    /**
     * 完成时间
     */
    private LocalDateTime completedTime;

    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedTime;
}

5.5 Wallet 实体

package com.canvas.chain.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("wallet")
public class Wallet implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 钱包ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 余额
     */
    private BigDecimal balance;

    /**
     * 冻结余额
     */
    private BigDecimal lockedBalance;

    /**
     * 总充值
     */
    private BigDecimal totalRecharge;

    /**
     * 总提现
     */
    private BigDecimal totalWithdraw;

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdTime;

    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedTime;
}

📋 6. 枚举类定义

6.1 用户角色枚举 - UserRoleEnum.java

package com.canvas.chain.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum UserRoleEnum {

    /**
     * 创作者 - 可以发布创意作品
     */
    CREATOR("CREATOR", "创作者"),

    /**
     * 收藏家 - 可以购买和收藏作品
     */
    COLLECTOR("COLLECTOR", "收藏家"),

    /**
     * 管理员 - 系统管理员
     */
    ADMIN("ADMIN", "管理员");

    private final String code;
    private final String desc;

    public static UserRoleEnum getValue(String code) {
        for (UserRoleEnum userRole : UserRoleEnum.values()) {
            if (userRole.code.equals(code)) {
                return userRole;
            }
        }
        return null;
    }
}

6.2 用户状态枚举 - UserStatusEnum.java

package com.canvas.chain.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum UserStatusEnum {

    /**
     * 激活 - 正常使用
     */
    ACTIVE("ACTIVE", "激活"),

    /**
     * 未激活 - 等待邮箱确认
     */
    INACTIVE("INACTIVE", "未激活"),

    /**
     * 被禁用 - 账户被冻结
     */
    BANNED("BANNED", "被禁用");

    private final String code;
    private final String desc;

    public static UserStatusEnum getValue(String code) {
        for (UserStatusEnum status : UserStatusEnum.values()) {
            if (status.code.equals(code)) {
                return status;
            }
        }
        return null;
    }
}

6.3 拍卖状态枚举 - AuctionStatusEnum.java

package com.canvas.chain.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum AuctionStatusEnum {

    /**
     * 待开始
     */
    PENDING("PENDING", "待开始"),

    /**
     * 进行中
     */
    STARTED("STARTED", "进行中"),

    /**
     * 已结束
     */
    ENDED("ENDED", "已结束"),

    /**
     * 已结算
     */
    SETTLED("SETTLED", "已结算");

    private final String code;
    private final String desc;

    public static AuctionStatusEnum getValue(String code) {
        for (AuctionStatusEnum status : AuctionStatusEnum.values()) {
            if (status.code.equals(code)) {
                return status;
            }
        }
        return null;
    }
}

6.4 交易类型枚举 - TransactionTypeEnum.java

package com.canvas.chain.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum TransactionTypeEnum {

    /**
     * 直接购买
     */
    DIRECT_PURCHASE("DIRECT_PURCHASE", "直接购买"),

    /**
     * 拍卖获胜
     */
    AUCTION_WIN("AUCTION_WIN", "拍卖获胜"),

    /**
     * 盲盒获得
     */
    BLINDBOX("BLINDBOX", "盲盒获得"),

    /**
     * 组合购买
     */
    COMBINED("COMBINED", "组合购买");

    private final String code;
    private final String desc;

    public static TransactionTypeEnum getValue(String code) {
        for (TransactionTypeEnum type : TransactionTypeEnum.values()) {
            if (type.code.equals(code)) {
                return type;
            }
        }
        return null;
    }
}

6.5 交易状态枚举 - TransactionStatusEnum.java

package com.canvas.chain.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum TransactionStatusEnum {

    /**
     * 待支付
     */
    PENDING("PENDING", "待支付"),

    /**
     * 已完成
     */
    COMPLETED("COMPLETED", "已完成"),

    /**
     * 已失败
     */
    FAILED("FAILED", "已失败"),

    /**
     * 已退款
     */
    REFUNDED("REFUNDED", "已退款");

    private final String code;
    private final String desc;

    public static TransactionStatusEnum getValue(String code) {
        for (TransactionStatusEnum status : TransactionStatusEnum.values()) {
            if (status.code.equals(code)) {
                return status;
            }
        }
        return null;
    }
}

📍 7. DTO 数据传输对象设计

7.1 用户登录请求 DTO - LoginRequest.java

package com.canvas.chain.dto.request;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequest {

    /**
     * 用户名或邮箱
     */
    @NotBlank(message = "用户名或邮箱不能为空")
    private String username;

    /**
     * 密码
     */
    @NotBlank(message = "密码不能为空")
    private String password;
}

7.2 用户注册请求 DTO - RegisterRequest.java

package com.canvas.chain.dto.request;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.*;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RegisterRequest {

    /**
     * 用户名(3-20字符)
     */
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 20, message = "用户名长度需要3-20个字符")
    private String username;

    /**
     * 邮箱
     */
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;

    /**
     * 密码(至少8位,包含大小写字母和数字)
     */
    @NotBlank(message = "密码不能为空")
    @Size(min = 8, message = "密码至少需要8个字符")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d@$!%*?&]{8,}$",
            message = "密码必须包含大小写字母和数字")
    private String password;

    /**
     * 确认密码
     */
    @NotBlank(message = "确认密码不能为空")
    private String confirmPassword;

    /**
     * 用户角色
     */
    @NotBlank(message = "用户角色不能为空")
    private String role; // CREATOR 或 COLLECTOR
}

7.3 用户登录响应 DTO - LoginResponse.java

package com.canvas.chain.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginResponse {

    /**
     * JWT 令牌
     */
    private String token;

    /**
     * 刷新令牌
     */
    private String refreshToken;

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 用户名
     */
    private String username;

    /**
     * 用户邮箱
     */
    private String email;

    /**
     * 用户角色
     */
    private String role;

    /**
     * 令牌有效期(秒)
     */
    private Long expiresIn;
}

7.4 用户信息响应 DTO - UserResponse.java

package com.canvas.chain.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserResponse {

    /**
     * 用户ID
     */
    private Long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 头像URL
     */
    private String avatarUrl;

    /**
     * 个人简介
     */
    private String bio;

    /**
     * 用户角色
     */
    private String role;

    /**
     * 账户状态
     */
    private String status;

    /**
     * 粉丝数
     */
    private Long followerCount;

    /**
     * 关注数
     */
    private Long followingCount;

    /**
     * 历史评分
     */
    private BigDecimal historicalRating;

    /**
     * 创建时间
     */
    private LocalDateTime createdTime;
}

📋 7. DTO 数据传输对象设计(续)

7.5 通用响应包装 DTO - ApiResponse.java

package com.canvas.chain.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 响应码
     */
    private String code;

    /**
     * 响应消息
     */
    private String message;

    /**
     * 响应数据
     */
    private T data;

    /**
     * 时间戳
     */
    private Long timestamp;

    /**
     * 成功响应
     */
    public static <T> ApiResponse<T> success(T data) {
        return ApiResponse.<T>builder()
            .code("200")
            .message("成功")
            .data(data)
            .timestamp(System.currentTimeMillis())
            .build();
    }

    /**
     * 成功响应(带消息)
     */
    public static <T> ApiResponse<T> success(String message, T data) {
        return ApiResponse.<T>builder()
            .code("200")
            .message(message)
            .data(data)
            .timestamp(System.currentTimeMillis())
            .build();
    }

    /**
     * 失败响应
     */
    public static <T> ApiResponse<T> fail(String code, String message) {
        return ApiResponse.<T>builder()
            .code(code)
            .message(message)
            .data(null)
            .timestamp(System.currentTimeMillis())
            .build();
    }

    /**
     * 失败响应(带数据)
     */
    public static <T> ApiResponse<T> fail(String code, String message, T data) {
        return ApiResponse.<T>builder()
            .code(code)
            .message(message)
            .data(data)
            .timestamp(System.currentTimeMillis())
            .build();
    }
}

7.6 分页响应 DTO - PageResponse.java

package com.canvas.chain.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PageResponse<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 当前页码
     */
    private Long current;

    /**
     * 每页数量
     */
    private Long size;

    /**
     * 总记录数
     */
    private Long total;

    /**
     * 总页数
     */
    private Long pages;

    /**
     * 数据列表
     */
    private List<T> records;

    /**
     * 是否有下一页
     */
    private Boolean hasNext;

    /**
     * 是否有上一页
     */
    private Boolean hasPrevious;
}

🛠️ 8. 工具类设计

8.1 JWT 工具类 - JwtUtil.java

package com.canvas.chain.util;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
public class JwtUtil {

    /**
     * JWT 秘钥(最少256位)
     */
    @Value("${jwt.secret:canvas-chain-secret-key-minimum-256-bits-long}")
    private String secret;

    /**
     * JWT 过期时间(单位:秒,默认24小时)
     */
    @Value("${jwt.expiration:86400}")
    private Long expiration;

    /**
     * 刷新令牌过期时间(单位:秒,默认7天)
     */
    @Value("${jwt.refresh-expiration:604800}")
    private Long refreshExpiration;

    /**
     * 生成 JWT Token
     */
    public String generateToken(Long userId, String username, String role) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        claims.put("username", username);
        claims.put("role", role);
        return createToken(claims, username, expiration);
    }

    /**
     * 生成刷新令牌
     */
    public String generateRefreshToken(Long userId, String username) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        claims.put("type", "refresh");
        return createToken(claims, username, refreshExpiration);
    }

    /**
     * 创建 Token
     */
    private String createToken(Map<String, Object> claims,
                              String subject,
                              Long expiration) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration * 1000);

        return Jwts.builder()
            .setClaims(claims)
            .setSubject(subject)
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            .signWith(Keys.hmacShaKeyFor(
                secret.getBytes(StandardCharsets.UTF_8)),
                SignatureAlgorithm.HS256)
            .compact();
    }

    /**
     * 从 Token 中获取用户ID
     */
    public Long getUserIdFromToken(String token) {
        Claims claims = getAllClaimsFromToken(token);
        return claims.get("userId", Long.class);
    }

    /**
     * 从 Token 中获取用户名
     */
    public String getUsernameFromToken(String token) {
        Claims claims = getAllClaimsFromToken(token);
        return claims.getSubject();
    }

    /**
     * 从 Token 中获取角色
     */
    public String getRoleFromToken(String token) {
        Claims claims = getAllClaimsFromToken(token);
        return claims.get("role", String.class);
    }

    /**
     * 获取所有声明
     */
    private Claims getAllClaimsFromToken(String token) {
        try {
            return Jwts.parserBuilder()
                .setSigningKey(Keys.hmacShaKeyFor(
                    secret.getBytes(StandardCharsets.UTF_8)))
                .build()
                .parseClaimsJws(token)
                .getBody();
        } catch (SecurityException e) {
            log.error("Invalid JWT signature: {}", e.getMessage());
            throw new JwtException("Invalid JWT signature", e);
        } catch (MalformedJwtException e) {
            log.error("Invalid JWT token: {}", e.getMessage());
            throw new JwtException("Invalid JWT token", e);
        } catch (ExpiredJwtException e) {
            log.error("Expired JWT token: {}", e.getMessage());
            throw new JwtException("Expired JWT token", e);
        } catch (UnsupportedJwtException e) {
            log.error("Unsupported JWT token: {}", e.getMessage());
            throw new JwtException("Unsupported JWT token", e);
        } catch (IllegalArgumentException e) {
            log.error("JWT claims string is empty: {}", e.getMessage());
            throw new JwtException("JWT claims string is empty", e);
        }
    }

    /**
     * 验证 Token 是否有效
     */
    public Boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                .setSigningKey(Keys.hmacShaKeyFor(
                    secret.getBytes(StandardCharsets.UTF_8)))
                .build()
                .parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            log.error("JWT validation failed: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 获取 Token 过期时间
     */
    public Date getExpirationDateFromToken(String token) {
        Claims claims = getAllClaimsFromToken(token);
        return claims.getExpiration();
    }

    /**
     * 判断 Token 是否过期
     */
    private Boolean isTokenExpired(String token) {
        try {
            Date expiration = getExpirationDateFromToken(token);
            return expiration.before(new Date());
        } catch (ExpiredJwtException e) {
            return true;
        }
    }
}

8.2 密码加密工具类 - EncryptUtil.java

package com.canvas.chain.util;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class EncryptUtil {

    private static final BCryptPasswordEncoder passwordEncoder =
        new BCryptPasswordEncoder();

    /**
     * 密码加密
     */
    public static String encodePassword(String password) {
        return passwordEncoder.encode(password);
    }

    /**
     * 密码验证
     */
    public static Boolean matchPassword(String rawPassword,
                                        String encodedPassword) {
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }
}

8.3 ID 生成工具类 - IdGenerator.java

package com.canvas.chain.util;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import org.springframework.stereotype.Component;

@Component
public class IdGenerator {

    private static final Snowflake snowflake = IdUtil.getSnowflake(1, 1);

    /**
     * 生成分布式ID(雪花算法)
     */
    public static Long nextId() {
        return snowflake.nextId();
    }

    /**
     * 生成 UUID
     */
    public static String nextUUID() {
        return IdUtil.simpleUUID();
    }

    /**
     * 生成 NFT Token ID
     */
    public static String generateNFTTokenId() {
        return "nft_" + System.currentTimeMillis() + "_" + IdUtil.simpleUUID();
    }
}

8.4 日期工具类 - DateUtil.java

package com.canvas.chain.util;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;

public class DateUtil {

    private static final DateTimeFormatter DEFAULT_FORMATTER =
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * 获取当前时间戳(毫秒)
     */
    public static Long getCurrentTimeMillis() {
        return System.currentTimeMillis();
    }

    /**
     * 获取当前时间戳(秒)
     */
    public static Long getCurrentTimeSeconds() {
        return System.currentTimeMillis() / 1000;
    }

    /**
     * LocalDateTime 转换为字符串
     */
    public static String format(LocalDateTime dateTime) {
        return dateTime.format(DEFAULT_FORMATTER);
    }

    /**
     * LocalDateTime 转换为 Date
     */
    public static Date toDate(LocalDateTime dateTime) {
        return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * Date 转换为 LocalDateTime
     */
    public static LocalDateTime toLocalDateTime(Date date) {
        return date.toInstant()
            .atZone(ZoneId.systemDefault())
            .toLocalDateTime();
    }
}

9. 异常处理设计

9.1 业务异常基类 - BusinessException.java

package com.canvas.chain.exception;

import lombok.Getter;

@Getter
public class BusinessException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    /**
     * 错误码
     */
    private String code;

    /**
     * 错误消息
     */
    private String message;

    public BusinessException(String message) {
        super(message);
        this.code = "500";
        this.message = message;
    }

    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
        this.message = message;
    }

    public BusinessException(String code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
        this.message = message;
    }
}

9.2 认证异常 - AuthException.java

package com.canvas.chain.exception;

public class AuthException extends BusinessException {

    private static final long serialVersionUID = 1L;

    public AuthException(String message) {
        super("401", message);
    }

    public AuthException(String code, String message) {
        super(code, message);
    }
}

9.3 参数验证异常 - ValidationException.java

package com.canvas.chain.exception;

public class ValidationException extends BusinessException {

    private static final long serialVersionUID = 1L;

    public ValidationException(String message) {
        super("400", message);
    }
}

9.4 资源不存在异常 - ResourceNotFoundException.java

package com.canvas.chain.exception;

public class ResourceNotFoundException extends BusinessException {

    private static final long serialVersionUID = 1L;

    public ResourceNotFoundException(String message) {
        super("404", message);
    }
}

9.5 全局异常处理 - GlobalExceptionHandler.java

package com.canvas.chain.config;

import com.canvas.chain.dto.response.ApiResponse;
import com.canvas.chain.exception.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.stream.Collectors;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseStatus(HttpStatus.OK)
    public ApiResponse<?> handleBusinessException(BusinessException e) {
        log.error("业务异常: {}", e.getMessage(), e);
        return ApiResponse.fail(e.getCode(), e.getMessage());
    }

    /**
     * 处理认证异常
     */
    @ExceptionHandler(AuthException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ApiResponse<?> handleAuthException(AuthException e) {
        log.error("认证异常: {}", e.getMessage(), e);
        return ApiResponse.fail(e.getCode(), e.getMessage());
    }

    /**
     * 处理资源不存在异常
     */
    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ApiResponse<?> handleResourceNotFoundException(
            ResourceNotFoundException e) {
        log.error("资源不存在异常: {}", e.getMessage(), e);
        return ApiResponse.fail(e.getCode(), e.getMessage());
    }

    /**
     * 处理参数验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiResponse<?> handleValidationException(
            MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        String errorMessage = bindingResult.getFieldErrors()
            .stream()
            .map(error -> error.getField() + ": " +
                error.getDefaultMessage())
            .collect(Collectors.joining("; "));

        log.error("参数验证异常: {}", errorMessage);
        return ApiResponse.fail("400", errorMessage);
    }

    /**
     * 处理未知异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ApiResponse<?> handleException(Exception e) {
        log.error("系统异常: {}", e.getMessage(), e);
        return ApiResponse.fail("500", "系统内部错误");
    }
}

⚙️ 10. 配置类设计

10.1 MyBatis Plus 配置 - MybatisPlusConfig.java

package com.canvas.chain.user.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {

    /**
     * 分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(
            new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

10.2 Web MVC 配置 - WebMvcConfig.java

package com.canvas.chain.user.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 配置消息转换器
     */
    @Override
    public void configureMessageConverters(
            List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter converter =
            new MappingJackson2HttpMessageConverter();
        converter.setSupportedMediaTypes(List.of(
            MediaType.APPLICATION_JSON,
            MediaType.APPLICATION_JSON_UTF8
        ));
        converters.add(0, converter);
    }
}

10.3 REST 模板配置 - RestTemplateConfig.java

package com.canvas.chain.common.config;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder
            .setConnectTimeout(Duration.ofSeconds(5))
            .setReadTimeout(Duration.ofSeconds(10))
            .build();
    }
}

📝 11. application.yml 配置文件

11.1 通用配置 - canvas-chain-common/resources/application-common.yml

# 公共配置
spring:
  application:
    name: canvas-chain
  jackson:
    default-property-inclusion: non_null
    serialization:
      write-dates-as-timestamps: false
      indent-output: true
  mvc:
    throw-exception-if-no-handler-found: true
  web:
    resources:
      add-mappings: false

# JWT 配置
jwt:
  secret: canvas-chain-secret-key-minimum-256-bits-long-for-security
  expiration: 86400          # 24 小时
  refresh-expiration: 604800 # 7 天

# 日志配置
logging:
  level:
    root: INFO
    com.canvas.chain: DEBUG
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/canvas-chain.log
    max-size: 10MB
    max-history: 30

11.2 用户服务配置 - canvas-chain-user-service/resources/application.yml

spring:
  application:
    name: canvas-user-service
  profiles:
    active: dev
  cloud:
    nacos:
      discovery:
        server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
        namespace: public
        group: DEFAULT_GROUP
      config:
        server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
        namespace: public
        group: DEFAULT_GROUP
        file-extension: yml
        prefix: ${spring.application.name}
  datasource:
    url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/canvas_chain?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai
    username: ${MYSQL_USER:root}
    password: ${MYSQL_PASSWORD:root123456}
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
  redis:
    host: ${REDIS_HOST:localhost}
    port: ${REDIS_PORT:6379}
    password: ${REDIS_PASSWORD:}
    timeout: 60000ms
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
  jpa:
    show-sql: false
    hibernate:
      ddl-auto: validate

# MyBatis Plus 配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
  mapper-locations: classpath*:mapper/**/*.xml
  type-aliases-package: com.canvas.chain.entity
  global-config:
    banner: false
    db-config:
      id-type: auto
      table-underline: true
      logic-delete-value: 1
      logic-not-delete-value: 0

# 服务器配置
server:
  port: 8001
  servlet:
    context-path: /
  tomcat:
    threads:
      max: 200
      min-spare: 10

# 平台信息
platform:
  commission-rate: 0.10  # 平台抽成 10%
  min-withdraw: 100      # 最低提现金额
  max-withdraw: 100000   # 最高提现金额

11.3 网关配置 - canvas-chain-gateway/resources/application.yml

spring:
  application:
    name: canvas-gateway
  cloud:
    gateway:
      routes:
        # 用户服务路由
        - id: user-service
          uri: lb://canvas-user-service
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                key-resolver: "#{@userKeyResolver}"
                redis-rate-limiter.replenish-rate: 100
                redis-rate-limiter.requested-tokens: 1

        # 创意服务路由
        - id: artwork-service
          uri: lb://canvas-artwork-service
          predicates:
            - Path=/api/artwork/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                key-resolver: "#{@userKeyResolver}"
                redis-rate-limiter.replenish-rate: 50
                redis-rate-limiter.requested-tokens: 1

      globalcors:
        cors-configurations:
          '[/**]':
            allow-credentials: true
            allowed-headers: "*"
            allowed-methods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allowed-origins: "*"
            max-age: 3600

    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: public
        group: DEFAULT_GROUP

server:
  port: 9000

logging:
  level:
    org.springframework.cloud.gateway: DEBUG

📌 12. 常量定义

12.1 系统常量 - SystemConstant.java

package com.canvas.chain.constant;

public class SystemConstant {

    /**
     * 平台名称
     */
    public static final String PLATFORM_NAME = "CanvasChain";

    /**
     * 平台版本
     */
    public static final String PLATFORM_VERSION = "1.0.0";

    /**
     * JWT Token 前缀
     */
    public static final String TOKEN_HEADER = "Authorization";

    /**
     * JWT Token 类型
     */
    public static final String TOKEN_TYPE = "Bearer";

    /**
     * 默认头像
     */
    public static final String DEFAULT_AVATAR =
        "https://api.canvas-chain.com/assets/default-avatar.png";

    /**
     * 平台佣金比率
     */
    public static final Double PLATFORM_COMMISSION_RATE = 0.10; // 10%

    /**
     * 最低充值金额
     */
    public static final Double MIN_RECHARGE_AMOUNT = 1.00;

    /**
     * 最高充值金额
     */
    public static final Double MAX_RECHARGE_AMOUNT = 100000.00;

    /**
     * 最低提现金额
     */
    public static final Double MIN_WITHDRAW_AMOUNT = 100.00;

    /**
     * 最高提现金额
     */
    public static final Double MAX_WITHDRAW_AMOUNT = 100000.00;
}

12.2 错误码定义 - ErrorCode.java

package com.canvas.chain.constant;

public class ErrorCode {

    // 成功
    public static final String SUCCESS = "200";
    public static final String SUCCESS_MESSAGE = "操作成功";

    // 客户端错误 (4xx)
    public static final String BAD_REQUEST = "400";
    public static final String BAD_REQUEST_MESSAGE = "请求参数错误";

    public static final String UNAUTHORIZED = "401";
    public static final String UNAUTHORIZED_MESSAGE = "未授权,请先登录";

    public static final String FORBIDDEN = "403";
    public static final String FORBIDDEN_MESSAGE = "禁止访问";

    public static final String NOT_FOUND = "404";
    public static final String NOT_FOUND_MESSAGE = "请求的资源不存在";

    // 业务错误
    public static final String USER_NOT_FOUND = "4001";
    public static final String USER_NOT_FOUND_MESSAGE = "用户不存在";

    public static final String USERNAME_ALREADY_EXISTS = "4002";
    public static final String USERNAME_ALREADY_EXISTS_MESSAGE = "用户名已存在";

    public static final String EMAIL_ALREADY_EXISTS = "4003";
    public static final String EMAIL_ALREADY_EXISTS_MESSAGE = "邮箱已被注册";

    public static final String PASSWORD_ERROR = "4004";
    public static final String PASSWORD_ERROR_MESSAGE = "密码错误";

    public static final String INVALID_TOKEN = "4005";
    public static final String INVALID_TOKEN_MESSAGE = "无效的Token";

    public static final String TOKEN_EXPIRED = "4006";
    public static final String TOKEN_EXPIRED_MESSAGE = "Token已过期";

    public static final String INSUFFICIENT_BALANCE = "4007";
    public static final String INSUFFICIENT_BALANCE_MESSAGE = "余额不足";

    // 服务器错误 (5xx)
    public static final String INTERNAL_SERVER_ERROR = "500";
    public static final String INTERNAL_SERVER_ERROR_MESSAGE = "服务器内部错误";

    public static final String SERVICE_UNAVAILABLE = "503";
    public static final String SERVICE_UNAVAILABLE_MESSAGE = "服务暂时不可用";
}

VibeCoding 导航:⬅️ 01-项目文档(Project Overview) | 02-CanvasChain 第1周 - 基础架构搭建详细设计文档 | ➡️ 01-计划