0

by tcdos 2026-06-01

01 | 从想法到落地 - 共享充电宝平台的技术选型与架构演进

一、引子

「帮我做个充电宝平台。」

朋友找到我的时候,市面上共享充电宝已经铺天盖地了。但他的痛点很具体:想在一个区域市场里自己做运营,不想被大平台的抽成卡脖子。

真正做起来才发现,这玩意儿看着简单,拆开全是细节。设备怎么跟服务器保持长连接?扫码租借那一瞬间到底发生了什么?订单计费怎么处理超时和封顶?微信支付分免押金的流程怎么打通?

网上能查到的资料,大多是商业模式分析,关于设备与服务端的 TCP 长连接通信、订单实时计费逻辑、支付分免押金对接、软硬件全链路调度这些核心技术,几乎没有。于是从零开始,后端 API、运营后台、商户端、微信小程序、设备 TCP 通信,全套自己搞了一遍,全部独立开发,全覆盖自研

今天这篇,是系列复盘的第一篇,先聊聊技术选型和整体架构设计

二、一对多架构:一个后端,三个前端

整个平台的服务对象有三类人:

角色使用端覆盖功能
平台运营OPS 后台(Vue 3)设备管理、商户管理、订单监控、系统配置
合作商户Web 后台(Vue 3)店铺管理、订单查看、收益提现
C 端用户微信小程序扫码租借、地图找店、订单支付

为什么三端共用一个后端?

简单来说,业务逻辑是同一套——设备管理、订单流转、支付回调,只是不同角色看到的页面和数据范围不一样。拆分多个后端服务反而会增加通信成本和数据一致性的维护难度。

所以后端只做了一套,通过 RBAC 权限体系 来控制数据和功能的可见范围。两套 Vue 3 前端打包后部署在不同的域名下,各自独立构建、独立发布。

技术栈选型:

  • 后端: .NET 6(后升级到 .NET 8 兼容)+ EF Core 6 + SQL Server + Autofac + SignalR
  • 前端(管理后台): Vue 3 + Element Plus + Pinia + Axios
  • 前端(小程序): 微信原生开发 + TDesign MiniProgram
  • 设备通信: 自研 TCP Socket 长连接,自定义协议

这几个选型不是拍脑袋定的。本人对 .NET 技术栈最熟悉,生态成熟,性能也不差。Vue 3 + Element Plus 是当前中后台开发的主流方案,组件丰富、文档齐全。微信小程序端用原生开发,是因为对接微信支付、手机号授权等原生能力最直接,绕开框架的中间层反而省心。

三、后端架构:四层分离,关注点隔离

项目结构不是那种教科书式的整洁,但四层职责分得很清楚:

COM.HS.Power.Server/
├── COM.HS.HttpApi/           # 表现层 - Controller、Middleware、SignalR Hub
├── COM.HS.Application/       # 业务逻辑层 - Service、Socket、微信支付
├── COM.HS.Domain/            # 领域层 - Entity 定义(数据库表映射)
├── COM.HS.EntityFrameworkCore/  # 数据访问层 - DbContext、Repository、Migration
├── COM.HS.Application.Contracts/  # DTO + 接口定义
├── COM.HS.Shared/            # 共享层 - 常量、缓存、帮助类、枚举
├── COM.HS.Utils/             # 工具层 - Json、加密、表达式树扩展
├── COM.HS.IRepository/       # 仓储接口层
└── COM.HS.License/           # 授权校验

几个关键设计点:

1. 无 Repository 模式

很多人可能注意到了,虽然有 IBaseRepository 接口,但实际业务 Service 是直接注入 IBaseRepository<TEntity> ,而不是为每个实体创建一个 Repository 类。原因是业务逻辑主要在 Service 层,Repository 只是做最基本的查询封装,不需要为每个表写一个。

public class OrderService : BaseService<ZnOrderEntity>, IOrderService
{
    private readonly IBaseRepository<ZnOrderEntity> currentRepository;
    private readonly IBaseRepository<ZnDeviceEntity> deviceRepository;
    private readonly IBaseRepository<ZnBatteryEntity> batteryRepository;
    // ...

    public OrderService(
        IUnitOfWork unitOfWork,
        IBaseRepository<ZnOrderEntity> currentRepository,
        IBaseRepository<ZnDeviceEntity> deviceRepository,
        // ...
    ) : base(unitOfWork, currentRepository)
    {
        this.currentRepository = currentRepository;
        this.deviceRepository = deviceRepository;
        // ...
    }
}

这是一种务实的选择——泛型仓储足够用了,就不多做一层抽象。加上 IUnitOfWork 保证事务一致性。

2. AOP 拦截器替代业务代码中的横切关注点

用 Castle DynamicProxy + Autofac 实现了三个 AOP 拦截器:

  • CacheAop:方法级缓存,通过 [Caching] 注解自动缓存和过期
  • AuditAop:增删改操作自动清除相关缓存
  • LoggerAop :操作日志记录

比如缓存拦截器的核心逻辑只有几十行:

