当前位置: 首页 > news >正文

PetLumina-02-后端开发与前后端联调


title: PetLumina 02 — Spring Boot 后端开发与前后端联调
date: 2026-05-21
tags:

  • PetLumina
  • Spring Boot
  • MyBatis-Plus
  • Sa-Token
  • AI开发
    categories:
  • 项目实战
    description: 使用 Spring Boot 2.7 + MyBatis-Plus 3.5 + Sa-Token 1.39 搭建后端服务,完成数据库设计、统一认证、接口规范,并与前端联调。

PetLumina 02 — Spring Boot 后端开发与前后端联调

Mock 阶段定义的数据结构,就是后端的 API 契约。按契约开发,联调零障碍。

一、技术选型与架构设计

1.1 技术栈

技术版本用途选型理由
Spring Boot2.7.x基础框架生态成熟,文档丰富
MyBatis-Plus3.5.xORM比原生 MyBatis 简洁,内置分页/逻辑删除
Sa-Token1.39.x权限认证比 Spring Security 轻量,注解式鉴权
MySQL8.0数据库项目规模不需要分布式数据库
Redis7.x缓存Sa-Token 默认使用 Redis 做 Session 存储

1.2 包结构设计

backend/src/main/java/com/petlumina/backend/ ├── annotation/ # 自定义注解 │ ├── AuthCheck.java # 权限校验注解 │ └── RateLimiter.java# 限流注解 ├── aop/ # 切面 │ ├── AuthCheckAspect.java │ └── RedisRateLimiterAspect.java ├── common/ # 公共类 │ ├── BaseResponse.java # 统一响应 │ ├── ResultUtils.java # 响应工具 │ ├── PageRequest.java # 分页请求基类 │ └── DeleteRequest.java # 删除请求 ├── config/ # 配置类 │ ├── JsonConfig.java # JSON 序列化 │ ├── CorsFilterConfig.java # 跨域配置 │ ├── MybatisPlusConfig.java # MP 配置 │ ├── SaTokenConfigure.java # Sa-Token 配置 │ └── AsyncConfig.java # 异步配置 ├── constant/ # 常量 │ ├── CosConstant.java # COS 路径常量 │ ├── RedisConstant.java # Redis Key 常量 │ └── UserConstant.java # 用户角色常量 ├── controller/ │ ├── admin/ # 管理端接口 │ └── user/ # 用户端接口 ├── model/ │ ├── entity/ # 数据库实体 │ ├── vo/ # 视图对象(返回前端) │ └── dto/ # 数据传输对象(接收参数) ├── mapper/ # MyBatis Mapper ├── service/ # 业务逻辑 │ └── impl/ ├── manager/ # 第三方服务管理 │ └── cos/ # COS 文件管理 ├── exception/ # 异常处理 └── utils/ # 工具类

为什么分admin/user/两套 Controller?

管理端和用户端的接口路径、权限校验、数据范围完全不同。管理端需要管理员权限,用户端只需要登录。分开后,权限注解、路径前缀、业务逻辑都清晰。

二、数据库设计

2.1 核心表结构

