🚗 JAVA 无人共享24小时自助洗车扫码系统 - 微信小程序代码片段

📁 项目结构


├── backend (Java SpringBoot)
│   └── src/main/java/com/carwash/
├── miniprogram (微信小程序)
│   ├── pages/
│   │   ├── index/          # 首页
│   │   ├── scan/           # 扫码页面
│   │   ├── order/          # 订单页
│   │   └── my/             # 我的
│   └── utils/

🔧 一、微信小程序前端代码

1️⃣ 首页 pages/index/index.wxml


xml

<view class="container">
  <!-- 顶部Banner -->
  <swiper class="banner" autoplay circular indicator-dots>
    <swiper-item wx:for="{{banners}}" wx:key="id">
      <image src="{{item.image}}" mode="aspectFill"></image>
    </swiper-item>
  </swiper>

  <!-- 附近洗车点 -->
  <view class="section">
    <view class="section-title">附近洗车点</view>
    <view class="store-list">
      <view 
        class="store-item" 
        wx:for="{{stores}}" 
        wx:key="id"
        bindtap="goToStore"
        data-id="{{item.id}}"
      >
        <image src="{{item.image}}" class="store-img"></image>
        <view class="store-info">
          <text class="store-name">{{item.name}}</text>
          <text class="store-distance">距离{{item.distance}}km</text>
          <view class="store-status {{item.status == 1 ? 'online' : 'offline'}}">
            {{item.status == 1 ? '营业中' : '已打烊'}}
          </view>
        </view>
        <view class="store-price">
          <text class="price">¥{{item.price}}</text>
          <text class="unit">/次</text>
        </view>
      </view>
    </view>
  </view>

  <!-- 扫码洗车按钮 -->
  <view class="scan-btn" bindtap="scanCode">
    <text class="iconfont icon-scan"></text>
    <text>扫码洗车</text>
  </view>
</view>

2️⃣ 首页逻辑 pages/index/index.js


javascript

const app = getApp()
const api = require('../../utils/api')

Page({
  data: {
    banners: [],
    stores: [],
    latitude: 0,
    longitude: 0
  },

  onLoad() {
    this.getLocation()
    this.getStores()
    this.getBanners()
  },

  // 获取位置
  getLocation() {
    wx.getLocation({
      type: 'gcj02',
      success: (res) => {
        this.setData({
          latitude: res.latitude,
          longitude: res.longitude
        })
      }
    })
  },

  // 获取附近洗车点
  async getStores() {
    const res = await api.getNearbyStores({
      lat: this.data.latitude,
      lng: this.data.longitude
    })
    this.setData({ stores: res.data })
  },

  // 获取轮播图
  async getBanners() {
    const res = await api.getBanners()
    this.setData({ banners: res.data })
  },

  // 扫码洗车
  scanCode() {
    wx.scanCode({
      onlyFromCamera: false,
      scanType: ['qrCode'],
      success: async (res) => {
        // res.result 是二维码内容,包含设备ID
        const deviceId = res.result.replace('wash:', '')
        wx.navigateTo({
          url: `/pages/order/order?deviceId=${deviceId}`
        })
      }
    })
  },

  goToStore(e) {
    const id = e.currentTarget.dataset.id
    wx.navigateTo({
      url: `/pages/order/order?storeId=${id}`
    })
  }
})

3️⃣ 扫码洗车/下单页 pages/order/order.wxml


xml

