华为云AI Agent集成鸿蒙车票查询项目(二编)

举报
落雨✦ 发表于 2025/10/20 13:01:23 2025/10/20
【摘要】 华为云AI Agent集成鸿蒙车票查询项目文档 一、项目概述 1.1 项目背景随着AI技术与移动应用的深度融合,本项目旨在通过华为云配置AI Agent实现智能车票查询能力,并基于鸿蒙操作系统(HarmonyOS)开发前端应用,为用户提供便捷的列车票务查询服务。 1.2 项目目标在华为云平台配置AI Agent,实现车票查询、车次信息解析等核心能力开发鸿蒙应用,通过前端页面与华为云AI A...

华为云AI Agent集成鸿蒙车票查询项目文档

一、项目概述

1.1 项目背景

随着AI技术与移动应用的深度融合,本项目旨在通过华为云配置AI Agent实现智能车票查询能力,并基于鸿蒙操作系统(HarmonyOS)开发前端应用,为用户提供便捷的列车票务查询服务。

1.2 项目目标

  • 在华为云平台配置AI Agent,实现车票查询、车次信息解析等核心能力
  • 开发鸿蒙应用,通过前端页面与华为云AI Agent交互,完成车票查询功能
  • 实现城市信息解析、令牌管理、数据存储等辅助功能,保障应用稳定性

二、技术栈

  • 前端框架:HarmonyOS ArkUI(ETS语言)
  • 后端服务:华为云AI Agent
  • 网络通信:HTTP/HTTPS、SSE(Server-Sent Events)
  • 数据存储:鸿蒙本地存储(Preferences)
  • 开发工具:DevEco Studio、华为云控制台

三、华为云AI Agent配置步骤

3.1 登录华为云控制台

  1. 访问华为云官网(https://www.huaweicloud.com/),登录账号并进入控制台
  2. 搜索“AI Agent”服务,进入服务管理页面

3.2 创建AI Agent实例

  1. 点击“创建Agent”,配置基本信息(名称、描述、所属区域等)
  2. 选择“自定义技能”,配置车票查询相关能力:
    • 定义输入参数:出发城市、到达城市、出发日期
    • 定义输出格式:车次列表(包含车次号、出发时间、到达时间、余票信息等)
  3. 配置API访问凭证(Access Key、Secret Key),记录Agent访问地址(Endpoint)

3.3 测试AI Agent

通过华为云控制台的“在线调试”功能,输入测试参数(如“北京到上海,2025-10-25”),验证Agent是否能正确返回车票信息

四、鸿蒙应用开发架构

4.1 项目目录结构

ets/
├── entryability/              # 应用入口能力
├── pages/                     # 页面组件
│   ├── API/                   # 网络请求相关
│   │   └── http_ai.ets        # 调用华为云AI Agent的HTTP工具
│   ├── Common/                # 公共模型与工具
│   │   └── Model/             # 数据模型定义
│   │       ├── cityModel.ets  # 城市信息模型
│   │       ├── searchHistoryModel.ets  #查询记录模型
│   │       ├── ticketModel.ets  # 车票信息模型
│   │       └── tokenApi/      # 令牌相关模型
│   │           ├── ReceiveModel.ets  # 令牌响应模型
│   │           └── TokenModel.ets    # 令牌请求模型
│   └── Utils/                 # 工具类
│       ├── getData.ets        # 数据获取工具
│       ├── cityParse.ets      # 城市信息解析工具
│       ├── getToken.ets       # 令牌获取与管理
│       ├── MakeCall.ets       # AI Agent调用封装
│       ├── SSEParser.ets      # SSE响应解析工具
│       └── TicketDataStore.ets# 车票数据本地存储
├── index.ets                  # 应用首页
├── TrainHomePage.ets          # 车票查询主页
└── TrainDetails.ets           # 车次详情页

五、核心文件实现

5.1 entryability(应用入口)

import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { CityListResponse } from '../pages/Common/Model/cityModel';
import json from '@ohos.util.json';
import { notificationManager } from '@kit.NotificationKit';

const TAG: string = '[PublishOperation]';
const DOMAIN_NUMBER: number = 0xFF00;
const DOMAIN = 0x0000;

export default class EntryAbility extends UIAbility {
  private fruits: TextCascadePickerRangeContent[] = []

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');

    // city.json获取
    try {
      this.context.resourceManager.getRawFileContent("city.json")
        .then((value: Uint8Array) => {
          console.log("city初始数据:" + value);
          let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
          console.log("city通过解码器转为UTF-8后的数据:" + json.stringify(textDecoder));
          let decodedData = textDecoder.decodeWithStream(value, { stream: false });
          console.log("city完成解码后的数据:" + decodedData);
          try {
            let parseData: CityListResponse = JSON.parse(decodedData);
            console.log("city通过JSON工具类解析后的数据:" + json.stringify(parseData));
            for (let i = 0; i < parseData.cityList.cities.length; i++) {
              this.fruits.push({
                text: parseData.cityList.cities[i].name
              })
            }
            console.log("city通过JSON工具类解析后放入数组的数据:" + JSON.stringify(this.fruits));
            AppStorage.setOrCreate<TextCascadePickerRangeContent[]>('cityList', this.fruits)
          } catch (error) {
            console.error("promise getRawFileContent failed, error is " + error);
          }
        })
        .catch((error: BusinessError) => {
          console.error("getRawFileContent promise error is " + error);
        });
    } catch (error) {
      let code = (error as BusinessError).code;
      let message = (error as BusinessError).message;
      console.error(`promise getRawFileContent failed, error code: ${code}, message: ${message}.`);
    }

    // 请求通知权限
    notificationManager.isNotificationEnabled().then((data: boolean) => {
      hilog.info(DOMAIN_NUMBER, TAG, "isNotificationEnabled success, data: " + JSON.stringify(data));
      if(!data){
        notificationManager.requestEnableNotification(this.context).then(() => {
          hilog.info(DOMAIN_NUMBER, TAG, `[ANS] requestEnableNotification success`);
        }).catch((err: BusinessError) => {
          if(1600004 == err.code){
            hilog.error(DOMAIN_NUMBER, TAG, `[ANS] requestEnableNotification refused, code is ${err.code}, message is ${err.message}`);
          } else {
            hilog.error(DOMAIN_NUMBER, TAG, `[ANS] requestEnableNotification failed, code is ${err.code}, message is ${err.message}`);
          }
        });
      }
    }).catch((err: BusinessError) => {
      hilog.error(DOMAIN_NUMBER, TAG, `isNotificationEnabled fail, code is ${err.code}, message is ${err.message}`);
    });

    // 发布通知
    let notificationRequest: notificationManager.NotificationRequest = {
      id: 1,
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, // 普通文本类型通知
        normal: {
          title: 'test_title',
          text: 'test_text',
          additionalText: 'test_additionalText',
        }
      }
    };
    notificationManager.publish(notificationRequest, (err: BusinessError) => {
      if (err) {
        hilog.error(DOMAIN_NUMBER, TAG, `Failed to publish notification. Code is ${err.code}, message is ${err.message}`);
        return;
      }
      hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in publishing notification.');
    });

  }

  onDestroy(): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
  }


  onWindowStageCreate(windowStage: window.WindowStage): void {
    // Main window is created, set main page for this ability
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }


  onWindowStageDestroy():
    void {
    // Main window is destroyed, release UI related resources
    hilog
      .info
      (
        DOMAIN,
        'testTag',
        '%{public}s',
        'Ability onWindowStageDestroy'
      )
    ;
  }

  onForeground():
    void {
    // Ability has brought to foreground
    hilog
      .info
      (
        DOMAIN,
        'testTag',
        '%{public}s',
        'Ability onForeground'
      )
    ;
  }

  onBackground():
    void {
    // Ability has back to background
    hilog
      .info
      (
        DOMAIN,
        'testTag',
        '%{public}s',
        'Ability onBackground'
      )
    ;
  }
}

