充电桩项目总结
介绍一下充电桩项目
我们这个充电桩项目是为新能源车车主提供充电服务的SaaS,我们充电的方式大致分为两种,用户手动扫码充电和即插即充模式,两种的区别就是用户需不需要进行扫码的步骤,手动扫码充电就需要用户在APP(或小程序)进行手动的操作充电流程,即插即充则只需要用户提前在我们的APP(或小程序)提前进行车的绑定,后续不需要再操作APP(或小程序),插枪就自动充电。
ps:小程序还是APP,看你自己怎么boxing的,推荐boxing小程序,微信小程序搜充电,可以搜到一大堆充电的小程序,建议自己最好使用一下,看一下你找的小程序,有没有即插即充的功能,如果没有,就建议只说扫码充电,把即插即充的方式作为项目可优化点提出,可以很加分。
什么是Saas
SaaS(Software as a Service,软件即服务)是一种通过云端向用户提供软件功能的服务模式。用户无需自行部署服务器、安装软件或维护系统,只需通过浏览器或移动端即可访问服务端应用。SaaS 通常采用多租户架构,由平台统一管理数据、安全、版本升级与运维,企业按订阅方式使用系统功能。相比传统本地部署软件,SaaS 具有上线快、成本低、可扩展性强、持续迭代快等优势。
更简单一点说:
SaaS 是“多租户模式”的云端软件,一个系统、一套代码,通过账号隔离数据,就能同时服务成百上千个企业客户。用户不需要部署,只要开通账号就可以直接用。
项目组多少人?人员构成?开发周期?

项目总共分三个阶段:
- 第1阶段:4个月,实现项目基础业务功能
- 第2阶段:2个月,实现项目增强功能。
- 第3阶段:维护期,每周都会更新迭代一些功能,周四发版。
项目部署流程?
一般常用的部署方式就是蓝绿发布和滚动发布。
滚动发布:先部分机器进行部署,作为灰度环境,然后通过线上生产流量对灰度进行验证,验证完后铺开。

蓝绿发布:两套一样的生产集群,先把新版本发到一个集群上,然后通过负载均衡做引流到这个集群上作为灰度环境,通过灰度验证后,将流量完全迁移过来,另一个集群就可以弹性收缩了。(两组服务器)

充电桩项目的主链路
用户使用我们的小程序进行扫码,扫码后我们这边会进行二维码的解析,获取到枪的信息和运营商的信息,然后进行启动充电,启动充电的方式需要根据当前运营商的口径判断,分为同步启动和异步启动,同步启动则直接取接口返回值即可,异步启动则需要等待运营商的异步回调结果,同时运营商会提供一个查询充电状态的接口,防止异步回调的结果过慢,我们有一个天花板的定时任务,每30秒进行一次主动的查询充电状态的变化,当充电状态变化到充电中后,运营商也会主动推送充电中的过程数据,类似于心跳机制,以确保充电过程的稳定,当充电完成后,用户进行结束充电,结束充电也要根据运营商的口径分为同步和异步两种方式,结束后运营商会推送订单信息给我们,然后进行结算。
定时任务天花板中的天花板是什么?
天花板只是一个描述而已,因为“天花板”定时任务最开始的时候只有一个费控的功能,就是防止用户费用超过阈值而诞生的。指的就是用户不能超过这个费用的意思。
使用定时任务查询充电状态的频率是多少?运营商推送充电状态的频率是多少?
定时任务查询充电状态的频率是30s一次,运营商推送是根据桩的特性和运营商的规则来的,正常就是5分钟推送一次。
天花板功能是否是冗余的?
这个不存在冗余的,主要原因有两点
第一,三方的服务并不能保证一定就是稳定的,如果他们因为网络抖动或者服务问题导致没有推送或者延迟推送,我们就无法正确及时的获取到结果并且阻塞我们的流程,这种方式对我们来说很不友好,所以我们增加一个主动查询的功能,提高稳定性。
第二,天花板的功能不止是为了获得充电状态的变化和一些过程数据,还有很多其他的功能,比如计费策略,用户余额小于2块的时候,主动停止充电,防止认损等
你们分工是怎么分的?你负责哪一块?
我们根据业务和服务进行不同的**责任田(负责的模块/服务)**划分,根据业务,分为家充和聚合充,我们的主要服务有路由服务、能源服务、充电服务、设备服务、用户服务、支付服务、common服务(通用工具服务),还有一些小的业务服务,比如地锁服务、营销服务、运营平台服务、开站助手服务等。我作为聚合业务的责任田田主以及充电服务、用户服务的责任田田主,平时负责聚合相关的业务需求的评审,评审需求排期,人力管道等,输出整体设计,进行设计文档的编写和澄清,以及充电服务和用户服务日常的版本迭代、代码质量管理、线上问题巡检等。
分布式事务
分布式与微服务区别?
分布式系统:指通过网络连接多个独立的服务器(节点)共同完成某个任务的系统。目标是提升单机的性能瓶颈,通过分散计算、分散存储来提升整体的能力
例子:分布式数据库、分布式计算
分布式是技术手段,解决如何让多台机器协同工作
微服务:是一种软件架构,通过将单体应用拆分成多个小型、松耦合的服务,每个服务独立开发、部署;核心目的就是提高开发效率和降低维护成本
例子:电商系统拆分为订单服务、支付服务、积分服务等
微服务是架构设计,解决如何让代码和团队高效协作
分布式事务的概念
一次大的操作由不同的小操作组成,这些小操作分布在不同的服务器上属于不同的业务,分布式事务就要保证这些小操作要么全部成功,要么全部失败,来达到一致性的效果;本质上来说,分布式事务就是要保证不同数据库上的数据要一致
分布式事务的解决方案
强一致性:要求所有参与者在每个阶段全部执行完毕后,才能继续执行下一个阶段或者进行回滚,实现强一致性,一般有一个协调者来协调所有参与者什么时候提交,什么时候回滚;比如常见的XA规范的二阶段和三阶段提交
最终一致性:要求所有参与者到达最终阶段,要么全部成功,要么全部进行回滚操作,实现最终一致性,一般基于可靠消息的最终一致性,最大努力通知
Seata
Seata是一个阿里开源的分布式事务的解决方案,用在分布式系统中实现分布式事务,它的宗旨就是简化分布式事务的开发和管理,帮助解决分布式系统中的数据一致性问题。
因为Seata的开发者坚定地认为:一个分布式事务是由若干个本地事务组成的。所以他们给Seata体系的所有组件定义成了三种,分别是Transaction Coordinator(TC),Transaction Manager(TM)和Resource Manager(RM)
事务协调器(TC)董事长 :维护全局事务的运行状态,负责协调并驱动全局提交或回滚
事务管理器(TM)项目经理 :事务发起方,控制全局事务的范围,负责开启一个全局事务,并最终发起全局提交或回滚全局的决议
资源管理器(RM)员工 :事务参与方,管理本地事务正在处理的资源,负责向 TC 注册本地事务、汇报本地事务状态,接收 TC 的命令来驱动本地事务的提交或回滚

AT模式
这是一种无侵入的分布式事务解决方案,用户只需要关注自己的业务SQL,Seata框架会自动生成事务的二阶段提交和回滚操作。在一阶段,Seata会拦截业务SQL,解析SQL语义,找到要更新的业务数据,并保存快照数据和行锁。在第二阶段如果是提交,Seata只需清理数据;如果是回滚,则用快照数据还原业务数据

工作流程
一阶段RM的工作
- 注册分支事务
- 记录undo-log (数据快照)
- 执行业务sql并提交
- 报告事务状态
二阶段RM的工作
- 提交时RM的工作:删除undo-log即可
- 回滚时RM的工作:根据undo-log恢复数据
优缺点
AT模式优点:
- 一阶段完成直接提交事务,释放数据库资源,性能比较好
- 利用全局锁实现读写隔离
- 没有代码侵入,框架自动完成回滚和提交
AT模式的缺点:
- 两阶段之间属于软状态,它们执行过程的状态相互不知道,只知道最终状态,属于最终一致性
- 框架的快照功能会影响性能,但比XA模式好很多
- 没办法用于非事务型数据库
代码
【启动类】:在每个微服务的启动类上添加 @EnableAutoDataSourceProxy 注解,以开启Seata的数据源代理

【订单服务】

【账号服务】

TCC模式
工作模式
TCC模式通过明确的 Try 、 Confirm 、 Cancel 三个阶段来实现分布式事务的一致性,在每个参与者上执行自定义的业务逻辑。

TCC模式和AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复,而不借助于数据库。需要实现三个方法
- Try:资源的检测和预留
- Confirm:完成资源操作业务;要求Try成功Confirm一定要成功
- Cancel:预留资源释放,可以理解为try的反向操作
优缺点
TCC的优点
- 一阶段完成直接提交事务,释放数据库资源,性能好
- 相比AT模式,无需生成快照,无需使用全局锁,性能最强
- 不依赖数据库事务,而是依赖补偿操作,可以用于非事务性数据库(ES,Redis等)
TCC的缺点
- 有代码侵入,需要人为编写 try 、 Confirm 、和 Cancel接口,太麻烦
- 终一致性
- 考虑 Confirm 和 Cancel 失败情况,做好幂等处理
Sage模式
【用的少】

XA模式
【用得少】
利用两阶段提交牺牲一定的可用性,来保证强一致性

XA模式的优点
- 事务的强一致性,满足ACID原则
- 常用数据库都支持,实现简单,并且没有代码侵入
XA模式的缺点
- 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
- 依赖关系型数据库实现事务
AT模式和XA模式的区别是什么?

从上面对比来看,AT和XA的本质区别在于第一阶段有没有直接提交事务

本地消息表&事务消息&最大努力通知
本地消息表、事务消息、最大努力通知都是依赖于MQ去实现最终一致性的方案
本地消息表

【注意】
- 如果在1,2的步骤失败了,他们是在本地事务里面,可以直接进行回滚,后面的步骤都没走,数据是一致的
- 如果在3的时候失败,那么依赖我们的定时任务去扫本地消息表的数据,没成功投递的消息会重新进行投递
- 如果是在4,5失败,MQ的重试机制可以进行解决,定时任务也可以解决
- 如果是在5失败,其实这个时候两个业务数据已经一致了,只是本地消息表的状态没有修改,这个时候可以依赖定时任务去查询下游系统的状态,如果成功了,就直接修改消息状态就可以了
【优点】
- 可靠性高:基于本地消息表实现分布式事务的,可以将本地的消息持久化和本地业务逻辑操作,放到一个事务里面去进行原子性的提交,保证了这个消息的可靠性。
- 可扩展高:可以将消息的发送和本地事务的执行拆开,提高了可扩展性。
- 适用的范围很广:可以适用于不同的业务类型和场景,可以满足不场景的需求。
【缺点】
- 实现复杂度高: 需要设计复杂的事务协议和消息发送机制,并且你需要进行相应的异常处理和补偿操作。
- 系统性能有影响:需要把消息写入到本地消息表里面,并且需要定时任务扫描消息表进行消息发送,所以对性能会有一定的影响
- 回滚困难:如果你需要进行回滚,你需要很多的补偿操作,所以他不适用于那种需要回滚的场景,更适用于那些上游成功,下游必须成功的场景。
- 会有一些消息堆积和扫表慢的问题
事务消息
选用的MQ要支持事务消息,然后吧事务消息运用在业务当中,从一次消息拆分成两个half消息,并且提供一个反查接口
最大努力通知
顾名思义,就是尽量让消费者尽可能收到生产者的消息,这种方式是实现起来最简单的,因为不需要去保证消费者一定就能接收到,只要尽自己最大努力去通知就可以了。最多就是在发送的地方加一个重试机制。
缺点也很明显,可能会导致消息的重复和丢失。
这种就是适合一些一致性要求不是那么高的场景,即使消息丢了也无所谓的场景。比如:一些站内信的推送,运营商感知到到达城市,发送的一些欢迎短信等。
手写TCC(重点)
保证充电订单和充值订单的一致性
整体流程图
【Try阶段】

【confirm阶段】

【Cancel阶段】

状态机
【事务状态机】

【充电订单状态机】

【支付订单状态机】

利用本地消息表实现充电订单和积分的最终一致性
充电订单结算完成后,我们需要给用户进行积分的增加,积分可以用于充电或者商城的积分商品的兑换。
我们不希望积分的增加影响到我们的主流程,就引入了下面这种方案

设计模式
七大基本原则
单一职责原则
核心思想:每个类只负责一项功能,避免职责复杂导致的维护困难
具体实现:一个类只做一件事,当一个 OrderService 类同时负责“创建订单”、“计算价格”、“发送短信通知”。它们之间的耦合度太高,修改发送短信的逻辑,可能会影响创建订单或计算价格的逻辑,所以我们需要将这个类拆分成三个类,分别负责“创建订单”、“计算价格”、“发送短信通知”的逻辑。 OrderCreator 负责创建订单逻辑,PriceCalculator 负责计算价格逻辑,SmsNotifier 负责发送短信通知逻辑。修改发送短信通知逻辑的时候,只需要修改 SmsNotifier 里面的代码,其他类不受影响,降低维护成本
开放-封闭原则
核心思想
- 类、模板、方法等应该对扩展开放,对修改关闭
- 通过抽象和接口实现扩展,而非修改现有代码

反例中:用一个 Logger 类里面的log方法包含了“写文件”、“写数据库”等操作,我们可以发现它们的共同操作都是写,只不过是写在哪的问题,这样我们就可以将写这样一个操作抽离出来,提供一个接口,一个方法,对于写文件、写数据库关于写的操作都可以实现这个接口,实现对应方法
里氏替换原则
确保继承关系合理性,子类可以扩展父类功能但不能改变原有行为
接口隔离原则
避免创建臃肿庞大的接口,比如一个接口实现 加减乘除 的逻辑,应该分为四个接口处理相关逻辑
依赖倒置原则
高层模块不应该依赖低层模块,两者都应该依赖于抽象(比如接口、抽象类);
抽象不应该依赖于细节,细节(具体实现类)应该依赖于抽象。
迪米特法则
一个对象对其他对象了解的越少越好
降低对象之间的耦合度,减少交互复杂度
合成复用原则
尽量使用组合/聚合关系,而非继承来达到复用目的
继承会导致类之间强耦合,而组合能保持松耦合和更大灵活性
设计模式的三大分类是什么?每类包含哪些典型模式?
核心:创建型(5 种)、结构型(7 种)、行为型(11 种)
列举 3-5 个典型模式(如创建型:单例、工厂;结构型:适配器、装饰器;行为型:观察者、策略)。
如何实现一个线程安全的单例模式?
- 采用饿汉式,类加载时初始化,是天然的线程安全

- 采用懒汉式 + 双重检查锁(Volatile 防止指令重排)