<view class="order-page">
  <!-- 设备信息 -->
  <view class="device-card" wx:if="{{deviceInfo}}">
    <image src="{{deviceInfo.image}}" class="device-img"></image>
    <view class="device-info">
      <text class="device-name">{{deviceInfo.name}}</text>
      <text class="device-addr">{{deviceInfo.address}}</text>
      <view class="device-status">
        <view class="status-dot {{deviceInfo.online ? 'green' : 'red'}}"></view>
        <text>{{deviceInfo.online ? '设备在线' : '设备离线'}}</text>
      </view>
    </view>
  </view>

  <!-- 洗车套餐选择 -->
  <view class="section">
    <view class="section-title">选择洗车套餐</view>
    <view class="package-list">
      <view 
        class="package-item {{selectedPackage == item.id ? 'active' : ''}}"
        wx:for="{{packages}}"
        wx:key="id"
        bindtap="selectPackage"
        data-id="{{item.id}}"
      >
        <view class="pkg-name">{{item.name}}</view>
        <view class="pkg-desc">{{item.description}}</view>
        <view class="pkg-price">
          <text class="currency">¥</text>
          <text class="amount">{{item.price}}</text>
        </view>
        <view class="pkg-duration">约{{item.duration}}分钟</view>
      </view>
    </view>
  </view>

  <!-- 支付方式 -->
  <view class="section">
    <view class="section-title">支付方式</view>
    <view class="pay-methods">
      <view 
        class="pay-item {{payMethod == 'wechat' ? 'active' : ''}}"
        bindtap="selectPay"
        data-method="wechat"
      >
        <image src="/images/wechat-pay.png"></image>
        <text>微信支付</text>
      </view>
      <view 
        class="pay-item {{payMethod == 'balance' ? 'active' : ''}}"
        bindtap="selectPay"
        data-method="balance"
      >
        <image src="/images/wallet.png"></image>
        <text>余额支付 (¥{{userBalance}})</text>
      </view>
    </view>
  </view>

  <!-- 底部支付按钮 -->
  <view class="bottom-bar">
    <view class="total">
      <text>合计:</text>
      <text class="total-price">¥{{totalPrice}}</text>
    </view>
    <button class="pay-btn" bindtap="submitOrder">立即支付</button>
  </view>
</view>

4️⃣ 下单逻辑 pages/order/order.js


javascript

const api = require('../../utils/api')

Page({
  data: {
    deviceId: '',
    storeId: '',
    deviceInfo: null,
    packages: [
      { id: 1, name: '标准洗车', description: '外观清洗+简单内饰', price: 10, duration: 15 },
      { id: 2, name: '精致洗车', description: '全车清洗+内饰清洁', price: 20, duration: 25 },
      { id: 3, name: '豪华洗车', description: '全车精洗+打蜡', price: 35, duration: 40 }
    ],
    selectedPackage: 1,
    payMethod: 'wechat',
    userBalance: 0,
    totalPrice: 10
  },

  onLoad(options) {
    if (options.deviceId) {
      this.setData({ deviceId: options.deviceId })
      this.getDeviceInfo(options.deviceId)
    }
    if (options.storeId) {
      this.setData({ storeId: options.storeId })
      this.getDeviceInfoByStore(options.storeId)
    }
    this.getUserBalance()
  },

  // 获取设备信息
  async getDeviceInfo(deviceId) {
    const res = await api.getDeviceInfo(deviceId)
    this.setData({ deviceInfo: res.data })
  },

  // 获取用户余额
  async getUserBalance() {
    const res = await api.getUserBalance()
    this.setData({ userBalance: res.data.balance })
  },

  // 选择套餐
  selectPackage(e) {
    const id = e.currentTarget.dataset.id
    const pkg = this.data.packages.find(p => p.id === id)
    this.setData({
      selectedPackage: id,
      totalPrice: pkg.price
    })
  },

  // 选择支付方式
  selectPay(e) {
    this.setData({ payMethod: e.currentTarget.dataset.method })
  },

  // 提交订单
  async submitOrder() {
    wx.showLoading({ title: '支付中...' })

    try {
      // 1. 创建订单
      const orderRes = await api.createOrder({
        deviceId: this.data.deviceId,
        packageId: this.data.selectedPackage,
        payMethod: this.data.payMethod
      })

      const orderId = orderRes.data.orderId

      // 2. 调用微信支付
      if (this.data.payMethod === 'wechat') {
        const payRes = await api.getWxPayParams(orderId)
        
        wx.requestPayment({
          timeStamp: payRes.data.timeStamp,
          nonceStr: payRes.data.nonceStr,
          package: payRes.data.package,
          signType: payRes.data.signType,
          paySign: payRes.data.paySign,
          success: () => {
            wx.showToast({ title: '支付成功', icon: 'success' })
            // 跳转到洗车控制页
            wx.redirectTo({
              url: `/pages/wash/wash?orderId=${orderId}&deviceId=${this.data.deviceId}`
            })
          },
          fail: () => {
            wx.showToast({ title: '支付取消', icon: 'none' })
          }
        })
      } else {
        // 余额支付
        await api.payByBalance(orderId)
        wx.showToast({ title: '支付成功', icon: 'success' })
        wx.redirectTo({
          url: `/pages/wash/wash?orderId=${orderId}&deviceId=${this.data.deviceId}`
        })
      }
    } catch (err) {
      wx.showToast({ title: err.message || '下单失败', icon: 'none' })
    } finally {
      wx.hideLoading()
    }
  }
})

5️⃣ 洗车控制页 pages/wash/wash.wxml


xml

<view class="wash-page">
  <view class="wash-header">
    <text class="wash-title">洗车进行中</text>
    <text class="wash-timer">剩余时间:{{remainTime}}s</text>
  </view>

  <!-- 洗车控制面板 -->
  <view class="control-panel">
    <view class="control-item" bindtap="toggleWash" data-type="wash">
      <view class="control-icon wash-icon {{isWashing ? 'active' : ''}}">
        🚿
      </view>
      <text>{{isWashing ? '清洗中' : '未开始'}}</text>
    </view>

    <view class="control-item" bindtap="toggleWash" data-type="foam">
      <view class="control-icon foam-icon {{isFoam ? 'active' : ''}}">
        🧴
      </view>
      <text>泡沫</text>
    </view>

    <view class="control-item" bindtap="toggleWash" data-type="wax">
      <view class="control-icon wax-icon {{isWax ? 'active' : ''}}">
        ✨
      </view>
      <text>打蜡</text>
    </view>

    <view class="control-item" bindtap="toggleWash" data-type="dry">
      <view class="control-icon dry-icon {{isDry ? 'active' : ''}}">
        💨
      </view>
      <text>吹干</text>
    </view>
  </view>

  <!-- 进度条 -->
  <view class="progress-bar">
    <view class="progress-fill" style="width: {{progress}}%"></view>
  </view>

  <!-- 底部操作 -->
  <view class="bottom-actions">
    <button class="stop-btn" bindtap="stopWash">结束洗车</button>
    <button class="call-btn" bindtap="callService">联系客服</button>
  </view>
</view>

6️⃣ 洗车控制逻辑 pages/wash/wash.js


javascript

const api = require('../../utils/api')

Page({
  data: {
    orderId: '',
    deviceId: '',
    isWashing: false,
    isFoam: false,
    isWax: false,
    isDry: false,
    remainTime: 0,
    progress: 0,
    timer: null
  },

  onLoad(options) {
    this.setData({
      orderId: options.orderId,
      deviceId: options.deviceId
    })
    this.startTimer()
    this.listenDeviceStatus()
  },

  onUnload() {
    if (this.data.timer) {
      clearInterval(this.data.timer)
    }
    this.stopListening()
  },

  // 倒计时
  startTimer() {
    let time = 900 // 15分钟
    this.setData({ remainTime: time })

    this.data.timer = setInterval(() => {
      time--
      this.setData({
        remainTime: time,
        progress: ((900 - time) / 900) * 100
      })

      if (time <= 0) {
        clearInterval(this.data.timer)
        this.autoStop()
      }
    }, 1000)
  },

  // 切换洗车模式
  async toggleWash(e) {
    const type = e.currentTarget.dataset.type
    const keyMap = {
      wash: 'isWashing',
      foam: 'isFoam',
      wax: 'isWax',
      dry: 'isDry'
    }
    
    const newVal = !this.data[keyMap[type]]
    this.setData({ [keyMap[type]]: newVal })

    // 发送指令到设备
    await api.sendDeviceCommand({
      deviceId: this.data.deviceId,
      command: type,
      status: newVal ? 1 : 0
    })

    wx.showToast({
      title: newVal ? `${type}已开启` : `${type}已关闭`,
      icon: 'none'
    })
  },

  // 监听设备状态(WebSocket)
  listenDeviceStatus() {
    this.wsTask = wx.connectSocket({
      url: `wss://your-domain.com/ws/device/${this.data.deviceId}`,
      success: () => {
        wx.onSocketMessage((res) => {
          const data = JSON.parse(res.data)
          if (data.type === 'status') {
            this.setData({
              isWashing: data.washing,
              isFoam: data.foam,
              isWax: data.wax,
              isDry: data.dry
            })
          }
        })
      }
    })
  },

  stopListening() {
    if (this.wsTask) {
      wx.closeSocket()
    }
  },

  // 结束洗车
  async stopWash() {
    wx.showModal({
      title: '确认结束',
      content: '确定要结束洗车吗?',
      success: async (res) => {
        if (res.confirm) {
          await api.stopWash({
            orderId: this.data.orderId,
            deviceId: this.data.deviceId
          })
          clearInterval(this.data.timer)
          wx.redirectTo({ url: '/pages/my/my' })
        }
      }
    })
  },

  autoStop() {
    this.stopWash()
  },

  callService() {
    wx.makePhoneCall({
      phoneNumber: '400-888-8888'
    })
  }
})

☕ 二、Java SpringBoot 后端核心代码

1️⃣ 实体类 Device.java


java

@Data
@Entity
@Table(name = "t_device")
public class Device {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String deviceCode;      // 设备编号
    private String deviceName;      // 设备名称
    private String qrCode;          // 二维码内容
    private Double latitude;        // 纬度
    private Double longitude;       // 经度
    private String address;         // 地址
    private Integer status;         // 0-离线 1-在线 2-故障
    private Integer deviceType;     // 1-自助洗车机
    
    @CreatedDate
    private LocalDateTime createTime;
    
    @UpdateTimestamp
    private LocalDateTime updateTime;
}

2️⃣ 订单实体 Order.java


java

@Data
@Entity
@Table(name = "t_order")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String orderNo;         // 订单号
    private Long userId;           // 用户ID(openid)
    private Long deviceId;         // 设备ID
    private Integer packageId;     // 套餐ID
    private BigDecimal amount;     // 金额
    private Integer payMethod;     // 1-微信 2-余额
    private Integer status;        // 0-待支付 1-已支付 2-洗车中 3-已完成 4-已取消
    private LocalDateTime payTime; // 支付时间
    private LocalDateTime startTime; // 开始洗车时间
    private LocalDateTime endTime;   // 结束时间
    
    @CreatedDate
    private LocalDateTime createTime;
}

3️⃣ 订单Controller OrderController.java


java

@RestController
@RequestMapping("/api/order")
@CrossOrigin
public class OrderController {

    @Autowired
    private OrderService orderService;

    @Autowired
    private WxPayService wxPayService;

    // 创建订单
    @PostMapping("/create")
    public Result createOrder(@RequestBody OrderDTO dto) {
        String orderNo = "CW" + System.currentTimeMillis();
        Order order = orderService.createOrder(orderNo, dto);
        return Result.success(order);
    }

    // 获取微信支付参数
    @PostMapping("/pay/{orderId}")
    public Result getWxPayParams(@PathVariable Long orderId) {
        Map<String, String> payParams = wxPayService.createPay(orderId);
        return Result.success(payParams);
    }

    // 支付回调
    @PostMapping("/notify")
    public String payNotify(@RequestBody String xmlData) {
        Map<String, String> result = wxPayService.parseNotify(xmlData);
        if ("SUCCESS".equals(result.get("result_code"))) {
            String orderNo = result.get("out_trade_no");
            orderService.updateStatus(orderNo, 1); // 已支付
            // 发送WebSocket消息通知设备开始
            deviceService.sendCommand(orderNo, "START");
        }
        return "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>";
    }

    // 结束洗车
    @PostMapping("/stop")
    public Result stopWash(@RequestBody StopWashDTO dto) {
        orderService.endOrder(dto.getOrderId(), dto.getDeviceId());
        return Result.success("洗车已结束");
    }
}

4️⃣ 微信支付服务 WxPayService.java


java

@Service
public class WxPayService {

    @Value("${wx.pay.appId}")
    private String appId;

    @Value("${wx.pay.mchId}")
    private String mchId;

    @Value("${wx.pay.apiKey}")
    private String apiKey;

    @Value("${wx.pay.notifyUrl}")
    private String notifyUrl;