public override void Intercept(IInvocation invocation)
{
    var method = invocation.MethodInvocationTarget ?? invocation.Method;
    var cacheAttr = method.GetCustomAttributes(true)
        .FirstOrDefault(x => x.GetType() == typeof(CachingAttribute));

    if (cacheAttr is CachingAttribute cachingAttribute)
    {
        var cacheKey = CustomCacheKey(invocation);
        var cacheValue = cacheService.Get(cacheKey);
        if (cacheValue != null)
        {
            invocation.ReturnValue = cacheValue;
            return;
        }
        invocation.Proceed();
        cacheService.Add(cacheKey, invocation.ReturnValue,
            TimeSpan.FromMinutes(cachingAttribute.AbsoluteExpiration),
            TimeSpan.FromMinutes(cachingAttribute.AbsoluteExpiration));
    }
    else
    {
        invocation.Proceed();
    }
}

3. Program.cs 是整站的总装车间

Program.cs 是最有看点的文件——它把依赖注入、中间件管道、第三方服务全部组织在一起。

服务注册阶段:
  DbContext → Autofac → AutoMapper → JWT → SignalR → Quartz → SocketServer

中间件阶段:
  静态文件 → 路由 → CORS → License校验 → 异常处理 → 认证 → 授权 → Endpoints

启动阶段:
  种子数据 → Quartz调度 → SocketServer.Start() → Swagger

核心启动代码像这样:

/* 授权校验 */
app.UseLicenseMiddleware();

/* 异常处理 */
app.UseExceptionMiddleware();

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<MessageHub>("/messageHub");
    endpoints.MapControllers();
});

/* SocketServer */
var socketServer = app.Services.GetService<SocketServer>();
socketServer?.Start();

看到这里你可能会问: SocketServer 和 Web API 为什么跑在同一个进程里?

是的,我故意这么做的。TCP 通信服务和 HTTP API 融合在一个进程中,设备上报的数据可以直接操作数据库,不需要再走一层网关转发。对于几百台设备的规模,这种做法足够经济,运维也简单。

四、自研 TCP 通信:不妥协的选择

这是整个项目里最"硬"的部分。

充电宝机柜通过 4G 模块连上服务器,维持一条 TCP 长连接。机柜主板上跑的是 C 固件,服务器端用 C# Socket 异步编程来对接。

为什么不用 MQTT 或 CoAP?

当时考虑过主流的 IoT 协议方案。MQTT 生态成熟、有 Broker 做消息转发,看起来省事。但问题在于:

  • 1. 机柜固件端对自定义 TCP 协议的支持最成熟,改协议栈意味着改固件,厂家不支持
  • 2. MQTT Broker 是一个额外节点,多一层转发就多一层延迟和故障点
  • 3. 协议自定义程度高——心跳、租借、归还、强制弹出、FOTA 升级这些指令,MQTT 的 pub/sub 模型反而绕弯子

所以写了一个 SocketServer 类,单例注入,随应用启动。

public class SocketServer
{
    public ConcurrentDictionary<Socket, SocketClient> OnlineSocketClients
        = new ConcurrentDictionary<Socket, SocketClient>();

    public async Task<ResultModel> Start()
    {
        serverSocket.Bind(new IPEndPoint(IPAddress.Parse(ip), port));
        serverSocket.Listen(maxListen);

        ThreadPool.QueueUserWorkItem(async state => await AcceptAsync());
        await Task.Run(async () => await CheckHeartbeats(cts.Token));

        return new ResultModel(true);
    }
}

设备上报的报文以 #* 开头、 *# 结尾,统一格式:

{
  "msg": 1718691245,
  "aims": 0,
  "sn": "DS24060001",
  "cmd": "heart",
  "data": {
    "int": 45,
    "csq": 18
  }
}

目前支持 13 种指令,从登录到心跳,从租借到 FOTA 升级,全部基于这个统一的报文结构。

五、项目结构一览

整个仓库的代码模块用一句话概括就是: 一个后端处理所有逻辑,两套 Vue 前端覆盖管理和商户场景,一个小程序连接 C 端用户。

后端项目概览:

COM.HS.HttpApi/          → 50+ Controller,6 个 Swagger 分组
COM.HS.Application/      → 业务 + 系统 + CMS + 微信 + Socket + Quartz 任务
COM.HS.Domain/           → 40+ 实体类
COM.HS.EntityFrameworkCore/  → 数据库迁移
COM.HS.Shared/           → 常量定义、缓存服务、GeoHelper、二维码生成
COM.HS.License/          → AES 加密 License + 硬件绑定

前端页面覆盖:

  • OPS 运营后台 :CMS管理、数据字典、组织架构、角色权限、定时任务、审计日志
  • Web 商户后台 :设备管理、电池管理、订单管理、店铺管理、收益提现、还款管理、心跳监控、IoT指令箱
  • 微信小程序 :地图找店、扫码租借、归还、订单列表、收益明细、设备信息

六、小结

这篇是系列的开篇,主要把整体的技术选型和架构脉络交代清楚。下一篇会深入讲讲 自研 TCP 通信协议的设计 ——设备怎么登录、心跳怎么保活、租借和归还的指令是怎么在服务器和机柜之间流转的。

如果你也在做 IoT 相关的项目,或者正在犹豫要不要自研通信协议,欢迎关注这个系列。

有疑问或者想讨论的点,留言区见。

欢迎关注 天草设计 公众号,领取 源代码 福利礼包!

系列文章:共享充电宝

围观:126| 收获点赞:0

评论 0

暂无评论,来说点什么吧

?