苍穹外卖Day3:公共字段填充与菜品模块实战解析
做了这么多年Java后端第一次把苍穹外卖完整跟下来的时候Day3给我留下的印象最深。倒不是因为菜品模块功能有多难而是“公共字段填充”这个设计让我意识到很多项目里啰嗦又容易漏的set操作真实生产环境里早就用框架机制一次性解决了。这篇文章就围绕Day3的两个主题——公共字段填充和菜品模块把设计思路、完整代码、踩坑记录一次说清楚。整个过程涉及的核心技术点包括MyBatis-Plus的字段自动填充、ThreadLocal在请求链路中的上下文传递、MetaObjectHandler拦截器的使用以及菜品相关的DTO/VO分层、批量插入、分页查询这类业务常规操作。适合正在跟苍穹外卖的朋友也适合想把项目里的重复代码抽干净的后端开发。1. 公共字段填充的整体设计思路先想一个问题一张业务表比如菜品表dish里面几乎都有create_time、create_user、update_time、update_user这四个字段。传统写法是在每个新增和修改的Service方法里手动一行一行set这4个值。第一两个方法还能忍等做到员工、菜品、分类、套餐、订单这些模块时同样的代码得写几十遍而且漏掉一个set就是事故。苍穹外卖在Day3就果断把这个问题治了。思想很直接既然所有实体类都有公共字段那就把这些字段的赋值逻辑统一抽出来由框架在insert或update时自动完成。实现靠的是MyBatis-Plus的MetaObjectHandler它像一个钩子在底层真正执行SQL之前被回调我们只需要在这个回调里给实体的公共属性赋值即可。调用方完全无感Service层代码干净很多。再深挖一层自动填充解决了“什么时间字段被赋值”但还有另一个问题——值从哪来create_user和update_user需要拿到当前登录用户的IDcreate_time和update_time直接取当前系统时间就行。时间好办System.currentTimeMillis()或LocalDateTime.now()都能搞定。用户ID却是个麻烦因为MetaObjectHandler的回调方法签名是固定的参数里只有一个MetaObject你没法从方法参数里把当前用户传进去。这时候就得靠ThreadLocal了。ThreadLocal在这里扮演的是“请求链路内的共享上下文”。用户的登录状态在拦截器或Controller层是能拿到的我们提前把这个用户ID存进ThreadLocal后续在MetaObjectHandler里再从同一个线程的ThreadLocal取出来用。因为一次HTTP请求从进入拦截器到Service执行完SQL全程都在同一个线程里处理同步情况下所以ThreadLocal天然适合这个场景。这套组合拳打完以后任何模块的新增方法都不用再操心四个公共字段了。1.1 为什么不用常规的AOP或拦截器来做看到这个需求有人第一反应是写个AOP切面拦截所有Mapper的insert和update。可以但成本偏高。你要自己去解析Mapper方法的参数、判断哪些字段要处理、还要处理批量插入的复杂情况。MyBatis-Plus本身已经开放了MetaObjectHandler这个扩展点直接实现它然后注册成Spring Bean就行比手工AOP要稳定很多。选框架现成的能力是我们日常开发最优先考虑的原则。1.2 数据流向全景理一下整个数据的流转方便后面写代码时不会迷路前端请求携带token进入拦截器或切面解析出当前用户ID用户ID存入BaseContext内部就是一个ThreadLocalController调用Service新增菜品实体对象里公共字段还是nullMyBatis-Plus执行插入前触发MetaObjectHandler的insertFill方法在insertFill里从BaseContext取用户ID配合当前时间给实体的createTime、createUser、updateTime、updateUser赋值SQL真正执行数据库里公共字段全部有值。这个流程理解透了代码实现就非常简单。2. 公共字段填充的具体实现先要把BaseContext工具类准备好。它本质上就是一个ThreadLocal的封装项目里几乎每个需要获取当前登录用户的地方都会用到。代码非常简单public class BaseContext { private static ThreadLocalLong threadLocal new ThreadLocal(); public static void setCurrentId(Long id) { threadLocal.set(id); } public static Long getCurrentId() { return threadLocal.get(); } public static void removeCurrentId() { threadLocal.remove(); } }注意这里有两个容易被忽略的点。第一ThreadLocal泛型用的是Long对应员工表的主键id类型。第二一定要提供remove方法。我之前在别的项目吃过亏——Tomcat的工作线程是复用的如果请求处理完不清理ThreadLocal下一次请求进来时当前线程可能还残留上一个用户的ID轻则数据错乱重则产生越权问题。所以在拦截器的afterCompletion阶段务必要调用BaseContext.removeCurrentId()。苍穹外卖课程里其实也在拦截器统一做了清理这一步千万别省。接着给实体类的公共字段加注解。MyBatis-Plus提供了TableField注解里面有个fill属性Data public class Dish implements Serializable { private static final long serialVersionUID 1L; private Long id; private String name; private Long categoryId; private BigDecimal price; // 其他业务字段省略 TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT) private Long createUser; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; TableField(fill FieldFill.INSERT_UPDATE) private Long updateUser; }FieldFill取值有两个最常用INSERT表示新增时填充INSERT_UPDATE表示新增和更新时都填充。这里要注意createTime和createUser只在插入时赋值因为记录创建时间和创建人一旦写入就不会变updateTime和updateUser则要在插入和更新时都刷新。这个设计思路要理解别四个字段全部用INSERT_UPDATE看起来省事其实语义上有问题。接下来是核心实现MetaObjectHandler并注册成Spring BeanComponent public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { Long currentId BaseContext.getCurrentId(); this.setFieldValByName(createTime, LocalDateTime.now(), metaObject); this.setFieldValByName(createUser, currentId, metaObject); this.setFieldValByName(updateTime, LocalDateTime.now(), metaObject); this.setFieldValByName(updateUser, currentId, metaObject); } Override public void updateFill(MetaObject metaObject) { Long currentId BaseContext.getCurrentId(); this.setFieldValByName(updateTime, LocalDateTime.now(), metaObject); this.setFieldValByName(updateUser, currentId, metaObject); } }这里有一个我曾经踩过的坑setFieldValByName传入的是实体类属性名不是数据库列名。比如数据库列叫create_time但这里写的必须是createTime因为MetaObject操作的是Java对象属性MyBatis-Plus底层拿到值以后再做驼峰到下划线的映射。写错名字不会报错只会静默不填充排查时特别容易怀疑人生。另外设计上要留个心眼如果某些极端场景下Service层已经手动给公共字段赋了值MetaObjectHandler会覆盖掉。所以更严谨的做法是先判空value不为null就不覆盖if (metaObject.hasSetter(updateTime)) { Object oldValue this.getFieldValByName(updateTime, metaObject); if (oldValue null) { this.setFieldValByName(updateTime, LocalDateTime.now(), metaObject); } }不过苍穹外卖项目里Service层从没手动赋值直接覆盖问题不大。但把这个习惯带进真实项目能省很多麻烦。3. 菜品模块的表结构分析与层级规划公共字段填充解决的是横切逻辑接下来就进入正题菜品模块。这是苍穹外卖里第一个真正完整的业务闭环从表结构到接口设计到业务校验都有代表性。先看数据库设计。菜品表dish用来存菜品基本信息比如名称、分类、价格、图片、描述、状态等菜品口味表dish_flavor用来存每个菜品的口味选项比如“微辣、中辣、特辣”。一张主表加一张子表典型的一对多关系CREATE TABLE dish ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, category_id BIGINT NOT NULL, price DECIMAL(10, 2) NOT NULL, image VARCHAR(255), description VARCHAR(255), status INT DEFAULT 1, sort INT DEFAULT 0, create_time DATETIME, create_user BIGINT, update_time DATETIME, update_user BIGINT ); CREATE TABLE dish_flavor ( id BIGINT PRIMARY KEY AUTO_INCREMENT, dish_id BIGINT NOT NULL, name VARCHAR(32), value VARCHAR(255), create_time DATETIME, create_user BIGINT, update_time DATETIME, update_user BIGINT );dish_flavor这里也加了四个公共字段这在真实业务里是常见做法。即使子表一般不需要显示创建人但保留审计信息便于追踪数据来源。不过按前两节的逻辑dish_flavor里的公共字段也会被自动填充机制处理前提是它的实体类也加了TableField注解。这一点在后续插入口味数据时要特别注意。从代码结构来说菜品模块沿用了标准的Controller-Service-Mapper三层另外加了DTO和VO对象。实体类Dish和DishFlavor直接对表DTO负责承载前端提交的数据VO负责向前端返回数据。为什么要拆这么细因为前端提交的DishDTO里会包含List 这是实体类Dish里不存在的关联数据而分页查询返回的DishVO里需要多一个categoryName分类名称字段也是实体类Dish里没有的。如果不用DTO和VO直接拿Map到处传项目一复杂就全是代码味道。我个人的习惯是凡是跨层传递数据都不要复用实体对象。实体对象是数据库的映射DTO是接口的契约VO是视图的形态三者各自独立最多通过BeanUtils.copyProperties互相拷贝。这样改表结构不会直接崩接口改接口也不会污染实体。4. 菜品新增功能的完整实现菜品新增是菜品模块里最有营养的一个功能因为它涉及事务、关联表插入、公共字段填充、批量操作前后端联调的坑也多。一个菜品的新增接口前端会同时提交菜品基本信息和口味列表JSON长这样{ name: 鱼香肉丝, categoryId: 105, price: 2800, image: xxx.jpg, description: 招牌川菜, status: 1, flavors: [ { name: 辣度, value: 微辣,中辣,特辣 }, { name: 忌口, value: 不吃葱,不吃蒜 } ] }注意price我写的2800这是以“分”为单位的整数避免Double类型带来的精度问题。苍穹外卖里价格字段在数据库是DECIMAL(10,2)接收时用BigDecimal前端传什么单位以项目接口文档为准。这点做项目时一定要看文档别自己想当然。Controller层代码很简洁PostMapping ApiOperation(新增菜品) public ResultString save(RequestBody DishDTO dishDTO) { log.info(新增菜品{}, dishDTO); dishService.saveWithFlavor(dishDTO); return Result.success(); }RequestBody告诉Spring MVC把请求体里的JSON反序列化成DishDTO对象。如果这里忘记加RequestBodySpring MVC会尝试从表单参数里一一匹配DTO属性结果全是null这是新手最常犯的错误之一。前端请求头Content-Type也必须是application/json否则Spring不会走JSON解析器。Service层是业务核心菜品新增的saveWithFlavor方法集中体现了一个典型的“先主后从”插入策略Transactional public void saveWithFlavor(DishDTO dishDTO) { Dish dish new Dish(); BeanUtils.copyProperties(dishDTO, dish); dishMapper.insert(dish); Long dishId dish.getId(); ListDishFlavor flavors dishDTO.getFlavors(); if (flavors ! null flavors.size() 0) { flavors.forEach(flavor - { flavor.setDishId(dishId); dishFlavorMapper.insert(flavor); }); } }第一步把DishDTO里的同名字段拷贝到Dish实体然后调用dishMapper.insert(dish)。这里Dish的主键id是数据库自增插入完成后MyBatis-Plus会把生成的主键回填到dish.getId()中所以后续才能用这个id作为dish_flavor的外键值。第二步是关键遍历口味列表逐个设置dishId然后执行insert。这里我没有用批量插入而是循环单条插入。数据量实在很小一个菜品最多几个口味循环插入完全够用也不需要额外拼接复杂的SQL。如果你喜欢用MP的批量能力也可以用lambdaInsertBatch但其实在苍穹外卖的课程版本里循环插入就是标准答案。先把业务跑通再考虑性能才是正确节奏。这里必须强调事务。Transactional加在saveWithFlavor上保证主表插入和子表插入是同一个事务如果第5个口味插入失败前面4个已经插入的口味和主表菜品都会被回滚不会产生脏数据。不加这个注解的话接口执行到一半报错数据库里就会残留一个没有口味的菜品记录这就是典型的半成品数据。所以凡是涉及多表写入的Service方法事务注解是底线没有例外。口味数据为空的情况也要处理。我在代码里做了判空flavors为null或者size为0就跳过子表插入。有时候前端没填口味直接提交一个空数组这种情况也常见。判空一定要写在前面否则一个NullPointerException就够查半天。5. 菜品分页查询、启售停售与公共字段的联动菜品模块还有三个高频接口不能忽略分页查询、启售停售和修改。顺着这三个接口公共字段填充的价值会被完整体现。分页查询的接口设计比较有讲究。需求是前端传入page、pageSize、name按名称模糊搜索、categoryId按分类过滤后台返回菜品列表数据并且每行菜品要带上分类名称。因为分页要返回分类名称而dish表里只有categoryId没有categoryName所以要么写SQL联查category表要么在Service层手动补查。苍穹外卖课程里选择了后者先查分页数据再遍历列表逐个查出分类名称封装成DishVO。我最初偷懒直接用循环联查结果被一个细节坑了直接用Dish实体返回给前端时多出来的categoryName字段根本不存在Jackson序列化时会漏掉这个字段前端拿不到分类名。正确做法是新建一个DishVO继承Dish或包含Dish的属性额外加一个categoryName字段。因为Java是单继承我一般选择让DishVO扩展DishData public class DishVO extends Dish { private String categoryName; }这之后再用BeanUtils.copyProperties(dish, dishVO)拷贝属性再给dishVO.setCategoryName(...)。分页查询的Service实现大致如下public PageResult pageQuery(DishPageQueryDTO queryDTO) { PageDish page PageHelper.startPage(queryDTO.getPage(), queryDTO.getPageSize()); dishMapper.pageQuery(queryDTO.getCategoryId(), queryDTO.getName()); PageDishVO voPage new Page(page.getTotal(), page.getPages()); // 遍历page.getResult()把Dish转成DishVO补上分类名 }这里直接用PageHelper分页插件SQL在Mapper里写动态条件。分页插件自动生成count查询和limit语句用起来还是很顺手。页面大小、当前页这些参数在查询前要校验一下page传0或者负数会有问题至少做一层安全兜底。启售停售接口是菜品管理里一个状态切换功能非常直白PostMapping(/status/{status}) public ResultString startOrStop(PathVariable Integer status, Long id) { dishService.startOrStop(status, id); return Result.success(); }Service实现里只要updateDishStatusById把dish.status字段改成传入的status值。这个动作就会触发MyBatis-Plus的update填充逻辑updateTime和updateUser会被自动刷新。你根本不用手动set updateUser只要BaseContext里拿到了当前登录用户IDMetaObjectHandler就帮你把活干了。这就是公共字段填充带来的直接体感做一个新接口时几乎不用关心操作人是谁代码自然就准确。修改菜品接口则类似新增接口的反过程先更新dish主表再对比处理口味列表。常规做法是先删除该菜品原有的所有口味记录再重新插入前端提交的口味列表。这种“先删后插”的方案虽然多执行几次SQL但逻辑够简单不会出现漏删、错改的问题。真实生产环境里如果口味数据特别多可以考虑增量对比更新但对于一个餐饮管理系统的后端来说先删后插是完全可接受的工程决策。6. 常见问题与排查技巧整个Day3实操下来最容易出问题的集中在五六个点。我这里把踩过的坑和排查思路整理成一张速查表遇到问题直接对着找。现象可能原因排查思路与解决方案自动填充没生效公共字段为null实体类没加TableField注解或MetaObjectHandler没注册成Bean检查实体类字段注解确认MyMetaObjectHandler上有Component检查setFieldValByName用的是属性名而非列名createUser一直为null其他字段正常拦截器或切面没把用户ID存入BaseContext在拦截器里打印日志确认解析出的用户ID确认BaseContext.setCurrentId在业务代码之前执行菜品新增后dish_flavor表没有数据前端提交的flavors为空或者flavors变量名不对后端打印dishDTO确认JSON字段名是否与DTO属性一致检查判空逻辑是否错杀空数组分页查询返回的categoryName为null查询遍历时没正确封装DishVO用BeanUtils.copyProperties后再setCategoryName确认DishVO继承了Dish修改功能不生效但报错提示字段不存在SQL里的字段名写错或实体类属性名不一致打开MyBatis SQL日志复制实际执行语句到数据库手工执行定位问题前端调用新增接口报400JSON格式不对或缺少RequestBody检查请求头Content-Type是否为application/json确认Controller方法参数上有RequestBody除了表格里的内容再补充几个实操细节。第一个是SQL日志。苍穹外卖的application.yml里一定要把MyBatis的日志级别调成debuglogging: level: com.sky: debug这样控制台能看到MyBatis执行的每条SQL和参数占位符排查“为什么SQL没带update_user”这类问题时非常直观。我之前定位公共字段填充失效就是靠看控制台SQL日志发现setFieldValByName属性名写错了。第二个是关于ThreadLocal的值类型。BaseContext里存的是Long但实际用户ID在数据库里用的是BIGINTJava侧对应Long没问题。如果你在项目里把用户ID定义成Integer那就要注意类型一致性setFieldValByName时如果类型对不上会静默失败。第三个是事务回滚的边界。saveWithFlavor里有Transactional但如果方法内部自调用事务注解会失效。比如在同一个类里一个没事务的方法调用另一个有事务的方法Spring代理机制导致事务不生效。所以要有事务的方法最好独立在Service实现类里由Controller直接调用别搞自调用。第四个要注意的是状态字段设计。dish.status我用1表示启售、0表示停售。有些业务还会有2表示售罄之类的扩展状态做项目时最好以需求文档为准并且在前端选项和后端枚举取值上保持一致否则很容易出现“前端选了启售后端存了0”这类低级问题。苍穹外卖里启售是1停售是0跟着项目走就行。7. 我的实操心得与扩展建议Day3做完最直接的感受是公共字段填充带来的不只是省几行set而是让代码语义更清晰。Service层的核心逻辑只关注业务本身审计信息这类横切关注点被集中处理后期维护时想改公共字段的规则只要去MetaObjectHandler改一处不用在所有Service里大海捞针。这个设计模式值得刻进肌肉记忆。菜品模块本身也是很好的学习样本。我从这个模块第一次体会到DTO和VO的分工价值DTO管进来、VO管出去、实体管持久化。很多人做项目时嫌类多一个实体打天下结果接口一多实体被塞满各种不存在的字段最终变成一个大杂烩。正确做法是在项目前期就把每层的模型职责约定好后面开发速度反而会越来越快。还有一个建议是关于关联表的插入策略。我在新增菜品时采用循环插入口味表在数据量小的场景下没有问题。但如果你在真实项目中遇到动辄上百条关联数据的场景就可以考虑MyBatis-Plus提供的批量插入能力或者写一个自定义的insertBatchSomeColumn方法一次insert多行。原理都一样先插入主表拿到主键再插入从表带上外键。理解了核心链路具体手段可以根据数据量灵活调整。接下来如果继续Day4大概率会做菜品的删除、修改套餐关联等。到时候公共字段填充依然会自动帮我们处理操作人和操作时间业务代码只需要关注自己那一小块逻辑就够了。这也正是这个项目有意思的地方前期把基建打好后面每一个业务模块的搭建都像在流水线上干活轻松很多。

相关新闻

LeetCode 189 轮转数组:五种解法与三次翻转原理详解

LeetCode 189 轮转数组:五种解法与三次翻转原理详解

1. 题目解读与考点分析1.1 题目到底在问什么LeetCode 第 189 题"轮转数组",给定一个整数数组nums,将数组中的元素向右轮转k个位置,其中k是非负数。举个例子就明白了:数组[1,2,3,4,5,6,7],k 3,轮…

2026/10/10 14:49:54 阅读更多 →
2024国赛C题农作物种植策略:贪心算法与价格弹性建模

2024国赛C题农作物种植策略:贪心算法与价格弹性建模

简介:一套2024年全国大学生数学建模竞赛C题《农作物的种植策略》一等奖完整方案,聚焦最优化建模与算法求解,适合备战国赛的本科生和建模团队参考。方案围绕2023年数据,结合贪心算法、优先队列、价格弹性与间作条件,系统…

2026/10/10 14:48:53 阅读更多 →
组合优化实战:Markowitz模型与遗传算法Python拆解

组合优化实战:Markowitz模型与遗传算法Python拆解

简介:面向金融量化与Python开发者的投资组合优化实战代码包,围绕Markowitz均值-方差模型与遗传算法两条主线,提供可运行的Python实现。内容聚焦风险约束下的资产配置:基于历史价格数据计算资产平均收益率与协方差矩阵,…

2026/10/10 14:48:53 阅读更多 →

最新新闻

编译原理课程实验全流程解析:从词法分析到目标代码优化

编译原理课程实验全流程解析:从词法分析到目标代码优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 15:43:22 阅读更多 →
TM4C1294低功耗供电方案:基于PCA9422 PMIC的完整设计

TM4C1294低功耗供电方案:基于PCA9422 PMIC的完整设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 15:43:22 阅读更多 →
Coze API 封装实战:单例模式、流式与轮询双模式解析

Coze API 封装实战:单例模式、流式与轮询双模式解析

简介:这是一份面向JavaScript开发者、尤其是需要为Web应用集成智能对话能力的研发人员所准备的Coze扣子API聊天机器人封装文档,重点解决API调用繁琐、会话状态难以维护、流式与轮询模式切换不便等问题。资源包内含1个docx文件,整体约18KB&…

2026/10/10 15:43:22 阅读更多 →
别让 fsearch 扫全盘:选择性索引三招,索引更快内存更省

别让 fsearch 扫全盘:选择性索引三招,索引更快内存更省

别让 fsearch 扫全盘:选择性索引三招,索引更快内存更省 【免费下载链接】fsearch Whole-disk file search for macOS: fuzzy names, typo tolerance, indexed content grep. ~1 ms over 8M files. 项目地址: https://gitcode.com/gh_mirrors/fsea/fsea…

2026/10/10 15:43:22 阅读更多 →
二叉树递归三板斧:翻转、对称、最小深度一次讲透

二叉树递归三板斧:翻转、对称、最小深度一次讲透

训练营第12天,三道题摆在一块儿的时候,我明显感觉到递归法在二叉树题目里那种“绕不开”的存在感:226.翻转二叉树、101.对称二叉树、111.二叉树的最小深度,题目看起来一个比一个短,但每一道都在逼你想清楚递归的终止条…

2026/10/10 15:43:22 阅读更多 →
基于Python+FaceNet的课堂签到系统:原理、实现与避坑指南

基于Python+FaceNet的课堂签到系统:原理、实现与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 15:42:20 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →