# 系统架构与公共模块说明 > 本文档描述项目整体架构、双应用部署、模块依赖、公共组件及开发规范要点。 > 帮助开发人员快速理解“为什么这样设计”以及各环节的特殊处理方式。 --- ## 一、系统架构 ### 1.1 双应用架构 | 应用 | 模块 | 端口 | Context-Path | 用途 | |------|------|------|--------------|------| | AdminApplication | haha-admin | 7070 | /admin | 运营平台后端 | | MiniappApplication | haha-miniapp | 7077 | /api | 小程序后端 | 两个应用共享 `haha-common` / `haha-entity` / `haha-mapper` / `haha-service`,通过 `@ComponentScan(basePackages = {"com.haha"})` 和 `@MapperScan("com.haha.mapper")` 自动扫描。 #### 为什么采用双应用而非单应用? 运营平台(haha-admin)和小程序后端(haha-miniapp)的**认证方式、API风格、安全要求**完全不同: - 运营平台:Sa-Token 管理员登录,权限控制严格(@RequirePermission),仅运营人员访问 - 小程序:微信登录 + accessToken,面向所有用户,无权限注解 分离部署的优势: - 独立扩缩容:小程序端流量远大于运营端 - 安全隔离:运营平台不暴露给用户端 - 独立发布:两端可独立上线,互不影响 ### 1.2 模块依赖方向(严格单向) ``` haha-admin --> haha-service --> haha-mapper --> haha-entity haha-miniapp --> haha-service --> haha-mapper --> haha-entity | haha-common <---------------------------+(所有模块均可依赖) haha-sdk(独立模块,不依赖其他内部模块) ``` **禁止反向依赖**: - haha-mapper 不可依赖 haha-service - haha-entity 不可依赖 haha-mapper 或 haha-service - haha-common 不可依赖任何其他内部模块 - haha-admin 与 haha-miniapp 不可互相依赖 ### 1.3 技术栈版本 | 技术 | 版本 | 说明 | |------|------|------| | Java | 21 | 禁止使用 Java 22+ 特性 | | Spring Boot | 4.0.3 | 使用 Jakarta 命名空间 | | MyBatis-Plus | 3.5.16 | mybatis-plus-spring-boot4-starter | | Sa-Token | 1.45.0 | sa-token-spring-boot3-starter | | Hutool | 5.8.40 | 禁止使用 hutool 6.x API | | FastJson2 | 2.0.53 | 禁止使用 fastjson (v1) | | Lombok | 1.18.36 | -- | | Jakarta EE | Jakarta | 非 Javax | --- ## 二、前端应用 ### 2.1 运营平台(haha-admin-web) - 框架:Vue 3 + Element Plus + TypeScript - 构建:Vite - 状态管理:Pinia - 路由:Vue Router ### 2.2 用户小程序(haha-mp) - 框架:UniApp + Vue 3 + TypeScript - 构建:Vite - 微信登录 + 支付分集成 ### 2.3 管理端小程序(haha-admin-mp) - 框架:UniApp + Vue 3 + TypeScript - 用途:合作商管理、门店巡检等 --- ## 三、公共模块 (haha-common) ### 3.1 枚举类 (`com.haha.common.enums`) | 枚举 | 说明 | code类型 | |------|------|----------| | OrderStatus | 订单状态 | int | | PayStatus | 支付状态 | String | | PaymentChannel | 支付渠道 | String | | DeviceDoorStatus | 门状态 | String | | DeviceAlertType | 告警类型 | String | | ActivityTypeEnum | 活动类型 | int | | ActivityStatusEnum | 活动状态 | int | | CouponTypeEnum | 优惠券类型 | int | | CouponStatusEnum | 优惠券状态 | int | | DiscountTypeEnum | 折扣类型 | int | | ValidTypeEnum | 有效期类型 | int | | DistributeTypeEnum | 发放类型 | int | | ApplyScopeEnum | 适用范围 | int | | OpenType | 开门类型 | String | | InviteStatusEnum | 邀请状态 | int | | InviteRewardStatusEnum | 邀请奖励状态 | int | | PayScoreState | 支付分状态 | String | **枚举规范**: - 只含 `code` + `description/label` 字段 - 必须提供 `fromCode()` / `getByCode()` 静态方法 - 数据库存储 `code` 值,禁止存储 `description` ### 3.2 常量类 (`com.haha.common.constant`) | 常量类 | 说明 | |--------|------| | OrderConstants | 订单状态/支付状态/订单类型常量 | | DeviceConstants | 设备相关常量 | | MarketingConstants | 营销相关常量 | | RedisConstants | Redis Key 定义 | | CallbackConstants | 回调类型常量 | | CommonConstants | 通用常量 | | ResponseEnum | 业务响应码枚举 | ### 3.3 通用VO | VO | 说明 | |----|------| | Result\ | 统一返回封装(code/message/data) | | PageResult\ | 分页返回(通过 `PageResult.of(IPage)` 转换) | | StatusLabel | 状态标签(label + color) | | OrderVO | 订单详情VO | ### 3.4 统一返回格式 ```json { "code": 200, "message": "操作成功", "data": { ... } } ``` | 场景 | code | message | |------|------|---------| | 成功 | 200 | 操作成功 / 具体描述 | | 参数错误 | 400 | 字段错误描述 | | 未登录 | 401 | 未登录 / token已过期 | | 无权限 | 403 | 无访问权限 | | 资源不存在 | 404 | xxx不存在 | | 业务异常 | 自定义 | BusinessException 消息 | | 系统异常 | 500 | 系统繁忙,请稍后重试 | --- ## 四、哈哈SDK (haha-sdk) ### 4.1 用途 独立模块,封装与哈哈平台(智能售卖机设备平台)的API交互,不依赖其他内部模块。 ### 4.2 核心功能 | 功能 | 说明 | |------|------| | 设备API | 开门、查询状态、设置温度/音量 | | 商品API | 商品查询、同步 | | 订单API | 创建订单、查询订单 | | 回调验证 | 签名校验 | ### 4.3 认证方式 通过 `authToken` 进行设备级认证,SDK内部处理签名和请求构建。 --- ## 五、权限体系 ### 5.1 运营平台权限(Sa-Token) - 权限注解:`@RequirePermission("module:operation")` - operation 标准值:`read` / `create` / `update` / `delete` / `other` - 小程序端不需要 `@RequirePermission`(通过 accessToken 鉴权) ### 5.2 数据权限 (`DataPermission`) 支持按门店维度的数据隔离,运营人员只能查看授权门店的数据。 ### 5.3 操作日志 - 注解:`@Log(module = "模块名", operation = OperationType.XXX, summary = "描述")` - OperationType:CREATE / UPDATE / DELETE / OTHER - 读操作不加 `@Log` --- ## 六、数据库规范 ### 6.1 表命名 - 前缀 `t_`,如 `t_order`、`t_device` - 关联表:`t_{主实体}_{关联实体}`,如 `t_activity_device`、`t_coupon_shop` ### 6.2 ID策略 - 全局雪花算法:`@TableId(type = IdType.ASSIGN_ID)` - 数据库列类型:BIGINT - 禁止自增ID ### 6.3 字段命名 - 数据库列:下划线(create_time, pay_status) - Java字段:驼峰(createTime, payStatus) - 自动映射:`map-underscore-to-camel-case: true` ### 6.4 XML映射文件 - 位置:`haha-mapper/src/main/resources/mapper/XxxMapper.xml` - 必须定义 `resultMap`,禁止 `resultType` - 逻辑删除不会自动添加到手写SQL中 --- ## 七、数据库表总览 ### 核心业务表 | 表名 | 说明 | |------|------| | t_order | 订单表 | | t_order_goods | 订单商品明细 | | t_order_item | 订单商品项 | | t_device | 设备表 | | t_shop | 门店表 | | t_product | 商品表 | | t_device_inventory | 设备库存表 | | t_door_record | 开关门记录表 | | t_user | 用户表 | | t_account | 用户账户表 | ### 营销优惠表 | 表名 | 说明 | |------|------| | t_marketing_activity | 营销活动表 | | t_activity_shop | 活动-门店关联 | | t_activity_device | 活动-设备关联 | | t_activity_product | 活动-商品关联 | | t_coupon_template | 优惠券模板表 | | t_user_coupon | 用户优惠券表 | | t_coupon_shop | 优惠券-门店关联 | | t_coupon_product | 优惠券-商品关联 | | t_coupon_distribute | 优惠券发放记录 | | t_timed_discount_activity | 定时折扣活动表 | | t_timed_discount_device | 定时折扣-设备关联 | | t_timed_discount_shop | 定时折扣-门店关联 | | t_timed_discount_product | 定时折扣-商品关联 | | t_timed_discount_record | 定时折扣执行记录 | | t_timed_discount_statistics | 定时折扣统计 | | t_invite_activity | 邀请活动表 | | t_invite_record | 邀请记录表 | | t_invite_reward | 邀请奖励表 | | t_marketing_statistics | 营销统计表 | ### 库存管理表 | 表名 | 说明 | |------|------| | t_stock_record | 补货记录表 | | t_stock_record_item | 补货明细表 | | t_inventory_log | 库存变更日志 | | t_price_adjustment_log | 价格调整日志 | ### 系统管理表 | 表名 | 说明 | |------|------| | t_admin | 管理员表 | | t_role | 角色表 | | t_role_permission | 角色权限关联 | | t_data_permission | 数据权限表 | | t_operation_log | 操作日志表 | | t_dict_type | 字典类型表 | | t_dict_data | 字典数据表 | | t_device_alert_record | 设备告警记录 | | t_announcement | 公告表 | ### 分销模块表 | 表名 | 说明 | |------|------| | t_distributor | 合作商表 | | t_distributor_commission | 佣金记录表 | | t_distributor_config | 合作商配置表 | | t_distributor_referral | 推荐记录表 | | t_distributor_withdrawal | 提现记录表 | | t_distributor_monthly_report | 月度报表 | ### 统计报表表 | 表名 | 说明 | |------|------| | t_stat_category_daily | 品类日统计 | | t_stat_device_daily | 设备日统计 | | t_stat_product_daily | 商品日统计 | | t_stat_shop_daily | 门店日统计 | | t_stat_user_repurchase | 用户复购统计 | --- ## 八、层模板管理 ### 8.1 LayerTemplate 实体 设备货架层配置模板,定义每一层的商品排列方式。 ### 8.2 核心功能 | 功能 | 说明 | |------|------| | 模板CRUD | 创建/编辑/删除层模板 | | 层配置 | 每层的商品类型、数量、位置 | | 设备应用 | 将模板应用到指定设备 | --- ## 九、开发规范要点 1. **Long类型序列化**:所有 Long 类型 ID 和外键字段必须添加 `@JsonSerialize(using = ToStringSerializer.class)` 防精度丢失 - **原因**:JavaScript 的 Number 类型最大安全整数为 2^53-1,超过则精度丢失。雪花算法生成的ID约19位数字,远超此限制 - **影响**:不添加此注解,前端接收到的ID末尾会被截断为0,导致根据ID查询/更新失败 2. **日期格式**:`@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")` 3. **金额字段**:必须使用 `BigDecimal`,禁止 `double` / `float` - **原因**:浮点数在计算机中无法精确表示十进制小数,如 0.1 + 0.2 != 0.3 - **运算**:使用 `setScale(2, RoundingMode.HALF_UP)` 保留2位小数 4. **依赖注入**:构造器注入(`final` + `@RequiredArgsConstructor`),循环依赖时用 `@Autowired @Lazy` 5. **事务**:写操作必须 `@Transactional(rollbackFor = Exception.class)` - **注意**:`rollbackFor = Exception.class` 不能省略,默认只回滚 RuntimeException 6. **分页校验**:`page < 1` 设为 1,`pageSize > 100` 设为 10 7. **业务异常**:使用 `BusinessException` + `ResponseEnum`,禁止 Controller 中抛出未处理异常 8. **雪花算法ID注意**:手写SQL中的逻辑删除条件不会自动添加,需手动加 `WHERE deleted = 0` --- ## 十、关键设计决策记录 ### 10.1 为什么用雪花算法而非自增ID? 系统需要与哈哈平台(设备端)交互,设备端生成 `activityId` 关联开关门和订单。自增ID依赖数据库生成,在分布式场景和回调场景下不可用。雪花算法在应用层生成ID,不依赖数据库,且全局唯一。 **迁移注意**:项目早期使用自增ID,后改为雪花算法。已有数据需保留原ID,新数据使用雪花算法。`alter_snowflake_id.sql` 处理了迁移逻辑。 ### 10.2 为什么 haha-sdk 是独立模块? `haha-sdk` 封装与哈哈平台的HTTP交互,不依赖任何内部业务模块。这样设计是因为: - SDK可独立发布给第三方使用 - 避免循环依赖(如果SDK依赖service,service又调用SDK) - SDK的更新不影响业务模块的编译 ### 10.3 MyBatis-Plus 逻辑删除的特殊处理 MyBatis-Plus 的 `@TableLogic` 注解在 MP 自动生成的 SQL 中会自动添加 `WHERE deleted = 0`,但**手写SQL(XML或注解式)不会自动添加**。开发手写SQL时必须手动处理逻辑删除条件,否则会查询到已删除的数据。