-- 用户表CREATETABLE`user`(`id`BIGINTNOTNULLCOMMENT'雪花ID',`username`VARCHAR(50)NOTNULLCOMMENT'用户名',`password`VARCHAR(128)NOTNULLCOMMENT'密码(BCrypt)',`nickname`VARCHAR(50)DEFAULTNULLCOMMENT'昵称',`avatar`VARCHAR(255)DEFAULTNULLCOMMENT'头像URL',`phone`VARCHAR(20)DEFAULTNULLCOMMENT'手机号',`role`TINYINTDEFAULT0COMMENT'0普通用户 1管理员',`create_time`DATETIMEDEFAULTCURRENT_TIMESTAMP,`update_time`DATETIMEDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP,`is_delete`TINYINTDEFAULT0COMMENT'逻辑删除',PRIMARYKEY(`id`),UNIQUEKEY`uk_username`(`username`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;-- 宠物表CREATETABLE`pet`(`id`BIGINTNOTNULL,`user_id`BIGINTNOTNULLCOMMENT'宠物主人ID',`name`VARCHAR(50)NOTNULLCOMMENT'宠物名字',`type`VARCHAR(20)DEFAULTNULLCOMMENT'类型(cat/dog/other)',`breed`VARCHAR(50)DEFAULTNULLCOMMENT'品种',`avatar`VARCHAR(255)DEFAULTNULLCOMMENT'头像URL',`birthday`DATEDEFAULTNULLCOMMENT'生日',`weight`DECIMAL(5,2)DEFAULTNULLCOMMENT'体重(kg)',`gender`TINYINTDEFAULT0COMMENT'0未知 1公 2母',`create_time`DATETIMEDEFAULTCURRENT_TIMESTAMP,`update_time`DATETIMEDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP,`is_delete`TINYINTDEFAULT0,PRIMARYKEY(`id`),KEY`idx_user_id`(`user_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;

2.2 设计要点

为什么用 BIGINT 而不是 INT 做主键?

雪花算法生成的 ID 是 19 位数字,INT 最大只有 21 亿(10 位),BIGINT 最大 9.2×10^18(19 位),刚好够用。

为什么用is_delete逻辑删除?

用户数据不能物理删除 — 宠物记录、健康数据、帖子等都有关联关系。物理删除会导致外键断裂、数据不一致。逻辑删除只是标记状态,数据还在。

三、统一响应封装

// common/BaseResponse.java@DatapublicclassBaseResponse<T>implementsSerializable{privateintcode;// 状态码privateStringmessage;// 提示信息privateTdata;// 数据publicBaseResponse(intcode,Tdata,Stringmessage){this.code=code;this.data=data;this.message=message;}publicBaseResponse(intcode,Tdata){this(code,data,"");}publicBaseResponse(ErrorCodeerrorCode){this(errorCode.getCode(),null,errorCode.getMessage());}}// common/ResultUtils.javapublicclassResultUtils{publicstatic<T>BaseResponse<T>success(Tdata){returnnewBaseResponse<>(0,data,"ok");}publicstaticBaseResponse<?>error(ErrorCodeerrorCode){returnnewBaseResponse<>(errorCode);}publicstaticBaseResponse<?>error(intcode,Stringmessage){returnnewBaseResponse<>(code,null,message);}}

code=0 表示成功— 和前端约定好,前端只判断code === 0,其他一律为失败。

四、Sa-Token 认证配置

4.1 依赖配置

# application.ymlsa-token:token-name:satokentimeout:86400# Token 有效期 24 小时is-concurrent:true# 允许同一账号并发登录is-share:true# 同端共享 Tokentoken-style:uuid# Token 格式

4.2 拦截器配置

// config/SaTokenConfigure.java@ConfigurationpublicclassSaTokenConfigureimplementsWebMvcConfigurer{@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(newSaInterceptor(handle->{// 管理端接口 — 必须管理员角色SaRouter.match("/api/v1/admin/**").check(r->StpUtil.checkRole("admin"));// 用户端接口 — 必须登录(排除登录/注册)SaRouter.match("/api/v1/**").notMatch("/api/v1/user/login","/api/v1/user/register").check(r->StpUtil.checkLogin());})).addPathPatterns("/api/**");}}

Sa-Token 的路由匹配比 Spring Security 简洁很多SaRouter.match().check()一行搞定一个规则。

五、MyBatis-Plus 配置

5.1 自动填充

// config/MyMetaObjectHandler.java@ComponentpublicclassMyMetaObjectHandlerimplementsMetaObjectHandler{@OverridepublicvoidinsertFill(MetaObjectmetaObject){this.strictInsertFill(metaObject,"createTime",Date.class,newDate());this.strictInsertFill(metaObject,"updateTime",Date.class,newDate());}@OverridepublicvoidupdateFill(MetaObjectmetaObject){this.strictUpdateFill(metaObject,"updateTime",Date.class,newDate());}}

5.2 实体类注解

// model/entity/Pet.java@Data@TableName("pet")publicclassPetimplementsSerializable{@TableId(type=IdType.ASSIGN_ID)// 雪花 IDprivateLongid;privateLonguserId;privateStringname;privateStringtype;privateStringbreed;privateStringavatar;privateDatebirthday;privateDoubleweight;privateIntegergender;@TableField(fill=FieldFill.INSERT)privateDatecreateTime;@TableField(fill=FieldFill.INSERT_UPDATE)privateDateupdateTime;@TableLogic// 逻辑删除privateIntegerisDelete;}

六、前端请求层改造

6.1 Axios 实例

// api/request.tsconstrequest=axios.create({baseURL:import.meta.env.VITE_API_BASE_URL||'/api/v1',timeout:10000,})// 请求拦截器 — 注入 Tokenrequest.interceptors.request.use(config=>{consttoken=localStorage.getItem('satoken')if(token){config.headers['satoken']=token// Sa-Token 的 Header 名}returnconfig})// 响应拦截器 — 统一处理request.interceptors.response.use(response=>{constres=response.dataif(res.code!==0){showToast(res.message||'请求失败')if(res.code===40100){// 未登录localStorage.removeItem('satoken')window.location.hash='#/login'}returnPromise.reject(newError(res.message))}returnres.data// 直接返回 data,不包装在 BaseResponse 中})

6.2 接口模块化

// api/pet.tsexportconstpetApi={getList:()=>request.get('/pet/list'),getDetail:(id:string)=>request.get('/pet/detail',{params:{id}}),add:(data:any)=>request.post('/pet/add',data),update:(data:any)=>request.post('/pet/update',data),delete:(id:string)=>request.post('/pet/delete',{id}),}

七、跨域配置

// config/CorsFilterConfig.java@ConfigurationpublicclassCorsFilterConfig{@BeanpublicCorsFiltercorsFilter(){UrlBasedCorsConfigurationSourcesource=newUrlBasedCorsConfigurationSource();CorsConfigurationconfig=newCorsConfiguration();config.setAllowCredentials(true);config.addAllowedOriginPattern("*");config.addAllowedHeader("*");config.addAllowedMethod("*");source.registerCorsConfiguration("/api/**",config);returnnewCorsFilter(source);}}

为什么用addAllowedOriginPattern("*")而不是addAllowedOrigin("*")

allowCredentials(true)addAllowedOrigin("*")不能同时使用 — 这是 CORS 规范的限制。addAllowedOriginPattern("*")是 Spring Boot 2.4+ 提供的替代方案。

八、踩坑记录

8.1 QueryWrapper 字段名

// ❌ 错误 — 驼峰是 Java 属性名,不是数据库字段名queryWrapper.eq("createTime",date);// ✅ 正确 — 数据库字段是下划线queryWrapper.eq("create_time",date);

MyBatis-Plus 的QueryWrapper使用的是数据库字段名,不是 Java 属性名。这个错误在后面的用户管理、宠物管理、帖子管理中反复出现。

8.2 Sa-Token 登录接口

@PostMapping("/login")publicBaseResponse<UserVO>login(@RequestBodyUserLoginRequestrequest){// 查询用户Useruser=userService.lambdaQuery().eq(User::getUsername,request.getUsername()).one();if(user==null||!BCrypt.checkpw(request.getPassword(),user.getPassword())){returnResultUtils.error(ErrorCode.PARAMS_ERROR,"用户名或密码错误");}// Sa-Token 登录 — 会自动生成 Token 并写入 RedisStpUtil.login(user.getId());// 返回用户信息 + TokenUserVOvo=UserVO.objToVo(user);vo.setToken(StpUtil.getTokenValue());returnResultUtils.success(vo);}

九、总结

v2.0 完成了后端基础框架搭建和前后端联调。

核心经验:

  1. API 契约先行— Mock 阶段定义的数据结构就是接口契约,后端按此实现
  2. 统一响应格式code=0成功,其他失败,前端只需一个判断
  3. QueryWrapper 用下划线— 这是最常见的坑,记住就好
  4. Sa-Token 的SaRouter— 一行代码一个权限规则,比 Spring Security 简洁 10 倍

http://www.jsqmd.com/news/980094/

相关文章:

  • 模电课设别再头疼!手把手教你用LM358和滑动变阻器搞定水位检测电路(附完整Multisim仿真文件)
  • 11.什么是单例模式?
  • 岳阳市黄金回收+白银回收+铂金回收+彩金回推荐收门店 本地靠谱店铺指南及地联系方式址和 - 大熊猫898989
  • 南充黄金回收哪家靠谱 本地靠谱实体门店汇总 - 润富黄金回收
  • 嘉兴SEO优化公司|ToB企业询盘提升,嘉兴SEO营销公司服务对比 - 招财兔数字员工
  • Web 编程核心思路 + 实用技巧(全栈通用)
  • 3分钟生成专业短视频:Pixelle-Video AI全自动视频创作工具完全指南
  • 2026工控机应用白皮书网络安全领域深度剖析:嵌入式工控机/工业平板电脑/工业计算机厂家/全国产化主板/国产化电脑定制/选择指南 - 优质品牌商家
  • 别再只盯着PHY芯片了!手把手教你搞定RGMII接口PCB布局布线(含TI TDA4/高通8295 SoC直连避坑指南)
  • 别再只用uvm_do_on了!手把手教你用start_item/finish_item搞定复杂transaction发送
  • STM32 HAL库ADC采样总是不准?可能是DMA配置踩了这些坑(以F103C8T6为例)
  • GPT-5.5 Instant实测:10分钟就能把读过的文献转化成学术论证!
  • ML工程师的CI/CD实战指南:构建可验证、可回滚的模型交付流水线
  • Spring WebFlux + AI 流式输出深度解析:Spring AI 与 LangChain4j 效果差异溯源
  • 云浮市黄金回收+白银回收+铂金回收+彩金回推荐收门店 本地靠谱店铺指南及地联系方式址和 - 大熊猫898989
  • 株洲市黄金回收本地靠谱店铺指南+白银回收+铂金回收+彩金回推荐收门店 及地联系方式址推荐 - 盛世金银回收
  • 越南服务器 ping 值多少?
  • 多维聚合数据操作:预计算、实时补丁与语义层三层架构
  • Python List底层原理与高性能使用指南
  • 多维聚合实战:从GROUP BY到OLAP立方体的数据操纵体系
  • 智能眼镜禁入之后:高考考场里的“AI巡检员”如何炼成?
  • 本科生毕业设计专用:OpenCV图像处理+CNN车牌字符识别完整实现包
  • 福清SEO优化公司|品牌搜索曝光升级,福清网站优化公司能力解析 - 招财兔数字员工
  • 双歧管拓扑优化针翅冷板:汽车功率逆变器高热通量热管理的破局之道
  • 从PLC到储能系统,工业网络为何越来越重视自主可控?
  • 青岛家政保姆怎么选?老牌机构刘大姐家政深度测评(避坑干货)
  • 驻马店市黄金回收本地靠谱店铺指南+白银回收+铂金回收+彩金回推荐收门店 及地联系方式址推荐 - 盛世金银回收
  • 运城市黄金回收+白银回收+铂金回收+彩金回推荐收门店 本地靠谱店铺指南及地联系方式址和 - 大熊猫898989
  • 有人在对话框里写“忽略你的设定“,我的 Agent 差点被带跑——聊聊 Prompt 注入防御
  • 铜川卖黄金选哪家 正规黄金回收门店实测汇总 - 润富黄金回收