最新要闻
- 马斯克要自建“乌托邦小镇”:员工全部搬进去 自己当“镇长”
- 拒绝投影行业亮度虚标!Vidda官宣三色激光全家桶新品
- 环球最资讯丨新一轮国内油价将于17日迎来调整:有再度搁浅可能
- 【环球聚看点】彻底解决“刹车争议”!电商平台上线特斯拉脚部专用记录仪:全程摄像
- 天天视讯!GTX 1050 Ti就能跑!顽皮狗公布《最后生还者:Part 1》PC版配置要求
- 世界看点:自称12年驾龄 特斯拉Model X车主在线维权:踩刹车没反应加速撞柱子
- 天天观察:苹果古典音乐软件已上架:Apple Music会员免费用!中国市场随后推出
- 当前资讯!明基推出首款48寸OLED电竞显示器:4K 120Hz、90W反向供电
- 《生化危机4:重制版》PS5版疑似已偷跑 小心剧透啊
- 世界百事通!illustrator学习心得体会(illustrator序列号)
- 65寸4K大屏电视不到2000元 LCD白菜价即将结束:3月价格上涨10%
- 环球微头条丨最强AI再次进化 ChatGPT下周升级GPT-4:支持视频了
- 【环球聚看点】免费玩!《生化危机4:重制版》体验版上线:不限时、不限次
- 当前滚动:德国电动空中出租车Lilium jet完成测试:时速250km/h 全机36个电风扇
- 杀疯了!长安深夜放大招 购车百亿补贴:深蓝SL03直降2.2万
- 世界观点:佳兆业成今年首家复牌出险房企
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
面向状态机编程:复杂业务逻辑应对之道
作者:京东零售 樊思国
一、背景
在研发项目中,经常能遇到复杂的状态流转类的业务场景,比如游戏编程中NPC的跳跃、前进、转向等状态变化,电商领域订单的状态变化等。这类情况其实可以有一种优雅的实现方法:状态机。如下图,是操作系统对进程调度的状态机:图 操作系统进程调度状态机
二、实现方式
面对以上场景,通常情况下的实现有以下几种,下面分别比较它们适用范围和优缺点:
(资料图片仅供参考)
2.1 if/else
优点:实现简单、直观。
缺点:状态多了代码可读性,业务与状态判断深度耦合,维护和扩展困难。
2.2 状态模式
状态模式类图及使用方式如下:
public class StatePatternDemo { public static void main(String[] args) { Context context = new Context(); StartState startState = new StartState(); startState.doAction(context); System.out.println(context.getState().toString()); StopState stopState = new StopState(); stopState.doAction(context); System.out.println(context.getState().toString()); }}
优点:状态单独实现,可读性比if/else好。
缺点:扩展状态需增加状态类,状态多了会出现很多状态类;并没有完全实现状态与业务解耦,不利于维护和了解整个系统状态全貌。
2.3 有限状态机
优点:严谨的数学模型,状态转移和业务逻辑基于事件完全解耦,能看到整个系统状态全貌便于维护和扩展。
缺点:需引入状态机实现方式,具备一定理解成本。
三、有限状态机
3.1 定义
有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机(英语:finite-state automaton,缩写:FSA),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。
3.2 关键概念
- 状态State:一般在状态转移图中用圆圈表示。
- 事件Event:表示从一种状态迁移到另一种状态的触发机制。对应状态转换图中的箭头部分。
- 动作Action: 表示状态转换后,执行的动作,但不是必须的,也可以状态转换后不执行任何动作。
- 转移Transition:表示状态转换,从原始状态迁移到目的状态的一个过程。
- 条件Guard:表示发生状态转移需满足的条件。
3.3 技术选型
在Java项目中,比较常用的有Spring Statemachine和Squirrel-foundation。
框架 | 优点 | 缺点 |
---|---|---|
Spring Statemachine | 基于Spring生态,社区强大。功能完备,支持多种状态机配置和持久化方式。 | 较为重量级,额外功能多。单例模式状态机不保证线程安全,只能通过工厂模式创建新的状态机实例实现,对性能有一定影响。 |
Squirrel-foundation | 轻量级实现,状态机的创建开销小。便于二次改造,实现定制业务。 | 社区没有spring活跃。特殊约定较多。 |
综上,在下面的项目中,由于团队使用SpringBoot作为开发框架,并且项目不涉及高并发场景,故选择Spring Statemachine。
四、项目实战
在现实项目中,碰到多种状态转换的复杂业务流程,可以通过以下几个步骤进行分级,逐步将产品需求清晰的实现出来:
4.1 需求背景
零售采销在维护SKU物流属性(长、宽、高和重量)时,会因为和物流仓库侧实际属性存在不一致的情况,导致带来物流成本的偏差。为解决这个问题,需设计一个系统供采销通过操作SKU的方式,将属性下发给物流侧审核,得到最终的准确物流属性。在对SKU进行审核操作的过程中,分别存在未操作、任务下发中、下发失败、已审核、自行联系供应商和挂起6种状态(状态转换详见4.2),考虑到这些状态转换的条件分布在不同的场景下,处于对可维护性和扩展性的考虑,采用状态机实现该需求。
4.2 状态转换图
通过梳理状态转换关系,画出状态转换图如下:
SKU属性审核状态转换图
4.3 配置状态机
4.3.1 定义状态枚举
public enum SkuStateEnum { /** * 未操作 */ INIT(0, "未操作"), /** * 任务下发中 */ TASK_DELIVERY(1, "任务下发中"), /** * 下发失败 */ DELIVERY_FAIL(2, "下发失败"), /** * 复核中 */ RECHECKING(3, "复核中"), /** * 已复核 */ RECHECKED(4, "已复核"), /** * 自行联系供应商 */ CONCAT_SUPPLIER(5, "自行联系供应商"), /** * 挂起 */ SUSPEND(6, "挂起"); /** * 状态代码 */ private Integer state; /** * 描述信息 */ private String desc; SkuStateEnum(Integer state, String desc) { this.state = state; this.desc = desc; } public static SkuStateEnum getByState(Integer state) { for (SkuStateEnum skuStateEnum : SkuStateEnum.values()) { if (skuStateEnum.getState().equals(state)) { return skuStateEnum; } } return null; } public Integer getState() { return state; } public String getDesc() { return desc; }}
4.3.2 定义事件枚举
public enum SkuAttrEventEnum { /** * 调用OMC属性采集接口成功 */ INVOKE_OMC_ATTR_COLLECT_API_SUCCESS, /** * 调用OMC属性采集接口失败 */ INVOKE_OMC_ATTR_COLLECT_API_FAIL, /** * 调用OMC下发查询接口并已经生成采集单 */ INVOKE_OMC_SKU_DELIVERY_API_GATHER_FINISH, /** * 调用OMC下发查询接口失败 */ INVOKE_OMC_SKU_DELIVERY_API_FAIL, /** * OMC的MQ返回SKU属性已变更 */ MQ_OMC_SKU_ATTR_CHANGED, /** * 调用商品中台jsf接口,返回SKU属性已变更 */ INVOKE_SKU_ATTR_API_CHANGED, /** * 京东有库存 */ HAS_JD_STOCK, /** * 京东无库存,VMI有库存 */ NO_JD_STOCK_HAS_VMI_STOCK, /** * 京东和VMI均无库存 */ NO_JD_STOCK_NO_VMI_STOCK, /** * 上传并复核 */ UPLOAD_AND_RECHECK;}
4.3.3 配置状态机
@Configuration@EnableStateMachineFactory@Slf4jpublic class SkuAttrStateMachineConfig extends StateMachineConfigurerAdapter { /** * 配置状态 * * @param states * @throws Exception */ @Override public void configure(StateMachineStateConfigurer states) throws Exception { states.withStates().initial(SkuStateEnum.INIT) .states(EnumSet.allOf(SkuStateEnum.class)); } @Override public void configure(StateMachineConfigurationConfigurer config) throws Exception { config.withConfiguration().listener(listener()).autoStartup(false); } /** * 配置状态转换和事件的关系 * * @param transitions * @throws Exception */ @Override public void configure(StateMachineTransitionConfigurer transitions) throws Exception { transitions.withExternal().source(SkuStateEnum.INIT).target(SkuStateEnum.TASK_DELIVERY) .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_SUCCESS) .action(ctx -> { log.info("[调用OMC属性采集接口成功],状态变更:{} -> {}.", SkuStateEnum.INIT.getDesc(), SkuStateEnum.TASK_DELIVERY.getDesc()); }) .and() .withExternal().source(SkuStateEnum.INIT).target(SkuStateEnum.DELIVERY_FAIL) .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_FAIL) .action(ctx -> { log.info("[调用OMC属性采集接口失败],状态变更:{} -> {}.", SkuStateEnum.INIT.getDesc(), SkuStateEnum.DELIVERY_FAIL.getDesc()); }) .and() .withExternal().source(SkuStateEnum.INIT).target(SkuStateEnum.CONCAT_SUPPLIER) .event(SkuAttrEventEnum.NO_JD_STOCK_HAS_VMI_STOCK) .action(ctx -> { log.info("[京东无库存,VMI有库存],状态变更:{} -> {}.", SkuStateEnum.INIT.getDesc(), SkuStateEnum.CONCAT_SUPPLIER.getDesc()); }) .and() .withExternal().source(SkuStateEnum.INIT).target(SkuStateEnum.SUSPEND) .event(SkuAttrEventEnum.NO_JD_STOCK_NO_VMI_STOCK) .action(ctx -> { log.info("[京东和VMI均无库存],状态变更:{} -> {}.", SkuStateEnum.INIT.getDesc(), SkuStateEnum.SUSPEND.getDesc()); }) .and() .withExternal().source(SkuStateEnum.SUSPEND).target(SkuStateEnum.TASK_DELIVERY) .event(SkuAttrEventEnum.HAS_JD_STOCK) .action(ctx -> { log.info("[京东有库存],状态变更:{} -> {}.", SkuStateEnum.SUSPEND.getDesc(), SkuStateEnum.TASK_DELIVERY.getDesc()); }) .and() .withExternal().source(SkuStateEnum.SUSPEND).target(SkuStateEnum.RECHECKING) .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_SUCCESS) .action(ctx -> { log.info("[调用OMC属性采集接口成功],状态变更:{} -> {}.", SkuStateEnum.SUSPEND.getDesc(), SkuStateEnum.RECHECKING.getDesc()); }) .and() .withExternal().source(SkuStateEnum.SUSPEND).target(SkuStateEnum.CONCAT_SUPPLIER) .event(SkuAttrEventEnum.NO_JD_STOCK_HAS_VMI_STOCK) .action(ctx -> { log.info("[京东无库存,VMI有库存]:{} -> {}.", SkuStateEnum.SUSPEND.getDesc(), SkuStateEnum.CONCAT_SUPPLIER.getDesc()); }) .and() .withExternal().source(SkuStateEnum.TASK_DELIVERY).target(SkuStateEnum.RECHECKING) .event(SkuAttrEventEnum.INVOKE_OMC_SKU_DELIVERY_API_GATHER_FINISH) .action(ctx -> { log.info("[调用OMC下发查询接口并已经生成采集单]:{} -> {}.", SkuStateEnum.TASK_DELIVERY.getDesc(), SkuStateEnum.RECHECKING.getDesc()); }) .and() .withExternal().source(SkuStateEnum.TASK_DELIVERY).target(SkuStateEnum.RECHECKED) .event(SkuAttrEventEnum.MQ_OMC_SKU_ATTR_CHANGED) .action(ctx -> { log.info("[OMC的MQ返回SKU属性已变更]:{} -> {}.", SkuStateEnum.TASK_DELIVERY.getDesc(), SkuStateEnum.RECHECKED.getDesc()); }) .and() .withExternal().source(SkuStateEnum.DELIVERY_FAIL).target(SkuStateEnum.TASK_DELIVERY) .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_SUCCESS) .action(ctx -> { log.info("[调用OMC属性采集接口成功]:{} -> {}.", SkuStateEnum.DELIVERY_FAIL.getDesc(), SkuStateEnum.TASK_DELIVERY.getDesc()); }) .and() .withExternal().source(SkuStateEnum.CONCAT_SUPPLIER).target(SkuStateEnum.RECHECKED) .event(SkuAttrEventEnum.INVOKE_SKU_ATTR_API_CHANGED) .action(ctx -> { log.info("[调用商品中台jsf接口,返回SKU属性已变更]:{} -> {}.", SkuStateEnum.CONCAT_SUPPLIER.getDesc(), SkuStateEnum.RECHECKED.getDesc()); }); } /** * 全局监听器 * * @return */ private StateMachineListener listener() { return new StateMachineListenerAdapter() { @Override public void transition(Transition transition) { //当状态的转移在configure方法配置中时,会走到该方法。 log.info("[{}]状态变更:{} -> {}", transition.getKind().name(), transition.getSource() == null ? "NULL" : ofNullableState(transition.getSource().getId()), transition.getTarget() == null ? "NULL" : ofNullableState(transition.getTarget().getId())); } @Override public void eventNotAccepted(Message event) { //当发生的状态转移不在configure方法配置中时,会走到该方法,此处打印error日志,方便排查状态转移问题 log.error("事件未收到: {}", event); } private Object ofNullableState(SkuStateEnum s) { return Optional.ofNullable(s) .map(SkuStateEnum::getDesc) .orElse(null); } }; }}
4.4 业务逻辑处理
4.4.1 构建状态机
对每个sku的操作,通过状态机工厂stateMachineFactory.getStateMachine
//注入状态机工厂实例@Autowiredprivate StateMachineFactory stateMachineFactory;//构建状态机public StateMachine buildStateMachine(String skuId) throws BusinessException { if (StringUtils.isEmpty(skuId)) { return null; } StateMachine stateMachine = null; try { //从DB中获取当前skuId对应的状态 LambdaQueryWrapper query = Wrappers.lambdaQuery(SkuAttrRecheckState.class); query.eq(SkuAttrRecheckState::getSkuId, skuId); SkuAttrRecheckState skuAttrRecheckState = this.baseMapper.selectOne(query); SkuStateEnum skuStateEnum = SkuStateEnum.getByState( skuAttrRecheckState == null ? SkuStateEnum.INIT.getState() : skuAttrRecheckState.getState()); //从状态机工厂获取一个状态机 stateMachine = stateMachineFactory.getStateMachine(skuId); stateMachine.stop(); //配置状态机参数 stateMachine.getStateMachineAccessor().doWithAllRegions(sma -> { //配置状态机拦截器,当状态发生转移时,会走到该拦截器中 sma.addStateMachineInterceptor(new StateMachineInterceptorAdapter() { @Override public void preStateChange(State state, Message message, Transition transition, StateMachine stateMachine, StateMachine rootStateMachine) { //获取状态转移时,对应的SKU详细信息 SkuAttrRecheckState result = JSON.parseObject( String.class.cast(message.getHeaders().get(JSON_STR)), SkuAttrRecheckState.class); //更新状态机转移后的状态(来自于4.3.3中的配置) result.setState(state.getId().getState()); //将状态机转移后的状态写入DB LambdaQueryWrapper query = Wrappers.lambdaQuery(SkuAttrRecheckState.class); query.eq(SkuAttrRecheckState::getSkuId, result.getSkuId()); if (baseMapper.exists(query)) { UpdateWrapper updateQuery = new UpdateWrapper<>(); updateQuery.eq("sku_id",result.getSkuId()); log.info("更新状态信息:{}", JSON.toJSONString(result)); baseMapper.update(result, updateQuery); } else { log.info("写入状态信息:{}", JSON.toJSONString(result)); baseMapper.insert(result); } } }); //将状态机的初始状态配置为DB中的skuId对应状态 sma.resetStateMachine(new DefaultStateMachineContext( skuStateEnum, null, null, null)); }); //启动状态机 stateMachine.start(); } catch (Exception e) { log.error("skuId={},构建状态机失败.", skuId, e); throw new BusinessException("状态机构建失败", e); } return stateMachine; }
4.4.2 封装事件
public synchronized Boolean sendEvent(StateMachine stateMachine, SkuAttrEventEnum skuAttrEventEnum, SkuAttrRecheckState skuAttrRecheckState) throws BusinessException { try { //发送事件,并将需要传递的信息写入header stateMachine.sendEvent(MessageBuilder.withPayload(skuAttrEventEnum) .setHeader(SKU_ID, skuAttrRecheckState.getSkuId()) .setHeader(STATE, skuAttrRecheckState.getState()) .setHeader(JSON_STR, JSON.toJSONString(skuAttrRecheckState)) .build()); } catch (Exception e) { log.error("发送事件失败", e); throw new BusinessException("发送事件失败", e); } return true; }
4.4.3 业务逻辑应用
当用户在界面上对“未操作”状态的SKU点击审核按钮时,会调用物流OMC接口将SKU属性下发到物流侧,当下发成功时,状态会转换为“任务下发中”,当调用接口失败,则会将状态转换为"下发失败",核心代码如下:
public Boolean recheck(List skuIds) throws BusinessException { if (CollectionUtils.isEmpty(skuIds)) { log.error("参数错误,sku列表为空"); throw new BusinessException("参数错误,sku列表为空"); } List skuDetails = skuAttrExceptionDetailMapper.queryBySkuIdList(skuIds); if (CollectionUtils.isEmpty(skuDetails)) { log.error("查询sku异常明细结果集为空,skuIds={}", JSON.toJSONString(skuIds)); return false; } for (SkuAttrExceptionDetail detail : skuDetails) { if (detail.getState() != SkuStateEnum.INIT.getState()) { log.info("{}不是未操作状态sku不进行复核", detail.getSkuId()); continue; } //构建SKU对应的状态机 StateMachine stateMachine = buildStateMachine(detail.getSkuId()); SkuAttrRecheckState skuAttrRecheckState = DomainBuilderUtil.buildSkuAttrRecheckState(detail); //判定库存并发送事件 adjustAndSendEvents(detail, stateMachine, skuAttrRecheckState); } return true; }public void adjustAndSendEvents(SkuAttrExceptionDetail detail, StateMachine stateMachine, SkuAttrRecheckState skuAttrRecheckState) throws BusinessException { //1、京东有库存,调用物流属性接口,下发SKU属性 if (detail.getSpotInventoryQtty() > 0) { invokeOmcSkuAttrCollectApiAndSendEvent(detail, stateMachine, skuAttrRecheckState); return; } //2、京东无库存,有VMI库存 if (detail.getSpotInventoryQtty() <= 0 && detail.getVmiInventoryQtty() > 0) { sendEvent(stateMachine, SkuAttrEventEnum.NO_JD_STOCK_HAS_VMI_STOCK, skuAttrRecheckState); return; } //3、京东和VMI均无库存 if (detail.getSpotInventoryQtty() <= 0 && detail.getVmiInventoryQtty() <= 0) { sendEvent(stateMachine, SkuAttrEventEnum.NO_JD_STOCK_NO_VMI_STOCK, skuAttrRecheckState); return; } }private void invokeOmcSkuAttrCollectApiAndSendEvent(SkuAttrExceptionDetail detail, StateMachine stateMachine, SkuAttrRecheckState skuAttrRecheckState) throws BusinessException { DistrustAttributeGatherRequest request = RequestUtil.buildOmcAttrCollectRequest(detail, reqSource); try { if (jsfInvokeService.invokeSkuAttrCollectApi(request)) { sendEvent(stateMachine, SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_SUCCESS, skuAttrRecheckState); } else { sendEvent(stateMachine, SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_FAIL, skuAttrRecheckState); } } catch (Exception e) { log.error("调用物流Sku属性采集接口错误,request={}", JSON.toJSONString(request), e); sendEvent(stateMachine, SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_FAIL, skuAttrRecheckState); } }
五、总结
本文通过介绍有限状态机,并结合具体项目,通过状态机的应用将状态和业务逻辑解耦,便于简化复杂业务逻辑,降低理解成本。另外,状态机的应用场景对于那种复杂的流程也同样适合,可以在实际项目中根据状态机核心要素梳理出隐性的状态转换关系,从而将一个复杂的流程问题转换为状态机的模式问题,再利用状态机模式来实现,可以有助于我们优雅的解决更广泛的复杂业务问题。
关键词:
-
世界微动态丨在java中String类为什么要设计成final?Java面试常见问题
2023Java面试题最经典的问题之一了,非常经典的Java基础知识,一定要学会!在Java中,String类被设计成f...
来源: 面向状态机编程:复杂业务逻辑应对之道
多光源渲染方案 - Many Lights Sampling
世界微动态丨在java中String类为什么要设计成final?Java面试常见问题
报道:LoadRunner——脚本优化(二)
马斯克要自建“乌托邦小镇”:员工全部搬进去 自己当“镇长”
拒绝投影行业亮度虚标!Vidda官宣三色激光全家桶新品
环球最资讯丨新一轮国内油价将于17日迎来调整:有再度搁浅可能
【环球聚看点】彻底解决“刹车争议”!电商平台上线特斯拉脚部专用记录仪:全程摄像
当前播报:简单到复杂:C#拷贝文件的3种方法
环球热门:对LSTM应用于图像的初步理解
【数论与组合数学 1】数论简介、素数、算数基本定理
JS回调地狱
天天视讯!GTX 1050 Ti就能跑!顽皮狗公布《最后生还者:Part 1》PC版配置要求
世界看点:自称12年驾龄 特斯拉Model X车主在线维权:踩刹车没反应加速撞柱子
天天观察:苹果古典音乐软件已上架:Apple Music会员免费用!中国市场随后推出
当前资讯!明基推出首款48寸OLED电竞显示器:4K 120Hz、90W反向供电
《生化危机4:重制版》PS5版疑似已偷跑 小心剧透啊
世界百事通!illustrator学习心得体会(illustrator序列号)
工厂模式进阶用法,如何动态选择对象?
迷你天猫商城代码审计
焦点简讯:K8S 性能优化 - K8S APIServer 调优
【全球聚看点】Prompt-Engineering-Guide 学习摘要1
前端设计模式——装饰者模式
65寸4K大屏电视不到2000元 LCD白菜价即将结束:3月价格上涨10%
环球微头条丨最强AI再次进化 ChatGPT下周升级GPT-4:支持视频了
【环球聚看点】免费玩!《生化危机4:重制版》体验版上线:不限时、不限次
当前滚动:德国电动空中出租车Lilium jet完成测试:时速250km/h 全机36个电风扇
杀疯了!长安深夜放大招 购车百亿补贴:深蓝SL03直降2.2万
世界快看点丨分享几个常用的运维 shell 脚本
世界观点:佳兆业成今年首家复牌出险房企
全球球精选!一座河南小县城的全球钻石生意爆火:价格不到天然的1/3
我国再次成功发射一箭双星:天绘六号A/B星顺利进入预定轨道
当前快看:资助8年的女生毕业放弃工作 嫁给有钱人成家庭主妇 资助人:失眠好几天
天天通讯!上班族如何备考公务员_如何备考公务员
全球焦点!读Java性能权威指南(第2版)笔记12_堆内存中
怎么处理消息重发的问题?
每日热点:HEU KMS Activator 30.0.0全能系统数字许可激活工具(全新体验纪念版)
环球热议:用盆吃10袋泡面男子火了 回应月薪2万邀约:浇完家里18亩地再说
实时:第127篇:异步函数(async和await)练习题(异步,消息队列)
焦点!【LeetCode回溯算法#05】分割回文串(复习双指针判断回文以及substr函数使用记录)
今日热议:【django-vue】celery延迟任务、定时任务 django中使用celery 秒杀功能 双写一致性 首页轮播图定时更新 课程前端页面
世界热头条丨关于JAVA泛型数组类型擦除引发的问题及解决方案
环球今日讯!Mint安装MySQL
快讯:苹果iPhone 14黄色款预售:全新配色不加价 5999元起
天天微速讯:租客辞职要搬走被房东介绍工作 当事人感动
即时焦点:一辆腾势敢占两个充电桩:真不怕挨揍?
“常温超导”把A股都晃晕了!真要改变我们的生活?
《霍格沃茨之遗》渗人面部Bug:玩家看了起鸡皮疙瘩
全球短讯!美环保署署长:将在今年最终确定甲烷排放规则
21世纪20年代是几几年(21世纪20年代是几几年)
爆肝两万字,详解fastdfs分布式文件系统
初识rollup 打包、配置vue脚手架
世界热议:62.类模板
当前热文:C语言——可变参函数
陕西招聘会现3万月薪岗位学生排长龙 招聘人员:半天收简历150份
当前动态:供不应求!真我GT Neo5 1TB版真香:二手用户也抢着要
“山药成了精”?男子买到奇葩山药外形酷似人脚掌
每日视点!快速读懂Redis分布式锁的实现和原理
美少女三消游戏 《Mirror 2: Project X》开发组宣布破产解散
酒店回应到211大学招服务员:符合流程 面向所有高校毕业生
快看点丨京东CEO徐雷:百亿补贴效果超过预期、要做天天低价
【新要闻】比亚迪加入降价大军!宋Pro DM-i限时优惠:88元折扣6888元
焦点简讯:耳机煲机一般要煲多久_耳机煲机方法是什么?买回来新耳机要怎样煲?耳机要煲多久?
世界热点评!前端如何相对优雅管理api
全球速看:浙四医院官网招聘2021_浙四医院官网
环球观点:京东2022年收入超1万亿:“百亿补贴”会一直有!
简约时尚 健身备一件:361°新款轻薄速干衣39元冲量
越来越卖不动了!最畅销十款数码相机一览:索尼成赢家 第一性价比绝
全球通讯!Intel的大小核CPU架构:最终还是把一些老游戏坑了
往返近1000元!景区回应坐滑竿上山按斤收费:网友力挺明码标价
天天实时:【翻译】发布 .NET 8 Preview 1
前沿资讯!海洋风筝爆火 一周暴涨498%超过秋裤!网友:春天的信号
天天消息!甜香丝滑 旺旺邦德低脂轻乳咖啡官方狂促:合2元一瓶
《银河护卫队3》导演回击网友言论:确定选角不因其是黑人
世界热议:东风系引发车市价格大战!纯电宝马i3终端大促销:最大降幅超10万
每日热点:日本原药温和驱蚊配方:超威电热蚊香液3瓶1器14.9元发车
快讯:uni-popup 遮不住头部标题的解决办法
环球焦点!网友晒空荡新房 各大品牌疯狂随份子 开局一套房其他全靠送
快播:女子在地铁上脱鞋抠脚死皮掉一地 杭州地铁回应:列车到站会打扫
世界速递!为拯救者Y9000P 2023量身打造:联想推出新140W氮化镓适配器
每日看点!富士康否认清退临时工拆除流水线:运转正常
全球即时:美国一特斯拉撞上消防车造成一死一伤 现场惨烈!调查结果让人心痛
Envisics获得5000万美元C轮融资
当前滚动:(数据库系统概论|王珊)第十一章并发控制-第二、三、四节:封锁、封锁协议活锁和死锁
每日焦点!「中华田园敏捷开发」,是老板无能还是程序员无力?
【世界聚看点】探究SMC局部代码加密技术以及在CTF中的运用
记录--Vue自定义指令实现加载中效果v-load(不使用Vue.extend)
【焦点热闻】十分钟读懂火山引擎 DataLeap 数据治理实践
老头环壶头哥:击败女武神超4千次 期待DLC到来
天天资讯:儿子沉迷手机爸爸帮请假“逼”他连玩17小时:效果很好
焦点消息!供应iPhone 15的OLED屏幕漏光?国产面板一哥京东方回应:不予评价
当前关注:纵享丝滑 回味愉悦:德芙巧克力37.5元/斤(官价5折)
每日速看!成龙进组20天把半年的封闭用完了!新电影《龙马精神》4月上映:有吴京参演
每日视点!通过案例讲解python循环语句
【全球独家】基于应用理解的协议栈优化
关注:串口登录提示"Login incorrect"
热推荐:节能降耗 | AIRIOT智慧电力综合管理解决方案
实时:C++笔记--函数、预处理
【天天播资讯】为啥人一上车就爱睡觉?原来是被“催眠”了
全球观察:95后大厂女生裸辞开麻将馆当保洁:很享受自由和成就感