山东大学软件学院-项目实训-个人开发日志(二):从设想到原型——前后端骨架、数据库与宝宝档案链路初步打通
引言
大家好,我是山东大学软件学院2023级本科生张钧虹,“字节摇篮队”负责人。从这一周开始,我的关注点不再停留雨“想法是否合理”的讨论中,我更在意另外一个问题:这个项目到底应该先怎么落地,才能尽快从概念走向原型。
对我来说,这一周最重要的工作并不是简单地多写几个页面、多开几个接口,而是要先把BabyMind的第一条核心链路真正跑通。因为只有当一条真实的数据流建立起来之后,项目才不再是“功能设想的堆叠”,而开始变成一个可以持续迭代的系统。
因此,这一周我主要围绕三个目标推进开发:
- 梳理并推动前后端基础骨架成型;
- 明确数据库和宝宝档案的数据模型设计;
- 优先打通“用户注册登录—宝宝档案录入—数据持久化”这条主链路。
一、从“做什么”转向“先做什么”:我重新思考了项目推进顺序
在项目刚启动的时候,我不断的设想应该如何在前端和后端实现AI相关功能,比如多Agent、RAG、语音交互、智能问答、营养推荐等。但这一周我在真正推进开发时,越来越强烈地意识到:一个项目早期最重要的,不是空想,而是基础框架的搭建。
BabyMind想做的内容很多,但如果一开始就试图同时铺开所有模块,很容易出现一种情况:每个方向都做了一点,但没有任何一条流程真的能完整跑通。对我来说,这种状态是非常危险的,因为它会导致项目看似“进度很多”,实际上却始终停留在“碎片化开发”的层面。
所以这一周我在思考项目推进顺序,确定第一条最优先推进的业务链路:
用户注册/登录→创建宝宝档案→宝宝信息持久化→为后续模块提供个性化上下文
BabyMind后续无论做提醒、健康记录、疫苗计划、营养推荐还是AI问答,不可能脱离宝宝的具体信息。换句话说,宝宝档案不是一个附属页面,而是整个系统后续智能能力的入口。这一周的推进思路就是先把这一条主线从前端、后端到数据库完整建立起来,而不是一开始就把精力分散在太多方向上。
二、宝宝档案设计
宝宝档案是整个BabyMind系统的上下文中心,作为信息录入模块,包含姓名、性别、出生日期、孕周、过敏信息、备注等字段
1、为其他模块的agent提供个性化宝宝信息
BabyMind做的是0-3岁婴幼儿科学育儿辅助,这个场景要求回答和建议必须尽可能贴近具体宝宝本身的情况。
比如:
- 同样是辅食添加问题,6个月和18个月宝宝的建议一定不同;
- 同样是成长节律问题,是否早产会直接影响判断;
- 同样是喂养或营养建议,是否存在食物过敏会成为重要限制条件;
- 同样是健康问答,宝宝的年龄、病史、背景信息都可能影响回答的风险等级。
因此,在我看来,宝宝档案并不是“一个先填一下的信息页”,而是系统后续进行个性化推荐、结构化问答和成长分析的前提。
2、数据库设计的基础
除此之外,后面的很多模块对应的数据库表设计,都会直接依赖它。
例如:
- 提醒模块需要根据出生日期计算月龄,生成不同阶段的成长提醒;
- 疫苗模块需要根据出生日期生成接种计划;
- 营养模块需要读取过敏信息和年龄背景;
- AI问答模块需要把宝宝信息作为Prompt上下文的一部分;
- 时间轴模块也要围绕一个宝宝去聚合事件。
三、项目骨架搭建:前端—后端—数据库的完整链路
1、后端分层
一开始就把职责拆开,而不是把所有逻辑都塞进路由函数里:
api负责暴露接口;schemas负责请求响应结构和字段校验;models负责数据库实体;services负责业务逻辑;core负责配置、安全与通用能力。
这样做的好处是,随着后续健康记录、提醒、时间轴、问答、营养等模块不断加入,系统不会迅速变成一个“所有逻辑都混在一起的大文件集合”。
2、前端、后端和数据库链路
这周我完成了下面这条前端、后端和数据库链路完整闭环:
Android前端录入宝宝信息→ViewModel发起请求→FastAPI后端接收请求→Service层处理逻辑→ORM模型写入MySQL→返回结果再同步回前端。
这使得BabyMind具备端到端的数据流能力。后续无论我要继续做提醒、健康记录、营养推荐还是AI问答,都可以建立在这条已经被验证过的链路之上。
下面我用几个关键代码文件来说明,这条链路是怎样一步步建立起来的。
(1)前端接口定义:先把“宝宝档案创建”变成真实请求
在Android端,我首先要明确:前端到底要向后端发送什么请求。对应接口定义位于:
frontend/app/src/main/java/com/babymind/network/ApiService.kt
宝宝档案新增与更新接口如下:
@GET("babies")
suspend fun getBabies(@Header("Authorization") token: String): Response<List<BabyProfile>>
@POST("babies")
suspend fun createBaby(@Header("Authorization") token: String, @Body request: BabyCreateRequest): Response<BabyProfile>
@PUT("babies/{baby_id}")
suspend fun updateBaby(
@Header("Authorization") token: String,
@Path("baby_id") babyId: Int,
@Body request: BabyCreateRequest
): Response<BabyProfile>
(2)前端状态管理:把页面输入真正送进后端
仅仅定义接口还不够,我还需要让页面里用户输入的内容,真正进入请求链路。对应逻辑位于:
frontend/app/src/main/java/com/babymind/ui/viewmodel/BabyViewModel.kt
其中宝宝档案创建逻辑如下:
fun createBaby(request: BabyCreateRequest, onDone: (Boolean) -> Unit = {}) {
val token = sessionManager.getAuthToken() ?: run {
onDone(false)
return
}
viewModelScope.launch {
try {
val response = RetrofitClient.instance.createBaby("Bearer $token", request)
if (response.isSuccessful) {
fetchBabies()
onDone(true)
} else {
onDone(false)
}
} catch (_: Exception) {
onDone(false)
}
}
}
编辑更新逻辑如下:
fun updateBaby(babyId: Int, request: BabyCreateRequest, onDone: (Boolean) -> Unit = {}) {
val token = sessionManager.getAuthToken() ?: run {
onDone(false)
return
}
viewModelScope.launch {
try {
val response = RetrofitClient.instance.updateBaby("Bearer $token", babyId, request)
if (response.isSuccessful) {
fetchBabies()
onDone(true)
} else {
onDone(false)
}
} catch (_: Exception) {
onDone(false)
}
}
}
(3)后端路由:把前端请求正式接入FastAPI
前端请求发出去之后,后端必须有明确的入口接收它。对应代码位于:
app/api/routers/babies.py
宝宝档案创建接口如下:
@router.post("", response_model=BabyProfileRead, status_code=status.HTTP_201_CREATED)
def create_baby(
payload: BabyProfileCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> BabyProfileRead:
baby = create_baby_profile(db, current_user, payload)
return BabyProfileRead.model_validate(baby)
宝宝档案更新接口如下:
@router.put("/{baby_id}", response_model=BabyProfileRead)
def update_baby(
baby_id: int,
payload: BabyProfileUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> BabyProfileRead:
baby = get_baby_profile_for_user(db, current_user, baby_id)
if baby is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Baby profile not found.")
updated = update_baby_profile(db, baby, payload)
return BabyProfileRead.model_validate(updated)
这一部分非常关键,不仅实现了接口功能,还通过get_current_user明确了数据归属关系:宝宝档案不是全局共享数据,而是属于当前登录用户的私有数据。这一点对于后续多用户隔离和真实产品逻辑来说非常重要。
(4)Service层:把宝宝档案真正写进数据库
在后端部分,我把写库逻辑放到了Service层,把接口接收参数和业务逻辑处理分开。这样后续如果我要继续在宝宝档案创建时附带生成默认提醒、初始化营养画像,或者触发更多联动逻辑,就可以继续在Service层扩展,而不用打乱整个接口结构。对应代码位于:
app/services/baby_service.py
创建逻辑如下:
def create_baby_profile(db: Session, user: User, payload: BabyProfileCreate) -> BabyProfile:
baby = BabyProfile(user_id=user.id, **payload.model_dump())
db.add(baby)
db.commit()
db.refresh(baby)
return baby
更新逻辑如下:
def update_baby_profile(db: Session, baby: BabyProfile, payload: BabyProfileUpdate) -> BabyProfile:
for field, value in payload.model_dump(exclude_unset=True).items():
setattr(baby, field, value)
db.add(baby)
db.commit()
db.refresh(baby)
return baby
(5)对象关系映射ORM
将数据库表baby_profiles映射为类BabyProfile,对应代码位于:
app/models/baby_profile.py
宝宝档案模型核心字段如下:
class BabyProfile(Base):
__tablename__ = "baby_profiles"
id: Mapped[int] = mapped_column(primary_key=True, index=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(100), nullable=False)
gender: Mapped[str | None] = mapped_column(String(20), nullable=True)
birth_date: Mapped[date] = mapped_column(Date, nullable=False)
gestational_weeks: Mapped[int | None] = mapped_column(nullable=True)
food_allergies: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
drug_allergies: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
medical_history: Mapped[str | None] = mapped_column(Text, nullable=True)
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
(6)Schema层:保证进入数据库之前的数据是干净的
在后端对接口输入输出的数据结构进行统一约束,校验前端提交的数据是否合法,同时规范后端返回给前端的数据结构。对应代码位于:
app/schemas/baby.py
基础结构如下:
class BabyProfileBase(BaseModel):
name: str = Field(min_length=1, max_length=100)
gender: str | None = Field(default=None, max_length=20)
birth_date: date
gestational_weeks: int | None = Field(default=None, ge=20, le=45)
food_allergies: list[str] = Field(default_factory=list)
drug_allergies: list[str] = Field(default_factory=list)
medical_history: str | None = Field(default=None, max_length=2000)
notes: str | None = Field(default=None, max_length=2000)
同时我也很关注字段校验和清洗逻辑,例如:
@field_validator("birth_date")
@classmethod
def validate_birth_date(cls, value: date) -> date:
if value > date.today():
raise ValueError("birth_date cannot be in the future")
return value
@field_validator("food_allergies", "drug_allergies", mode="before")
@classmethod
def normalize_allergy_list(cls, value: object) -> list[str]:
if value is None:
return []
if not isinstance(value, list):
raise ValueError("allergy fields must be arrays of strings")
四、当前存在的问题与我接下来的计划
接下来我比较关注的几个方向是:
1、继续基于宝宝档案向外扩展业务能力
既然宝宝档案已经成为系统入口,那么下一步我会更关注如何让它真正驱动后续模块,比如提醒生成、疫苗计划和健康记录。
2、进一步推进前后端联调
只有当更多真实页面真正接上后端接口,很多隐藏问题才会暴露出来。后续我会继续关注Android页面与FastAPI接口之间的字段一致性、返回结构和状态管理问题。
五、结语
这一周我主要搭前后端骨架、梳理数据库结构、实现宝宝档案全链路、验证前后端数据库连接存储。项目正从设想一步步实现,后面的提醒、健康、时间轴、营养和AI问答将在这个基础上逐渐形成一个完整系统。接下来,我会继续沿着这条路径,把BabyMind的核心业务链路一点点补全。
欢迎各位老师、同学批评指正!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)