如何快速画UML

以前毕业设计画 UML 图累的要死。。。

最近在学习一个项目,做数据模型的时候使用 AI 绘制 UML 图来理清楚数据模型之间的关系。以下是步骤

1、代码

示例如下,代码来自于datawhale的 hello-agents 项目

"""数据模型定义"""

from typing import List, Optional, Union
from pydantic import BaseModel, Field, field_validator
from datetime import date


# ============ 请求模型 ============

class TripRequest(BaseModel):
    """旅行规划请求"""
    city: str = Field(..., description="目的地城市", example="北京")
    start_date: str = Field(..., description="开始日期 YYYY-MM-DD", example="2025-06-01")
    end_date: str = Field(..., description="结束日期 YYYY-MM-DD", example="2025-06-03")
    travel_days: int = Field(..., description="旅行天数", ge=1, le=30, example=3)
    transportation: str = Field(..., description="交通方式", example="公共交通")
    accommodation: str = Field(..., description="住宿偏好", example="经济型酒店")
    preferences: List[str] = Field(default=[], description="旅行偏好标签", example=["历史文化", "美食"])
    free_text_input: Optional[str] = Field(default="", description="额外要求", example="希望多安排一些博物馆")
    
    class Config:
        json_schema_extra = {
            "example": {
                "city": "北京",
                "start_date": "2025-06-01",
                "end_date": "2025-06-03",
                "travel_days": 3,
                "transportation": "公共交通",
                "accommodation": "经济型酒店",
                "preferences": ["历史文化", "美食"],
                "free_text_input": "希望多安排一些博物馆"
            }
        }


class POISearchRequest(BaseModel):
    """POI搜索请求"""
    keywords: str = Field(..., description="搜索关键词", example="故宫")
    city: str = Field(..., description="城市", example="北京")
    citylimit: bool = Field(default=True, description="是否限制在城市范围内")


class RouteRequest(BaseModel):
    """路线规划请求"""
    origin_address: str = Field(..., description="起点地址", example="北京市朝阳区阜通东大街6号")
    destination_address: str = Field(..., description="终点地址", example="北京市海淀区上地十街10号")
    origin_city: Optional[str] = Field(default=None, description="起点城市")
    destination_city: Optional[str] = Field(default=None, description="终点城市")
    route_type: str = Field(default="walking", description="路线类型: walking/driving/transit")


# ============ 响应模型 ============

class Location(BaseModel):
    """地理位置"""
    longitude: float = Field(..., description="经度")
    latitude: float = Field(..., description="纬度")


class Attraction(BaseModel):
    """景点信息"""
    name: str = Field(..., description="景点名称")
    address: str = Field(..., description="地址")
    location: Location = Field(..., description="经纬度坐标")
    visit_duration: int = Field(..., description="建议游览时间(分钟)",gt=0)
    description: str = Field(..., description="景点描述")
    category: Optional[str] = Field(default="景点", description="景点类别")
    rating: Optional[float] = Field(default=None, description="评分")
    photos: Optional[List[str]] = Field(default_factory=list, description="景点图片URL列表")
    poi_id: Optional[str] = Field(default="", description="POI ID")
    image_url: Optional[str] = Field(default=None, description="图片URL")
    ticket_price: int = Field(default=0, description="门票价格(元)")


class Meal(BaseModel):
    """餐饮信息"""
    type: str = Field(..., description="餐饮类型: breakfast/lunch/dinner/snack")
    name: str = Field(..., description="餐饮名称")
    address: Optional[str] = Field(default=None, description="地址")
    location: Optional[Location] = Field(default=None, description="经纬度坐标")
    description: Optional[str] = Field(default=None, description="描述")
    estimated_cost: int = Field(default=0, description="预估费用(元)")


class Hotel(BaseModel):
    """酒店信息"""
    name: str = Field(..., description="酒店名称")
    address: str = Field(default="", description="酒店地址")
    location: Optional[Location] = Field(default=None, description="酒店位置")
    price_range: str = Field(default="", description="价格范围")
    rating: str = Field(default="", description="评分")
    distance: str = Field(default="", description="距离景点距离")
    type: str = Field(default="", description="酒店类型")
    estimated_cost: int = Field(default=0, description="预估费用(元/晚)")