    public Map<String, String> createPay(Long orderId) {
        Order order = orderService.getByOrderNo(
            orderService.getOrderNo(orderId)
        );

        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
        request.setBody("自助洗车-" + order.getPackageId());
        request.setOut_trade_no(order.getOrderNo());
        request.setTotal_fee(order.getAmount().multiply(new BigDecimal(100)).intValue());
        request.setSpbill_create_ip("127.0.0.1");
        request.setNotify_url(notifyUrl);
        request.setTrade_type("JSAPI");
        request.setOpenid(order.getUserOpenid());

        WxPayUnifiedOrderResult result = WxPayApi.unifiedOrder(request, apiKey);
        
        // 返回小程序支付参数
        Map<String, String> params = new HashMap<>();
        params.put("appId", appId);
        params.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
        params.put("nonceStr", UUID.randomUUID().toString().replace("-", ""));
        params.put("package", "prepay_id=" + result.getPrepayId());
        params.put("signType", "MD5");
        
        String sign = WxPayApi.paySign(params, apiKey);
        params.put("paySign", sign);
        
        return params;
    }
}

5️⃣ 设备控制WebSocket DeviceWebSocket.java


java

@ServerEndpoint("/ws/device/{deviceId}")
@Component
public class DeviceWebSocket {

    private static ConcurrentHashMap<String, Session> sessions = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(Session session, @PathParam("deviceId") String deviceId) {
        sessions.put(deviceId, session);
        System.out.println("设备连接: " + deviceId);
    }

    @OnClose
    public void onClose(@PathParam("deviceId") String deviceId) {
        sessions.remove(deviceId);
    }

    // 发送控制指令到设备
    public static void sendCommand(String deviceId, String command) {
        Session session = sessions.get(deviceId);
        if (session != null && session.isOpen()) {
            try {
                session.getBasicRemote().sendText(
                    JSON.toJSONString(Map.of("cmd", command))
                );
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @OnMessage
    public void onMessage(String message, @PathParam("deviceId") String deviceId) {
        // 接收设备上报状态
        JSONObject data = JSON.parseObject(message);
        // 更新设备状态到数据库
        deviceService.updateStatus(deviceId, data.getInteger("status"));
    }
}

🔌 三、API工具类 utils/api.js


javascript

const BASE_URL = 'https://your-domain.com/api'

const request = (url, method, data) => {
  return new Promise((resolve, reject) => {
    wx.request({
      url: BASE_URL + url,
      method,
      data,
      header: {
        'Authorization': 'Bearer ' + wx.getStorageSync('token'),
        'Content-Type': 'application/json'
      },
      success: (res) => {
        if (res.data.code === 200) {
          resolve(res.data)
        } else {
          reject(new Error(res.data.message))
        }
      },
      fail: reject
    })
  })
}

module.exports = {
  getNearbyStores: (params) => request('/store/nearby', 'GET', params),
  getBanners: () => request('/banner/list', 'GET'),
  getDeviceInfo: (id) => request(`/device/${id}`, 'GET'),
  createOrder: (data) => request('/order/create', 'POST', data),
  getWxPayParams: (orderId) => request(`/order/pay/${orderId}`, 'POST'),
  getUserBalance: () => request('/user/balance', 'GET'),
  sendDeviceCommand: (data) => request('/device/command', 'POST', data),
  stopWash: (data) => request('/order/stop', 'POST', data)
}

📊 系统架构图


┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│  微信小程序   │────▶│  SpringBoot   │────▶│   MySQL     │
│  (前端)      │     │  (后端API)    │     │  (数据库)    │
└─────────────┘     └──────┬───────┘     └─────────────┘
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │ 微信支付  │ │ WebSocket│ │ MQTT协议  │
        │          │ │ 设备通信  │ │ IoT设备   │
        └──────────┘ └──────────┘ └──────────┘

✅ 核心功能清单

功能 说明
🔐 扫码识别设备 微信扫一扫 → 解析设备ID
💳 微信支付 JSAPI支付,余额支付
🚿 远程控制 WebSocket实时控制洗车机
📍 LBS定位 查找附近洗车点
📊 订单管理 订单状态全流程跟踪
💰 余额充值 微信充值/消费记录
🔔 消息通知 支付成功/洗车完成推送

💡 提示:实际部署需要配置 微信支付商户号SSL证书MQTT Broker(如EMQX) 对接IoT洗车设备硬件。需要完整项目可以进一步沟通!

更多推荐