5.2 页面组件

5.2.1 index.ets(应用首页)

import { router } from '@kit.ArkUI'
import { getData } from './Utils/getData'
import { common } from '@kit.AbilityKit'
import { AITicketResponse } from './Commons/Model/ticketModel'
import { SearchHistory } from './Commons/Model/searchHistoryModel'
import { relationalStore } from '@kit.ArkData'

@Entry
@Component
struct Index {
  @State cityList: TextCascadePickerRangeContent[] = []
  @State fromCityIndex: number = 0
  @State fromCityString: string = '郑州市'
  @State toCityIndex: number = 0
  @State toCityString: string = '上海市'
  @State selectedDate: Date = new Date()
  @State aiResponse: AITicketResponse | null = null
  @State searchHistory: SearchHistory[] = [];
  private context = getContext(this) as common.UIAbilityContext;
  private rdbStore: relationalStore.RdbStore | null = null;
  private readonly STORE_CONFIG: relationalStore.StoreConfig = {
    name: 'TrainTicketDB.db',
    securityLevel: relationalStore.SecurityLevel.S2
  };
  private readonly TABLE_NAME: string = 'search_history';
  private readonly SQL_CREATE_TABLE: string = `
    CREATE TABLE IF NOT EXISTS ${this.TABLE_NAME} (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      fromCity TEXT NOT NULL,
      toCity TEXT NOT NULL
    )
  `;

  async initRdbStore(): Promise<void> {
    try {
      this.rdbStore = await relationalStore.getRdbStore(this.context, this.STORE_CONFIG);
      await this.rdbStore.executeSql(this.SQL_CREATE_TABLE);
      console.info('数据库初始化成功');
    } catch (err) {
      console.error(`数据库初始化失败: ${JSON.stringify(err)}`);
    }
  }

  async saveSearchHistory(): Promise<void> {
    if (!this.rdbStore) {
      await this.initRdbStore();
    }

    console.info('数据库保存开始', this.fromCityString, this.toCityString);

    // 修复:使用正确的目的地
    const valueBucket: relationalStore.ValuesBucket = {
      'fromCity': this.fromCityString,
      'toCity': this.toCityString
    };

    try {
      await this.rdbStore!.insert(this.TABLE_NAME, valueBucket);
      console.info('搜索记录保存到数据库成功');
      await this.loadSearchHistory();
    } catch (err) {
      console.error(`保存搜索记录到数据库失败: ${err}`);
    }
  }

  async loadSearchHistory(): Promise<void> {
    if (!this.rdbStore) {
      await this.initRdbStore();
    }

    try {
      const predicates = new relationalStore.RdbPredicates(this.TABLE_NAME);
      predicates.orderByDesc('id');
      predicates.limitAs(10);

      const resultSet = await this.rdbStore!.query(predicates,
        ['id', 'fromCity', 'toCity']);

      this.searchHistory = [];
      while (resultSet.goToNextRow()) {
        const history: SearchHistory = {
          id: resultSet.getLong(resultSet.getColumnIndex('id')),
          fromCity: resultSet.getString(resultSet.getColumnIndex('fromCity')),
          toCity: resultSet.getString(resultSet.getColumnIndex('toCity'))
        };
        this.searchHistory.push(history);
      }
      resultSet.close();
    } catch (err) {
      console.error(`加载搜索历史失败: ${JSON.stringify(err)}`);
    }
  }

  aboutToAppear(): void {
    // 初始化数据库,完成后保存当前搜索记录
    this.initRdbStore().then(() => {
      this.saveSearchHistory();
    });
  }

  build() {
    Column({ space: 10 }) {
      // 出发地、目的地的选择和显示功能
      Row({ space: 10 }) {
        Column({ space: 10 }) {
          Button('出发地')
            .onClick(() => {
              this.getUIContext().showTextPickerDialog({
                range: AppStorage.get('cityList') as TextCascadePickerRangeContent[],
                selected: this.fromCityIndex,
                onChange: (value: TextPickerResult) => {
                  this.fromCityString = value.value as string
                },
                onAccept: (value: TextPickerResult) => {
                  this.fromCityIndex = value.index as number
                  this.fromCityString = value.value as string
                }
              });
            })
          Text(this.fromCityString)
        }

        Text('——').fontWeight(FontWeight.Bold)

        Column({ space: 10 }) {
          Button('目的地')
            .onClick(() => {
              this.getUIContext().showTextPickerDialog({
                range: AppStorage.get('cityList') as TextCascadePickerRangeContent[],
                selected: this.toCityIndex,
                onChange: (value: TextPickerResult) => {
                  this.toCityString = value.value as string
                },
                onAccept: (value: TextPickerResult) => {
                  this.toCityIndex = value.index as number
                  this.toCityString = value.value as string
                }
              });
            })
          Text(this.toCityString)
        }
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)

      // 日期选择功能
      Row({ space: 10 }) {
        Text(this.selectedDate.toLocaleDateString() + '')
          .padding({
            left: 15,
            right: 15,
            top: 10,
            bottom: 10
          })
          .borderRadius(20)
          .fontColor('#ffffffff')
          .backgroundColor('#007dfe')
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.getUIContext().showDatePickerDialog({
              start: new Date(),
              selected: this.selectedDate,
              onDateChange: (value: Date) => {
                this.selectedDate = value
              }
            })
          })
      }

      // 搜索记录展示
      Row() {
        ForEach(this.searchHistory, (item: SearchHistory, index) => {
          if (index <= 2) {
            Text(item.fromCity + '--' + item.toCity)
              .fontSize(12)
              .fontColor('#8a8a8a')
              .layoutWeight(1)
          }
        })
      }
      .width('90%')

      Button('查询车次')
        .onClick(() => {
          router.pushUrl({
            url: 'pages/TrainDetails',
            params: {
              fromCity: this.fromCityString,
              toCity: this.toCityString,
              selectedDate: this.selectedDate.toLocaleDateString(),
              aiResponse: this.aiResponse
            }
          }, (err) => {
            if (err) {
              console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
              return;
            }
            console.info('Invoke pushUrl succeeded.');
          })
          this.saveSearchHistory()
        })

      Button('展示AI查询结果')
        .onClick(async () => {
          await getData(this.fromCityString, this.toCityString, this.selectedDate.toLocaleDateString(),
            getContext(this) as common.UIAbilityContext)
            .then((response) => {
              this.aiResponse = response;
            })
        })

      Scroll() {
        Text(this.aiResponse ? JSON.stringify(this.aiResponse) : '暂无数据')
      }
    }
    .width('100%')
    .height('100%')
  }
}

5.2.2 TrainHomePage.ets(车票查询主页)

import { webview } from '@kit.ArkWeb';

@Entry
@Component
struct TrainHomePage {
  webviewController: webview.WebviewController = new webview.WebviewController();

  build() {
    Column() {
      Web({
        src: "https://www.12306.cn",
        controller: this.webviewController,
      })
    }
    .width('100%')
    .height('100%')
  }
}

5.2.3 TrainDetails.ets(车次详情页)

import { router } from '@kit.ArkUI'
import { TicketItem, SeatType, AITicketResponse } from './Common/Model/ticketModel'
import { JSON } from '@kit.ArkTS'
import { MakeCall } from './Utils/MakeCall'

class TrainDetailsProps {
  fromCity?: string
  toCity?: string
  selectedDate?: string
  aiResponse: AITicketResponse | null = null
}

@Entry
@Component
struct TrainDetails {
  scroller: Scroller = new Scroller();
  @State fromCity: string = '郑州' // 出发城市
  @State toCity: string = '上海' // 目的城市
  @State aiResponse: AITicketResponse | null = null
  @State selectedDateStr: string = '今天 10.19' // 日期显示字符串
  @State ticketList: TicketItem[] = this.aiResponse?.ticketList || []
  @State phoneNumber: string = '400 - 888 - 8888'

  aboutToAppear(): void {
    const params = router.getParams() as TrainDetailsProps
    if (params) {
      this.fromCity = params.fromCity || '郑州'
      this.toCity = params.toCity || '上海'
      this.aiResponse = params.aiResponse
      // 日期参数处理(若需动态日期,可扩展此处)
      if (params.selectedDate) {
        this.selectedDateStr = `今天 ${params.selectedDate}`
      }
    }

    console.log('ai对话返回结果:', JSON.stringify(this.aiResponse))
    console.log('ai对话 selectedDate:', JSON.stringify(this.aiResponse))
    console.log('ai对话返回列表结果:', JSON.stringify(this.aiResponse?.ticketList))
    console.log('ai对话赋值给列表:', this.ticketList.toString())
    console.log('ai对话返回的信息中的的温馨提示:', this.aiResponse?.tips.toString())

  }

  // 查找第一个有余票的席位(只包含"张"的才算有票)
  getFirstAvailableSeat(seatTypes: SeatType[]): SeatType | null {
    for (let seat of seatTypes) {
      if (seat.ticketStatus.includes('张')) {
        return seat
      }
    }
    return null
  }

  //构建车次项
  @Builder
  TrainItemBuilder(item: TicketItem, index: number) {
    // 在@Builder中调用方法获取有余票的席位
    // let availableSeat: SeatType | null = this.getFirstAvailableSeat(item.seatTypes)

    Column() {
      // 车次核心信息行:出发时间/站 + 车次/历时 + 到达时间/站 + 票价
      Row({ space: 20 }) {
        // 出发信息:时间 + 站名
        Column({ space: 5 }) {
          Text(item.departureTime).fontSize(18).fontColor(Color.Black)
          Text(item.departureStation).fontSize(14).fontColor('#666')
        }

        // 车次与历时
        Column({ space: 5 }) {
          Text(item.trainNo).fontSize(16).fontColor(Color.Black)
          Text(item.duration).fontSize(14).fontColor('#666')
        }

        // 到达信息:时间 + 站名
        Column({ space: 5 }) {
          Text(item.arrivalTime).fontSize(18).fontColor(Color.Black)
          Text(item.arrivalStation).fontSize(14).fontColor('#666')
        }

        // 票价显示:显示第一个有余票的席位价格,如果没有则显示售罄
        Column({ space: 5 }) {
          // if (availableSeat) {
          Row() {
            Text('¥').fontSize(14).fontColor('#f98131')
            Text(item.seatTypes[0].price.split('元')[0]).fontSize(16).fontColor('#f98131')
            Text('起').fontSize(14).fontColor('#f98131')
          }
        }
      }
      .padding(10)

      Divider().width('90%')

      // 席别与余票状态行
      Row({ space: 15 }) {
        ForEach(item.seatTypes, (seat: SeatType) => {
          Row({ space: 5 }) {
            Text(seat.seatName).fontSize(12).fontColor('#666')
            // 不同余票状态,颜色区分
            if (seat.ticketStatus === '候补+') {
              Text(seat.ticketStatus).fontSize(12).fontColor('#007dff')
            } else if (seat.ticketStatus.includes('张')) {
              Text(seat.ticketStatus).fontSize(12).fontColor('#ff32e90b')
            } else if (seat.ticketStatus === '售罄') {
              Text(seat.ticketStatus).fontSize(12).fontColor('#999')
            } else {
              Text(seat.ticketStatus).fontSize(12).fontColor('#666')
            }
          }
        })
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceAround)
      .padding({
        left: 10,
        right: 10,
        top: 10,
        bottom: 10
      })

    }
    .width('95%')
    .margin({
      left: '2.5%',
      right: '2.5%',
      top: index == 0 ? 10 : 5,
      bottom: 5
    })
    .borderRadius(8)
    .backgroundColor(Color.White)
  }

  build() {
    Column() {
      // 顶部导航栏:城市切换 + 日期显示
      Row({ space: 10 }) {
        Text('<').fontSize(20).fontColor(Color.White).fontWeight(FontWeight.Bolder)
          .onClick(() => router.back())

        Row() {
          Text(this.fromCity).fontColor(Color.White).fontWeight(FontWeight.Bolder)
          Text(' <> ').fontColor(Color.White).fontWeight(FontWeight.Bolder)
          Text(this.toCity).fontColor(Color.White).fontWeight(FontWeight.Bolder)
        }

        Text(this.selectedDateStr).fontColor(Color.White).fontWeight(FontWeight.Bolder)
      }
      .width('100%')
      .padding({
        left: 15,
        right: 15,
        top: 10,
        bottom: 10
      })
      .backgroundColor('#3b99fb')
      .justifyContent(FlexAlign.SpaceBetween)

      // 车次标签页(直达/中转)
      Tabs() {
        // 直达标签内容
        TabContent() {
          Column() {
            List() {
              ForEach(this.aiResponse?.ticketList, (item: TicketItem, index) => {
                ListItem() {
                  Column() {
                    // 使用@Builder构建每个车次项
                    this.TrainItemBuilder(item, index)
                    // 温馨提示
                    if (index == (this.aiResponse?.ticketList.length as number) - 1) {
                      Column({ space: 5 }) {
                        Text('温馨提示').width('90%').fontSize(14).fontColor('#ffff7500').margin({ top: 10 })
                        ForEach(this.aiResponse?.tips, (tip: string) => {
                          Text(tip).width('90%').fontSize(12).fontColor('#999')
                        })
                      }

                      Column({ space: 10}) {
                        Text('12306官网')
                          .fontColor('#1867f5')
                          .onClick(() => {
                            router.push({
                              url: 'pages/TrainHomePage'
                            })
                          })
                        Text(`可咨询详情请拨打:${this.phoneNumber}`)
                          .fontColor('#1867f5')
                          .onClick(() => {
                            MakeCall.makeCall(this.phoneNumber)
                          })
                      }
                      .margin({ top: 10 })
                    }
                  }
                }
              })
            }
            .width('100%')
            .height('100%')
            .scrollBar(BarState.Off)
            .backgroundColor('#f5f5f5')


          }
        }
        .tabBar('直达')


        // 中转标签内容
        TabContent() {
          Text('中转功能待完善')
            .width('100%')
            .height('100%')
            .textAlign(TextAlign.Center)
            .backgroundColor('#f5f5f5')
        }.tabBar('中转')
      }
      .width('100%')
      .height('100%')
      .barHeight(50)
    }
    .width('100%')
    .height('100%')
  }
}

5.3 API模块(网络请求)

5.3.1 http_ai.ets(AI Agent HTTP请求工具)

import { rcp } from '@kit.RemoteCommunicationKit'
import { auth, Inputs } from '../Common/Model/tokenApi/TokenModel'

// const url =  'https://123.249.99.67/v1/9d72d169ec634537bd3cfe7fa066eed6/agents/0a32ae0d-8f66-42b1-9d83-bb827d445332/conversations/1?version=1760701576547' // 王
const url =  'https://123.249.99.67/v1/b188216baed24ebfae78ed6de041db54/agents/450c4d64-1caf-4189-b881-5d829380e3e3/conversations/1?version=1760932844103' // 杨
export async function get_ai(token:string,a:Inputs){
  try {
    const headers:rcp.RequestHeaders = {
      'X-Auth-Token':token,
      'Content-Type': 'application/json'
    }
    const  security:rcp.SecurityConfiguration = {
      remoteValidation:'skip'
    }
    const session =  rcp.createSession({
      headers:headers,
      requestConfiguration:{
        security:security
      }
    })

    const post_ai =await session.post(url,a)
    return post_ai;
  }catch (e) {
    console.log('ai回答_get_ai_error'+JSON.stringify(e))
    return null
  }
}


const url_token = 'https://iam.cn-north-4.myhuaweicloud.com/v3/auth/tokens'
export async function token_api():Promise<rcp.Response | undefined>{
  const headers: rcp.RequestHeaders = {
    'Content-Type': 'application/json'
  }
  const session = rcp.createSession({
    headers: headers
  })
  try {

    return await session.post(url_token, new auth() )

  } catch (e) {
    console.error(JSON.stringify(e) + "ai调用3")
  }
  return undefined
}

5.4 Common/Model模块(数据模型)

5.4.1 cityModel.ets(城市信息模型)

// city.json解析接口
export interface City {
  id: number;
  name: string;
  province: string;
  pinyin: string;
  shortPinyin: string;
}

export interface CityList {
  updateTime: string;
  cities: City[];
}

export interface CityListResponse {
  cityList: CityList;
}

5.4.2 searchHistoryModel.ets(查询记录模型)

export  interface SearchHistory {
  id: number;
  fromCity: string;
  toCity: string;
}

5.4.3 ticketModel.ets(车票信息模型)

// pages/Common/Model/ticketModel.ets
export interface QueryCondition {
  departureCity: string; // 出发城市(与AI JSON字段一致)
  arrivalCity: string;   // 到达城市
  travelDate: string;    // 出行日期(yyyy-MM-dd)
}

export interface SeatType {
  seatName: string;      // 席别名称(如"二等座")
  price: string;         // 票价(含单位,如"553元")
  ticketStatus: string;  // 余票状态(如"有票"/"无票"/"候补")
}

export interface TicketItem {
  trainNo: string;               // 车次编号(如"G101")
  departureStation: string;      // 出发车站(如"北京南站")
  arrivalStation: string;        // 到达车站(如"上海虹桥站")
  departureTime: string;         // 出发时间(yyyy-MM-dd HH:mm:ss)
  arrivalTime: string;           // 到达时间(yyyy-MM-dd HH:mm:ss)
  duration: string;              // 历时(如"6小时30分")
  seatTypes: SeatType[];         // 该车次的席别列表
}

export interface TicketMetadata {
  dataGenerateTime: string;  // 数据生成时间(yyyy-MM-dd HH:mm:ss)
  dataSource: string;        // 数据来源(如"12306-MCP对接数据")
  status: "success" | "fail";// 数据状态
}

export interface AITicketResponse {
  queryCondition: QueryCondition; // 查询条件
  ticketList: TicketItem[];       // 车次列表
  metadata: TicketMetadata;       // 元数据
  tips: string[];                 // 提示信息
}

export interface LocalTicketResponse {
  parseStatus: "success" | "fail" | "no_data"; // 解析状态
  message: string;                             // 状态描述(如错误信息)
  data?: AITicketResponse;                     // 结构化票务数据(成功时存在)
}

5.4.4 tokenApi/TokenModel.ets(令牌请求模型)

// pages/Common/Model/tokenApi/TokenModel.ets
//调取请求AIM获取token的请求体
class Domain {
  // name:string ="GT-gcw_XaRUNQkg_Syk" // 王
  name: string = "hid_4ocll2m1t6hwqj2" // 杨
}

class User {
  domain: Domain = new Domain()
  // name:string = "wwh" // 王
  name: string = "Luo Yu" // 杨
  // password:string = "wang2006" // 王
  password: string = "By107871895123." // 杨
}

class Password {
  user: User = new User()
}

class Identity {
  methods: string[] = ["password"]
  password: Password = new Password()
}

class Project {
  // id:string = "9d72d169ec634537bd3cfe7fa066eed6" // 王
  name: string = "cn-north-4" // 杨
}

class Scope {
  project: Project = new Project()
}


interface GeneratedTypeLiteralInterface_1 {
  identity: Identity;
  scope: Scope;
}

export class auth {
  auth: GeneratedTypeLiteralInterface_1 = {
    identity: new Identity(),
    scope: new Scope()
  }
}

//ai参数
interface GeneratedTypeLiteralInterface_2 {
  query: string
}

export class Inputs {
  inputs: GeneratedTypeLiteralInterface_2 = {
    query: '你好'
  }
}
// 一、角色与任务定义
// 你是 “火车票查询系统” 的专属数据生成 Agent,需基于用户输入的火车票查询条件,返回标准化、结构化的数据,用于鸿蒙端应用(ArkTS 开发)的 API 接口调用,数据需完全匹配 (如直达车次展示、票价参考、余票提示等)。
// 二、查询条件必填项(用户需提供,Agent 需校验)
// 出发城市:需为国内地级市及以上城市名称(如 “深圳”“西安”,不可为 “广东省”“西安市长安区” 等非标准城市名);
// 到达城市:同 “出发城市” 规范,且不可与出发城市重复;
// 出行日期:需为 “yyyy-MM-dd” 格式(如 “2025-10-01”),且需为当前日期及未来 15天内(符合 12306 常规预售期逻辑);
// 若用户未提供上述任一必填项,需先返回提示:“请补充完整查询条件(出发城市、到达城市、出行日期,日期格式为 yyyy-MM-dd)”,不生成无效数据。
// 三、输出数据字段规范(API 数据源核心字段)
// 需返回包含「查询条件回显」「车票列表」「数据元信息」「温馨提示」4 大类的结构化数据,每个字段的定义、格式、约束如下表:
// 数据大类	字段名	字段定义	格式要求	约束条件
// 查询条件回显	departureCity	用户查询的出发城市	字符串(如 “深圳”)	非空,需与用户输入一致
// arrivalCity	用户查询的到达城市	字符串(如 “西安”)	非空,需与用户输入一致,且≠departureCity
// travelDate	用户查询的出行日期	字符串(yyyy-MM-dd,如 “2025-10-01”)	非空,需在当前日期 + 15天内
// 车票列表(array)	trainNo	车次编号(如 G102、Z23)	字符串	非空,符合铁路车次编码规则(G/D/C/Z/T/K 开头 + 数字)
// departureStation	出发车站(需含城市名,如 “深圳北站”“西安站”)	字符串	非空,需属于 departureCity
// arrivalStation	到达车站(需含城市名,如 “西安北站”“深圳站”)	字符串	非空,需属于 arrivalCity
// departureTime	出发时间	字符串(HH:mm,如 “2025-10-01 08:00”)	非空,需匹配 travelDate
// arrivalTime	到达时间	字符串( HH:mm,如 “2025-10-01 14:30”)	非空,需晚于 departureTime
// duration	历时(如 “6 小时 30 分”)	字符串	非空,需根据 departureTime 与 arrivalTime 计算
// seatTypes(array)	席别及对应信息	数组(含子字段)	非空,至少包含 “二等座”“一等座”,最多含 “商务座”“无座”
// - seatName	席别名称	字符串(如 “二等座”“一等座”“商务座”“无座”)	非空,需为标准席别名称
//   - price	席别票价	字符串(含单位 “元”,如 “580 元”)	非空,需符合对应席别市场价格范围
//   - ticketStatus	余票状态	字符串(仅可选 “有票”“无票”“候补”)	非空,需与席别匹配
// 数据元信息	dataGenerateTime	数据生成时间(Agent 返回数据的时间)	字符串(yyyy-MM-dd HH:mm:ss,如 “2025-09-30 10:15:30”)	非空,自动生成当前时间
// dataSource	数据来源	字符串(固定为 “12306-MCP 对接数据”)	不可修改,固定值
// status	数据返回状态	字符串(仅可选 “success”“fail”)	非空,查询成功为 “success”,失败为 “fail”
// 温馨提示	tips(array)	火车票相关提示信息(退票、改签、实名制等)	字符串数组	非空,至少包含 2 条提示,最多 5 条
// 四、返回格式约束(强制 JSON 格式,不可增减字段)
// 仅返回 JSON 数据,无任何多余文本(如 “好的,查询结果如下:” 等描述性文字);
// JSON 字段大小写严格匹配上述 “输出数据字段规范”(如 departureCity 不可写为 DepartureCity);
// 数组类型字段(如 ticketList、seatTypes、tips)需保证元素格式统一,不可出现空数组;
// 若查询无结果(如无该日期的车次),需将 ticketList 设为空数组,status 设为 “success”,并在 tips 中添加 “当前日期无从 {departureCity} 到 {arrivalCity} 的直达车次,请调整日期或查询中转方案”;
// 示例格式如下(仅为示例,需根据实际查询条件生成真实数据):
// json
// {
//   "queryCondition": {
//   "departureCity": "深圳",
//   "arrivalCity": "西安",
//   "travelDate": "2025-10-01"
// },
//   "ticketList": [
//   {
//     "trainNo": "G82",
//   "departureStation": "深圳北站",
//   "arrivalStation": "西安北站",
//   "departureTime": "08:00:00",
//   "arrivalTime": "14:30:00",
//   "duration": "6小时30分",
//   "seatTypes": [
//     {
//       "seatName": "二等座",
//     "price": "580元",
//     "ticketStatus": "有票"
//     },
//     {
//       "seatName": "一等座",
//     "price": "920元",
//     "ticketStatus": "有票"
//     },
//     {
//       "seatName": "商务座",
//     "price": "1780元",
//     "ticketStatus": "候补"
//     }
//     ]
//   },
//   {
//     "trainNo": "G1024",
//   "departureStation": "深圳站",
//   "arrivalStation": "西安站",
//   "departureTime": "10:15:00",
//   "arrivalTime": "17:05:00",
//   "duration": "6小时50分",
//   "seatTypes": [
//     {
//       "seatName": "二等座",
//     "price": "565元",
//     "ticketStatus": "无票"
//     },
//     {
//       "seatName": "一等座",
//     "price": "900元",
//     "ticketStatus": "候补"
//     },
//     {
//       "seatName": "无座",
//     "price": "565元",
//     "ticketStatus": "有票"
//     }
//     ]
//   }
//   ],
//   "metadata": {
//   "dataGenerateTime": "2025-09-30 10:15:30",
//   "dataSource": "12306-MCP对接数据",
//   "status": "success"
// },
//   "tips": [
//   "退票需在发车前8天以上免手续费,发车前48小时-8天手续费5%,发车前24小时-48小时手续费10%,发车前24小时内手续费20%",
//   "乘车需携带购票时所用的有效身份证件原件(身份证、护照等)",
//   "儿童票为成人票的50%,身高1.2-1.5米儿童需购买儿童票,1.5米以上需购买成人票"
//   ]
// }
// 五、数据约束补充
// 车次编号(trainNo)需符合真实铁路编码逻辑:高铁(G)、动车(D)、城际(C)、直达特快(Z)、特快(T)、快速(K),不可虚构无意义编码(如 “G000”“X123”);
// 出发 / 到达车站需为对应城市的真实车站(如深圳的 “深圳北站”“深圳站”“深圳东站”,不可写 “深圳西站”“深圳南站” 等不存在的车站);
// 票价需符合对应车次类型的市场价格范围(如深圳到西安的高铁二等座约 550-600 元,不可出现 “100 元”“2000 元” 等异常价格);
// 历时计算需准确(如出发时间 08:00、到达时间 14:30,历时需为 “6 小时 30 分”,不可出现 “5 小时 90 分”“7 小时” 等错误)。
// 响应速度要在20秒内

5.4.5 tokenApi/ReceiveModel.ets(令牌响应模型)

// pages/Common/Model/tokenApi/ReceiveModel.ets
/**
 * 华为云IAM认证接口模型
 *
 */
export interface AuthResponse {

  token: Token;
}

/**
 * Token核心数据结构(JSON中token对象的内容)
 */
export interface Token {
  // token过期时间
  expires_at: string;
  methods: string[];

}

export interface data {
  exent: string
  content: string
  createdTime: string
}

5.5 Utils模块(工具类)

5.5.1 getToken.ets(令牌获取与管理)

// pages/Utils/getToken.ets
import { preferences } from "@kit.ArkData"
import { common } from "@kit.AbilityKit"
import { token_api } from "../API/http_ai"
import { BusinessError } from "@kit.BasicServicesKit"
import { util } from "@kit.ArkTS"
import { AuthResponse } from "../Common/Model/tokenApi/ReceiveModel"

interface returnData {
  token_get: string
  time_get: string
}

export class Token_management {
  private name: string = 'Token'
  private preferences: preferences.Preferences | null = null
  private context: common.UIAbilityContext

  // 外部调用方传入上下文
  constructor(context: common.UIAbilityContext) {
    this.context = context
  }

  //初始化preferences
  async init(): Promise<boolean> {
    try {
      this.preferences = await preferences.getPreferences(this.context, this.name)
      console.log('初始化成功')
      return true
    } catch (err) {
      return false
    }
  }

  //调用获取token的api
  async get_new_token(): Promise<returnData | null> {
    if (!this.preferences) {
      return null
    }

    let token_get: string = ''
    let time_get: string = ''
    try {
      await token_api().then(response => {
        console.log('获取token' + response)
        token_get = response?.headers['x-subject-token'] as string;
        time_get = buf2String(response?.body).token.expires_at
      })

      if (token_get && time_get) {
        console.log('get_new_token' + "获取成功")
        return { token_get, time_get }
      } else {
        console.log("返回值为空")
        return null
      }
    } catch (err) {
      console.error('token_get' + err)
      return null
    }
  }

  /**
   * 储存新的token
   */
  async set_token(): Promise<string | undefined> {
    if (!this.preferences) {
      console.log(this.preferences + '')
      return undefined
    }
    //判读本地是否有token
    if (this.preferences?.hasSync('token') && this.preferences.hasSync('time')) {
      const storedToken = this.preferences.getSync('token', '') as string;
      const storedExpireTime = this.preferences.getSync('time', '') as string;
      //判断是否过期
      if (!this.is_overdue(storedExpireTime)) {
        console.log('token可以直接使用')
        return storedToken
      } else {
        //已经过期重新将新的放在容器中 获取新的token
        const new_token_vessel = await this.get_new_token()
        const new_token = new_token_vessel?.token_get
        const new_time = new_token_vessel?.time_get
        //将获取到的token等持久化
        this.persistent_token(new_token, new_time)
        return new_token
      }
    } else {
      //本地并没有储存调用接口来进行储存
      const new_token_vessel = await this.get_new_token()
      const new_token = new_token_vessel?.token_get
      const new_time = new_token_vessel?.time_get
      //将获取到的token等持久化
      this.persistent_token(new_token, new_time)
      return new_token
    }
  }

  /**
   * 拿到token可以自动判断是否过期
   * 获取token
   */
  async get_token(): Promise<string | null> {
    try {
      if (this.preferences?.hasSync('token') && this.preferences.hasSync('time')) {
        const storedExpireTime = this.preferences.getSync('time', 'defValue') as string;
        if (!this.is_overdue(storedExpireTime)) {
          //没有过期可以直接使用
          const token_get = this.preferences.getSync('token', 'defValue')
          return token_get as string
        } else {
          //已经过期或不存在
          return null
        }
      } else {
        console.log('没有本地储存,进行储存')
        // this.persistent_token()
        return null
      }
    } catch (err) {
      console.log('获取token失败' + err)
      return null
    }
  }

  //将获取到的token等持久化
  async persistent_token(new_token: string | undefined, new_time: string | undefined): Promise<boolean> {
    if (!this.preferences) {
      return false
    }
    if (new_token && new_time) {
      this.preferences.putSync(new_token, 'new_token')
      this.preferences.putSync(new_time, 'new_time')

      await new Promise<void>((resolve, reject) => {
        this.preferences!.flush((err: BusinessError | undefined) => {
          if (err) {
            console.error(`Token持久化失败: 代码=${err.code}, 消息=${err.message}`);
            reject(err);
          } else {
            console.info('Token已成功持久化到本地');
            resolve();
          }
        });
      });

      return true
    } else {
      return false
    }
  }

  //判断token是否过期如果过期就重新获取token
  async is_overdue(End_time: string): Promise<boolean> {
    try {
      const end_time = new Date(End_time)
      const Current_time = new Date()
      return end_time.getTime() < Current_time.getTime()
    } catch (err) {
      console.log(err + '时间比对失败')
      return true
    }
  }

  //获取当前时间
  get_Current_time(): string {
    return new Date().toISOString()
  }

  async loadToken(): Promise<string | undefined> {
    if (!this.get_token() == null) {
      //可以正常获取到token
      const token_get = await this.preferences?.getSync('token', 'defValue') as string
      return token_get
    } else {
      //token过期或不存在时调用储存方法来重新获取
      return await this.set_token()
    }
  }
}
//接收返回数据进行解析
function buf2String(buf: ArrayBuffer | undefined) {
  let msgArray = new Uint8Array(buf as ArrayBuffer);
  let textDecoder = util.TextDecoder.create("utf-8");
  let rString = textDecoder.decodeWithStream(msgArray);
  return JSON.parse(rString) as AuthResponse;
}

5.5.2 cityParse.ets(城市信息解析工具)

// pages/Utils/cityParse.ets
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';
import json from '@ohos.util.json';
import { CityListResponse } from '../Common/Model/cityModel';

const DOMAIN = 0x0000;

export default class EntryAbility extends UIAbility {
  private fruits: TextCascadePickerRangeContent[] = []

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    try {
      this.context.resourceManager.getRawFileContent("city.json")
        .then((value: Uint8Array) => {
          //////
          let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
          let decodedData = textDecoder.decodeWithStream(value, { stream: false });
          try {
            let parsedData: CityListResponse = JSON.parse(decodedData);
            console.log("city通过JSON工具类解析后的数据:" + json.stringify(parsedData));
            for (let i = 0; i < parsedData.cityList.cities.length; i++) {
              this.fruits.push({
                text: parsedData.cityList.cities[i].name
              })
            }
            console.log("city通过JSON工具类解析后放入数组的数据:" + this.fruits);
            AppStorage.SetOrCreate<TextCascadePickerRangeContent[]>('globalCityListData', this.fruits);
            //////////
          } catch (e) {
            console.error('json文件' + json.stringify(e))
          }
        })
        .catch((error: BusinessError) => {
          console.error("json文件getRawFileContent promise error is " + error);
        });
    } catch (error) {
      let code = (error as BusinessError).code;
      let message = (error as BusinessError).message;
      console.error(`json文件promise getRawFileContent failed, error code: ${code}, message: ${message}.`);
    }
    this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
  }
}

5.5.3 MakeCall.ets(AI Agent调用封装)

// pages/Utils/MakeCall.ets
// import需要的模块
import { call } from '@kit.TelephonyKit';
import { BusinessError } from '@kit.BasicServicesKit';

export class MakeCall {
   public static  makeCall(phoneNumber: string): void {
     // 调用查询能力接口
     let isSupport = call.hasVoiceCapability();
     if (isSupport) {
       // 如果设备支持呼叫能力,则继续跳转到拨号界面,并显示拨号的号码
       // 从API15开始支持tel格式电话号码,如:"tel:13xxxx"
       call.makeCall(phoneNumber, (err: BusinessError) => {
         if (!err) {
           console.info("make call success.");
         } else {
           console.error("make call fail, err is:" + JSON.stringify(err));
         }
       });
     }
  }
}



5.5.4 SSEParser.ets(SSE响应解析工具)

// pages/Utils/SSEParser.ets
import { AITicketResponse, LocalTicketResponse } from '../Common/Model/ticketModel';
import { TicketDataStore } from './TicketDataStore';

// SSE原始事件结构
interface RawSSEEvent {
  event: string;       // 事件类型(如"summary_response"/"message")
  content?: string;    // 核心内容
  role?: string;       // 可选字段
  createdTime?: number;// 可选字段
}

// SSE解析器
export class SSEParser {
  // 主方法:解析SSE数据 → 结构化票务数据 → 存入全局存储
  public static parseAndStore(sseData: string): LocalTicketResponse {
    try {
      // 1. 筛选有效SSE行(仅保留"data:"开头且非空的行)
      const validLines = sseData.split('\n')
        .map(line => line.trim())
        .filter(line => line.startsWith('data:') && line.slice(5).trim());
      if (validLines.length === 0) {
        const res: LocalTicketResponse = {
          parseStatus: "no_data",
          message: "SSE响应无有效内容"
        };
        TicketDataStore.getInstance().setData(res);
        return res;
      }

      // 2. 优先解析"summary_response"事件
      for (const line of validLines) {
        const sseEvent = SSEParser.parseSingleLine(line);
        if (sseEvent?.event === 'summary_response' && sseEvent.content) {
          console.log('原始content:', sseEvent.content); // 打印原始内容
          const cleanedContent = SSEParser.cleanContent(sseEvent.content);
          if (!cleanedContent) {
            throw new Error("清理后无有效JSON内容");
          }
          console.log('清理后content:', cleanedContent);
          // 解析纯净的JSON
          const aiData = JSON.parse(cleanedContent) as AITicketResponse;
          const res: LocalTicketResponse = {
            parseStatus: "success",
            message: "解析成功(来源:summary_response,已清理格式)",
            data: aiData
          };
          TicketDataStore.getInstance().setData(res);
          return res;
        }
      }

      // 3. 解析"message"事件(多段拼接)
      const fullContent = validLines
        .map(line => SSEParser.parseSingleLine(line))
        .filter(event => event?.event === 'message')
        .map(event => event?.content || '')
        .join('');

      if (fullContent) {
        const aiData = JSON.parse(fullContent) as AITicketResponse;
        const res: LocalTicketResponse = {
          parseStatus: "success",
          message: "解析成功(来源:message拼接)",
          data: aiData
        };
        TicketDataStore.getInstance().setData(res);
        return res;
      }

      // 4. 无匹配事件
      const noMatchRes: LocalTicketResponse = {
        parseStatus: "no_data",
        message: "未找到summary_response或message事件"
      };
      TicketDataStore.getInstance().setData(noMatchRes);
      return noMatchRes;

    } catch (error) {
      // 解析异常(如JSON格式错误)
      const errMsg = error instanceof Error ? error.message : "未知错误";
      const errRes: LocalTicketResponse = {
        parseStatus: "fail",
        message: `SSE解析失败:${errMsg}`
      };
      TicketDataStore.getInstance().setData(errRes);
      return errRes;
    }
  }