class DayPlan(BaseModel):
    """单日行程"""
    date: str = Field(..., description="日期 YYYY-MM-DD")
    day_index: int = Field(..., description="第几天(从0开始)")
    description: str = Field(..., description="当日行程描述")
    transportation: str = Field(..., description="交通方式")
    accommodation: str = Field(..., description="住宿")
    hotel: Optional[Hotel] = Field(default=None, description="推荐酒店")
    attractions: List[Attraction] = Field(default=[], description="景点列表")
    meals: List[Meal] = Field(default=[], description="餐饮列表")


class WeatherInfo(BaseModel):
    """天气信息"""
    date: str = Field(..., description="日期 YYYY-MM-DD")
    day_weather: str = Field(default="", description="白天天气")
    night_weather: str = Field(default="", description="夜间天气")
    day_temp: Union[int, str] = Field(default=0, description="白天温度")
    night_temp: Union[int, str] = Field(default=0, description="夜间温度")
    wind_direction: str = Field(default="", description="风向")
    wind_power: str = Field(default="", description="风力")

    @field_validator('day_temp', 'night_temp', mode='before')
    @classmethod
    def parse_temperature(cls, v):
        """解析温度,移除°C等单位"""
        if isinstance(v, str):
            # 移除°C, ℃等单位符号
            v = v.replace('°C', '').replace('℃', '').replace('°', '').strip()
            try:
                return int(v)
            except ValueError:
                return 0
        return v


class Budget(BaseModel):
    """预算信息"""
    total_attractions: int = Field(default=0, description="景点门票总费用")
    total_hotels: int = Field(default=0, description="酒店总费用")
    total_meals: int = Field(default=0, description="餐饮总费用")
    total_transportation: int = Field(default=0, description="交通总费用")
    total: int = Field(default=0, description="总费用")


class TripPlan(BaseModel):
    """旅行计划"""
    city: str = Field(..., description="目的地城市")
    start_date: str = Field(..., description="开始日期")
    end_date: str = Field(..., description="结束日期")
    days: List[DayPlan] = Field(..., description="每日行程")
    weather_info: List[WeatherInfo] = Field(default=[], description="天气信息")
    overall_suggestions: str = Field(..., description="总体建议")
    budget: Optional[Budget] = Field(default=None, description="预算信息")


class TripPlanResponse(BaseModel):
    """旅行计划响应"""
    success: bool = Field(..., description="是否成功")
    message: str = Field(default="", description="消息")
    data: Optional[TripPlan] = Field(default=None, description="旅行计划数据")


class POIInfo(BaseModel):
    """POI信息"""
    id: str = Field(..., description="POI ID")
    name: str = Field(..., description="名称")
    type: str = Field(..., description="类型")
    address: str = Field(..., description="地址")
    location: Location = Field(..., description="经纬度坐标")
    tel: Optional[str] = Field(default=None, description="电话")


class POISearchResponse(BaseModel):
    """POI搜索响应"""
    success: bool = Field(..., description="是否成功")
    message: str = Field(default="", description="消息")
    data: List[POIInfo] = Field(default=[], description="POI列表")


class RouteInfo(BaseModel):
    """路线信息"""
    distance: float = Field(..., description="距离(米)")
    duration: int = Field(..., description="时间(秒)")
    route_type: str = Field(..., description="路线类型")
    description: str = Field(..., description="路线描述")


class RouteResponse(BaseModel):
    """路线规划响应"""
    success: bool = Field(..., description="是否成功")
    message: str = Field(default="", description="消息")
    data: Optional[RouteInfo] = Field(default=None, description="路线信息")


class WeatherResponse(BaseModel):
    """天气查询响应"""
    success: bool = Field(..., description="是否成功")
    message: str = Field(default="", description="消息")
    data: List[WeatherInfo] = Field(default=[], description="天气信息")


# ============ 错误响应 ============

class ErrorResponse(BaseModel):
    """错误响应"""
    success: bool = Field(default=False, description="是否成功")
    message: str = Field(..., description="错误消息")
    error_code: Optional[str] = Field(default=None, description="错误代码")


2、 输入大模型得到PlantUML 格式

把第一步的源代码输入到大模型中,让他将该数据模型转换成 PlantUML 格式,得到以下代码:

@startuml
' 设置样式:隐藏方法,只显示属性,让图表更清爽
hide methods
skinparam classAttributeIconSize 0
skinparam monochrome true