单例模式的缺点是什么?
缺点:难测试(依赖全局状态)、不支持继承;不适合:需要多个实例的场景(如多数据库连接)。
策略+工厂+模板的项目落地(重点)
介绍
策略模式:就是把 “做什么” 和 “怎么做” 分离 —— 客户端只需要 “要做这件事”,具体用哪种方法(策略)来做,由策略类决定,且可以动态切换。
工厂模式:将对象的创建过程封装起来,让客户端无需直接通过 new 关键字创建对象,而是通过 “工厂” 间接获取实例。
模板模式:定义一个模板类,子类实现具体步骤
责任链模式:将请求沿着处理链传递,直到被处理(如请假审批:组长 ——> 经理 ——> 总监逐级审批)
设计思路
- 模板方法模式:
使用一个模板类定义充电的完整流程,有验证设备、检查连接、执行充电、记录日志、确认状态
子类只需实现特定步骤,如验证设备和执行充电,其余的检查连接,记录日志,确认状态则由父类实现
- 策略模式:
所有运营商的充电实现都遵循统一接口,比如充电接口
每个运营商有自己的策略实现,通过调用运营商提供的充电接口
可以在运行时根据需要切换不同的运营商策略
- 工厂模式:
设计一个工厂负责创建和管理所有充电策略(不同的运营商有不同的充电接口)实例
客户端只需提供运营商 ID,无需关心具体实现类的创建过程
通过Strategy支持动态扩展新的运营商
具体实现
【模板方法模式】

在 AbstractChargeTemplate 中实现了接口的 startCharging 方法,这是策略模式的体现;在 startCharging 中调用了内部的 【验证设备信息、检查与运营商的连接、执行具体的充电操作、记录充电日志、确认充电状态】 的方法,其中【检查与运营商的连接,执行具体的充电操作】需要子类实现,其他的方法父类中已经实现了;它的子类 OperatorA、OperatorB、OperatorC再继承 AbstractChargeTemplate 类实现检【查与运营商的连接,执行具体的充电操作】;这样就将不同的运营商之间的耦合度降低,也方便测试数据
【策略模式】

【工厂模式】

在 ChargeStrategyFactory 这个工厂中将所有运营商的信息记录在内,当其他方法调用 getChargeStrategy 根据 Id 返回对应的运营商策略
设计优势
新增运营商时,只需添加新的策略实现类并注册到工厂,无需修改现有代码,符合开闭原则
充电流程框架统一,但允许各运营商定制特定步骤,兼顾一致性和灵活性
对于客户端的感知,我们的修改是无感的,客户端代码与具体实现解耦,降低了维护成本
需要新增运营商时,测试的测试用例无需覆盖以前运营商的所有测试用例,只需覆盖新的测试用例即可,我们的测试的时间也会更短,提升效率,缩短迭代时间。
责任链模式的项目落地(重点)
在充电流程中校验模块

设计思路
- 抽象责任链处理器:定义责任链节点的通用接口,包含设置下一个节点和处理校验的方法
- 具体处理器:继承抽象责任链处理器,欠费检验、用户黑名单检验、支付状态检验、站点黑名单检验这四个处理器分别对应四个子类,在子类中实现 doValidate 方法
- 校验上下文:封装校验所需的参数(用户 ID、站点 ID 等)和校验结果(状态、消息)
- 责任链构建:按业务顺序组装处理器链条,确保请求按序传递
设计优势
- 解耦校验步骤:每个校验逻辑独立封装,新增 / 删除校验步骤只需修改链条组装,无需改动其他节点(符合开放 - 封闭原则)
- 灵活调整顺序:通过责任链工厂可自由调整校验顺序(如先查站点黑名单再查用户信息)
- 简化调用逻辑:客户端只需调用链条起点,无需关心中间步骤,降低调用复杂度
代码

流程:各个子类继承该责任链处理器类,子类之间通过 setNextHandler 设置下一个检测节点,再调用第一个子类(节点)的 handle 方法,先检验上一个节点的 isValid 是否合法,如果不合法就终止链条,直接退出这次递归;如果合法就执行当前节点的检验逻辑,将结果存入 isValid 传递给下一个节点处理(如果存在下一个节点的话);当所有节点都执行完毕后,判断 isValid 是否合法来看检测结果