  /**
   * 清理content格式:移除```json开头、```结尾及多余换行/空格
   * @param rawContent - 原始content字符串
   * @returns 纯净的JSON字符串(失败返回空)
   */
  private static cleanContent(rawContent: string): string {
    if (!rawContent) return "";
    // 1. 移除开头的换行符(\n)和空格
    let cleaned = rawContent.trimStart();
    // 2. 移除开头的```json标记(不区分大小写,兼容可能的格式差异)
    const startMarker = /^```json/i; // 正则:匹配开头的```json(忽略大小写)
    cleaned = cleaned.replace(startMarker, "").trimStart();
    // 3. 移除结尾的```标记(可能带换行或空格)
    const endMarker = /```\s*$/; // 正则:匹配结尾的```及后续空格/换行
    cleaned = cleaned.replace(endMarker, "").trimEnd();
    return cleaned;
  }

  // 辅助方法:解析单行SSE(移除"data:"前缀,转JSON)
  private static parseSingleLine(line: string): RawSSEEvent | null {
    try {
      const jsonStr = line.slice(5).trim();
      return JSON.parse(jsonStr);
    } catch (e) {
      console.warn(`单行SSE解析失败(跳过): ${line}`);
      return null;
    }
  }
}

5.5.5 TicketDataStore.ets(车票数据本地存储)

// pages/Utils/TicketDataStore.ets
import { LocalTicketResponse } from '../Common/Model/ticketModel';

// 票务数据单例
export class TicketDataStore {
  private static instance: TicketDataStore;
  private ticketData: LocalTicketResponse; // 存储解析后的票务数据

  // 私有构造,初始化默认状态
  private constructor() {
    this.ticketData = {
      parseStatus: "no_data",
      message: "尚未查询票务数据"
    };
  }

  // 获取单例实例
  public static getInstance(): TicketDataStore {
    if (!TicketDataStore.instance) {
      TicketDataStore.instance = new TicketDataStore();
    }
    return TicketDataStore.instance;
  }

  // 存储票务数据
  public setData(data: LocalTicketResponse): void {
    this.ticketData = data;
  }

  // 获取票务数据
  public getData(): LocalTicketResponse {
    return this.ticketData;
  }

  // 清空数据
  public clearData(): void {
    this.ticketData = {
      parseStatus: "no_data",
      message: "数据已清空,等待新查询"
    };
  }
}

5.5.6 getData.ets(通用数据获取工具)

// pages/Utils/getData.ets
import { AITicketResponse, LocalTicketResponse } from '../Common/Model/ticketModel';
import { Inputs } from '../Common/Model/tokenApi/TokenModel';
import { get_ai } from '../API/http_ai';
import { SSEParser } from './SSEParser';
import { Token_management } from './getToken';
import { TicketDataStore } from './TicketDataStore';
import { common } from '@kit.AbilityKit';


let aiData: AITicketResponse | null = null
let token: string | undefined = ''
let tokenManager: Token_management | null = null;
let ticketData: LocalTicketResponse = TicketDataStore.getInstance().getData();

/**
 * 获取数据
 * @param fromStation 传入始站
 * @param toStation 传入终点
 * @param date 传入日期
 * @param context 传入上下文 示例:getContext(this) as common.UIAbilityContext
 * @returns : AITicketResponse | null 返回数据
 */
export async function getData(fromStation: string, toStation: string, date: string,
  context: common.UIAbilityContext): Promise<AITicketResponse | null> {
  try {
    tokenManager = new Token_management(context)
    await tokenManager.init()
    token = await tokenManager.loadToken()

    const aa = new Inputs();
    aa.inputs.query = `{
  "fromStation": ${fromStation},
  "toStation": ${toStation},
  "date": ${date}
}`
    await get_ai(token as string, aa).then(async (car) => {
      console.log("ai对话 token" , car)
      ticketData = SSEParser.parseAndStore(car?.toString() as string)
      console.log("ai对话 解析成功1" , ticketData.message)
      aiData = ticketData.data as AITicketResponse
      return aiData
    })
  } catch (error) {
    ticketData = {
      parseStatus: "fail",
      message: `查询失败:${(error as Error).message}`
    };
  }
  ;
  return aiData
}

六、测试与部署

6.1 本地测试

  1. 在DevEco Studio中配置鸿蒙模拟器或连接真实设备
  2. 运行应用,测试核心流程:
    • 首页跳转至查询页
    • 输入城市和日期,点击查询
    • 验证是否能正确显示车票列表
    • 点击车次进入详情页,验证数据展示

6.2 华为云AI Agent联调

  1. 确保AI Agent已正确配置并启动
  2. 检查Access Key、Secret Key和Endpoint是否正确
  3. 测试令牌获取功能,验证是否能正常拿到accessToken
  4. 模拟异常场景(如网络中断、参数错误),验证错误处理逻辑

6.3 应用部署

  1. 生成鸿蒙应用签名(参考华为开发者文档)
  2. 在DevEco Studio中打包HAP文件
  3. 通过华为应用市场或本地安装部署到设备

七、注意事项

  1. 华为云Access Key和Secret Key需妥善保管,避免硬编码在前端代码中(建议通过后端服务转发)
  2. 令牌有效期通常为24小时,需确保getToken.ets中的缓存逻辑正确
  3. AI Agent的响应格式需严格约定,避免解析失败
  4. 网络请求需处理超时和异常,提升用户体验
  5. 本地存储数据需定期清理,避免占用过多设备空间

八、总结

本项目通过华为云AI Agent实现了智能车票查询的后端能力,结合鸿蒙应用的前端交互,为用户提供了便捷的票务查询服务。核心实现包括AI Agent配置、令牌管理、网络通信、数据解析与存储等模块,各模块职责清晰、分工明确,可扩展性强。后续可优化方向:增加车票预订功能、集成用户登录、优化AI响应速度等。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。