' ============ 基础/通用模型 ============
class Location {
    +longitude: float
    +latitude: float
}

' ============ 请求模型 ============
class TripRequest {
    +city: str
    +start_date: str
    +end_date: str
    +travel_days: int
    +transportation: str
    +accommodation: str
    +preferences: List[str]
    +free_text_input: Optional[str]
}

class POISearchRequest {
    +keywords: str
    +city: str
    +citylimit: bool
}

class RouteRequest {
    +origin_address: str
    +destination_address: str
    +origin_city: Optional[str]
    +destination_city: Optional[str]
    +route_type: str
}

' ============ 业务实体模型 ============
class Attraction {
    +name: str
    +address: str
    +location: Location
    +visit_duration: int
    +description: str
    +category: Optional[str]
    +rating: Optional[float]
    +photos: Optional[List[str]]
    +poi_id: Optional[str]
    +image_url: Optional[str]
    +ticket_price: int
}

class Meal {
    +type: str
    +name: str
    +address: Optional[str]
    +location: Optional[Location]
    +description: Optional[str]
    +estimated_cost: int
}

class Hotel {
    +name: str
    +address: str
    +location: Optional[Location]
    +price_range: str
    +rating: str
    +distance: str
    +type: str
    +estimated_cost: int
}

class DayPlan {
    +date: str
    +day_index: int
    +description: str
    +transportation: str
    +accommodation: str
    +hotel: Optional[Hotel]
    +attractions: List[Attraction]
    +meals: List[Meal]
}

class WeatherInfo {
    +date: str
    +day_weather: str
    +night_weather: str
    +day_temp: Union[int, str]
    +night_temp: Union[int, str]
    +wind_direction: str
    +wind_power: str
}

class Budget {
    +total_attractions: int
    +total_hotels: int
    +total_meals: int
    +total_transportation: int
    +total: int
}

' ============ 核心响应模型 ============
class TripPlan {
    +city: str
    +start_date: str
    +end_date: str
    +days: List[DayPlan]
    +weather_info: List[WeatherInfo]
    +overall_suggestions: str
    +budget: Optional[Budget]
}

class TripPlanResponse {
    +success: bool
    +message: str
    +data: Optional[TripPlan]
}

' ============ POI 相关模型 ============
class POIInfo {
    +id: str
    +name: str
    +type: str
    +address: str
    +location: Location
    +tel: Optional[str]
}

class POISearchResponse {
    +success: bool
    +message: str
    +data: List[POIInfo]
}

' ============ 路线相关模型 ============
class RouteInfo {
    +distance: float
    +duration: int
    +route_type: str
    +description: str
}

class RouteResponse {
    +success: bool
    +message: str
    +data: Optional[RouteInfo]
}

' ============ 天气响应模型 ============
class WeatherResponse {
    +success: bool
    +message: str
    +data: List[WeatherInfo]
}

' ============ 错误模型 ============
class ErrorResponse {
    +success: bool
    +message: str
    +error_code: Optional[str]
}

' ============ 关系定义 ============

' 核心行程组合关系
TripPlan "1" o-- "1..*" DayPlan : contains
TripPlan "1" o-- "0..1" Budget : contains
TripPlan "1" o-- "0..*" WeatherInfo : includes

' 每日行程组合关系
DayPlan "1" o-- "0..1" Hotel : recommends
DayPlan "1" o-- "0..*" Attraction : visits
DayPlan "1" o-- "0..*" Meal : eats

' 实体与地理位置的关系
Attraction "1" --> "1" Location : has
Meal "1" --> "0..1" Location : has
Hotel "1" --> "0..1" Location : has
POIInfo "1" --> "1" Location : has

' 响应包装关系
TripPlanResponse "1" o-- "0..1" TripPlan : data
POISearchResponse "1" o-- "0..*" POIInfo : data
RouteResponse "1" o-- "0..1" RouteInfo : data
WeatherResponse "1" o-- "0..*" WeatherInfo : data

@enduml

3、绘图

将得到的PlantUML 语言复制到 drawio 绘图软件里,立马出图

image

然后就可以得到自己的 UML 图:

image

posted @ 2026-05-01 22:21  江鸟Dev  阅读(4)  评论(0)    收藏  举报