HarmonyOS NEXT 实战开发:实现日常提醒应用

news2024/12/29 4:43:01

为什么要开发这个日常提醒应用?

  • 最近鸿蒙热度一直不减,而且前端的就业环境越来越差,所以心里面萌生了换一个赛道的想法。
  • HarmonyOS NEXT 是华为打造的国产之光,而且是纯血版不再是套壳,更加激起了我的好奇心。
  • ArkTS是HarmonyOS优选的主力应用开发语言。ArkTS围绕应用开发在TypeScript(简称TS)生态基础上做了进一步扩展,继承了TS的所有特性,是TS的超集。所以对于我们前端开发来说非常友好。
  • HarmonyOS NEXT 文档也比较齐全。而且官方也有相关示例极大的方便了开发。
  • 根据文档以及自己之前开发经验做一个日常提醒demo 加深自己对HarmonyOS NEXT的理解和实际应用。

日常提醒主要包含功能有哪些?

  • 首页和个人中心tab页切换,以及tab 底部自定义实现。

  • 封装公共弹窗日期选择、列表选择等组件。

  • 访问本地用户首选项preferences实现数据本地化持久存储。

  • 实现后台任务reminderAgentManager提醒。

  • 提醒列表展示,以及删除等。

  • 新增编辑日常提醒记录。

1.实现首页自定义tab页切换

主要依据tab组件以及tab 组件的BottomTabBarStyle的构造函数。

11.jpg

首页page/MinePage.ets代码如下


import {HomeTabs} from  "./components/TabHome"
import {UserBaseInfo} from  "./components/UserBaseInfo"


@Entry
@Component
struct MinePage {
  @State currentIndex: number = 0

  // 构造类 自定义底部切换按钮
  @Builder TabBuilder(index: number,icon:Resource,selectedIcon:Resource,name:string) {
    Column() {
      Image(this.currentIndex === index ? selectedIcon : icon)
        .width(24)
        .height(24)
        .margin({ bottom: 4 })
        .objectFit(ImageFit.Contain)
      Text(`${name}`)
        .fontColor(this.currentIndex === index ? '#007DFF' : '#000000')
        .fontSize('14vp')
        .fontWeight(500)
        .lineHeight(14)
    }.width('100%').height('100%')
    .backgroundColor('#ffffff')
  }
  build() {

    Column() {
      Tabs({ barPosition: BarPosition.End }) {
        TabContent() {
          HomeTabs(); //首页
        }.tabBar(this.TabBuilder(0,$r('app.media.ic_home'),$r('app.media.ic_home_selected'),'首页'))

        TabContent() {
          UserBaseInfo()//个人中心
        }.tabBar(this.TabBuilder(1,$r('app.media.ic_mine'),$r('app.media.ic_mine_selected'),'我的'))
      }
      .vertical(false)
      .scrollable(true)
      .barMode(BarMode.Fixed)
      .onChange((index: number) => {
        this.currentIndex = index;
      })
      .width('100%')
    }
    .width('100%')
    .height('100%')

    .backgroundColor('#f7f7f7')
  }
}

2.封装公共弹窗组件

在ets/common/utils 目录下新建 CommonUtils.ets 文件


import CommonConstants from '../constants/CommonConstants';

/**
 * This is a pop-up window tool class, which is used to encapsulate dialog code.
 * Developers can directly invoke the methods in.
 */
export class CommonUtils {
  /**
   * 确认取消弹窗
   */
  alertDialog(content:{message:string},Callback: Function) {
    AlertDialog.show({
      message: content.message,
      alignment: DialogAlignment.Bottom,
      offset: {
        dx: 0,
        dy: CommonConstants.DY_OFFSET
      },
      primaryButton: {
        value: '取消',
        action: () => {
          Callback({
            type:1
          })
        }
      },
      secondaryButton: {
        value: '确认',
        action: () => {
          Callback({
            type:2
          })
        }
      }
    });
  }


  /**
   * 日期选择
   */
  datePickerDialog(dateCallback: Function) {
    DatePickerDialog.show({
      start: new Date(),
      end: new Date(CommonConstants.END_TIME),
      selected: new Date(CommonConstants.SELECT_TIME),
      lunar: false,
      onAccept: (value: DatePickerResult) => {
        let year: number = Number(value.year);
        let month: number = Number(value.month) + CommonConstants.PLUS_ONE;
        let day: number = Number(value.day);
        let birthdate: string = `${year}-${this.padZero(month)}-${this.padZero(day)}`
        dateCallback(birthdate,[year, month, day]);
      }
    });
  }

  /**
   * 时间选择
   */
  timePickerDialog(dateCallback: Function) {
    TimePickerDialog.show({
      selected:new Date(CommonConstants.SELECT_TIME),
      useMilitaryTime: true,
      onAccept: (value: TimePickerResult) => {
        let hour: number = Number(value.hour);
        let minute: number = Number(value.minute);
        let time: string =`${this.padZero(hour)}:${this.padZero(minute)}`
        dateCallback(time,[hour, minute]);
      }
    });
  }
  padZero(value:number):number|string {
    return value < 10 ? `0${value}` : value;
  }

  /**
   * 文本选择
   */
  textPickerDialog(sexArray?: string[], sexCallback?: Function) {
    if (this.isEmpty(sexArray)) {
      return;
    }
    TextPickerDialog.show({
      range: sexArray,
      selected: 0,
      onAccept: (result: TextPickerResult) => {
        sexCallback(result.value);
      },
      onCancel: () => {
      }
    });
  }


  /**
   * Check obj is empty
   *
   * @param {object} obj
   * @return {boolean} true(empty)
   */
  isEmpty(obj: object | string): boolean {
    return obj === undefined || obj === null || obj === '';
  }

}

export default new CommonUtils();

3.封装本地持久化数据preferences操作

在ets/model/database 新建文件 PreferencesHandler.ets

import data_preferences from '@ohos.data.preferences';
import CommonConstants from '../../common/constants/CommonConstants';
import PreferencesListener from './PreferencesListener';

/**
 * Based on lightweight databases preferences handler.
 */
export default class PreferencesHandler {
  static instance: PreferencesHandler = new PreferencesHandler();
  private preferences: data_preferences.Preferences | null = null;
  private defaultValue = '';
  private listeners: PreferencesListener[];

  private constructor() {
    this.listeners = new Array();
  }

  /**
   * Configure PreferencesHandler.
   *
   * @param context Context
   */
  public async configure(context: Context) {
    this.preferences = await data_preferences.getPreferences(context, CommonConstants.PREFERENCE_ID);
    this.preferences.on('change', (data: Record<string, Object>) => {
      for (let preferencesListener of this.listeners) {
        preferencesListener.onDataChanged(data.key as string);
      }
    });
  }

  /**
   * Set data in PreferencesHandler.
   *
   * @param key string
   * @param value any
   */
  public async set(key: string, value: string) {
    if (this.preferences != null) {
      await this.preferences.put(key, value);
      await this.preferences.flush();
    }
  }

  /**
   * 获取数据
   *
   * @param key string
   * @param defValue any
   * @return data about key
   */
  public async get(key: string) {
    let data: string = '';
    if (this.preferences != null) {
      data = await this.preferences.get(key, this.defaultValue) as string;
    }
    return data;
  }

  /**
   * 删除数据
   *
   * @param key string
   * @param defValue any
   * @return data about key
   */
  public async delete(key: string) {
    if (this.preferences != null) {
       await this.preferences.delete(key);
    }
  }

  /**
   * Clear data in PreferencesHandler.
   */
  public clear() {
    if (this.preferences != null) {
      this.preferences.clear();
    }
  }

  /**
   * Add preferences listener in PreferencesHandler.
   *
   * @param listener PreferencesListener
   */
  public addPreferencesListener(listener: PreferencesListener) {
    this.listeners.push(listener);
  }
}

4.封装代理提醒reminderAgentManager

在ets/model 目录下新建 ReminderService.ets

/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License,Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import reminderAgent from '@ohos.reminderAgentManager';
import notification from '@ohos.notificationManager';
import ReminderItem from '../viewmodel/ReminderItem';

/**
 * Base on ohos reminder agent service
 */
export default class ReminderService {
  /**
   * 打开弹窗
   */
  public openNotificationPermission() {
    notification.requestEnableNotification().then(() => {
    }).catch((err: Error) => {
    });
  }

  /**
   * 发布相应的提醒代理
   *
   * @param alarmItem ReminderItem
   * @param callback callback
   */
  public addReminder(alarmItem: ReminderItem, callback?: (reminderId: number) => void) {
    let reminder = this.initReminder(alarmItem);
    reminderAgent.publishReminder(reminder, (err, reminderId: number) => {
      if (callback != null) {
        callback(reminderId);
      }
    });
  }

  /**
   * 根据需要删除提醒任务。
   *
   * @param reminderId number
   */
  public deleteReminder(reminderId: number) {
    reminderAgent.cancelReminder(reminderId);
  }

  private initReminder(item: ReminderItem):  reminderAgent.ReminderRequestCalendar {
    return {
      reminderType: reminderAgent.ReminderType.REMINDER_TYPE_CALENDAR,
      title: item.title,
      content: item.content,
      dateTime: item.dateTime,
      repeatDays: item.repeatDays,
      ringDuration: item.ringDuration,
      snoozeTimes: item.snoozeTimes,
      timeInterval: item.timeInterval,
      actionButton: [
        {
          title: '关闭',
          type: reminderAgent.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE,
        },
        {
          title: '稍后提醒',
          type: reminderAgent.ActionButtonType.ACTION_BUTTON_TYPE_SNOOZE
        },
      ],
      wantAgent: {
        pkgName: 'com.example.wuyandeduihua2',// 点击提醒通知后跳转的目标UIAbility信息
        abilityName: 'EntryAbility'
      },
      maxScreenWantAgent: { // 全屏显示提醒到达时自动拉起的目标UIAbility信息
        pkgName: 'com.example.wuyandeduihua2',
        abilityName: 'EntryAbility'
      },
      notificationId: item.notificationId,
      expiredContent: '消息已过期',
      snoozeContent: '确定要延迟提醒嘛',
      slotType: notification.SlotType.SERVICE_INFORMATION
    }
  }
}

5.新增编辑提醒页面

33.jpg

新增编辑AddNeedPage.ets页面 代码如下

import router from '@ohos.router';

import  {FormList,FormItemType,formDataType} from  '../viewmodel/AddNeedModel'
import CommonUtils from '../common/utils/CommonUtils';
import addModel from '../viewmodel/AddNeedModel';

@Entry
@Component
struct AddNeedPage {
  @State formData:formDataType={
     id:0,
     title:"",
     content:"",
     remindDay:[], //日期
     remindDay_text:"",
     remindTime:[],//时间
     remindTime_text:"",
     ringDuration:0,
     ringDuration_text:"", //提醒时长
     snoozeTimes:0,
     snoozeTimes_text:"", //延迟提醒次数
     timeInterval:0,
     timeInterval_text:"", //延迟提醒间隔
  }
  private viewModel: addModel = addModel.instant;

  aboutToAppear() {
    let params = router.getParams() as Record<string, Object|undefined>;
    if (params !== undefined) {
      let alarmItem: formDataType = params.alarmItem as formDataType;
      if (alarmItem !== undefined) {
        this.formData =  {...alarmItem}
      }
    }
  }

  build() {
    Column() {
      Column(){
        ForEach(FormList,(item:FormItemType)=>{
          Row(){
            Row(){
              Text(item.title)
            }.width('35%')
            Row(){
              TextInput({ text: item.type.includes('Picker')?this.formData[`${item.key}_text`]: this.formData[item.key],placeholder: item.placeholder })
                .borderRadius(0)
                .enabled(item.isPicker?false:true) //禁用
                .backgroundColor('#ffffff')
                .onChange((value: string) => {
                  if(!item.type.includes('Picker')){
                    this.formData[item.key] = value;
                  }
                })
              Image($r('app.media.ic_arrow'))
                .visibility(item.isPicker?Visibility.Visible:Visibility.Hidden)
                .width($r('app.float.arrow_image_width'))
                .height($r('app.float.arrow_image_height'))
                .margin({ right: $r('app.float.arrow_right_distance') })

            }.width('65%').padding({right:15})
            .onClick(()=>{
              if(item.isPicker){
                switch (item.type) {
                  case 'datePicker':
                    CommonUtils.datePickerDialog((value: string,timeArray:string[]) => {
                      this.formData[`${item.key}_text`] = value;
                      this.formData[item.key] = timeArray;
                    });
                    break;
                  case 'timePicker':
                    CommonUtils.timePickerDialog((value: string,timeArray:string[]) => {
                      this.formData[`${item.key}_text`] = value;
                      this.formData[item.key] = timeArray;
                    });
                    break;
                  case 'TextPicker':
                    CommonUtils.textPickerDialog(item.dicData, (value: string) => {
                      this.formData[`${item.key}_text`] = value;
                      this.formData[`${item.key}`] =item.dicMap[value];
                    });
                    break;

                  default:
                    break;
                }
              }
            })
          }.width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .backgroundColor('#ffffff')
          .padding(10)
          .borderWidth({
            bottom:1
          })
          .borderColor('#f7f7f7')
        })

        Button('提交',{ type: ButtonType.Normal, stateEffect: true })
          .fontSize(18)
          .width('90%')
          .height(40)
          .borderRadius(15)
          .margin({ top:45 })
          .onClick(()=>{
            this.viewModel.setAlarmRemind(this.formData);
            router.back();
          })
      }
    }.width('100%')
    .height('100%')
    .backgroundColor('#f7f7f7')
  }
}

AddNeedModel.ets页面代码如下


import CommonConstants from '../common/constants/CommonConstants';
import ReminderService from '../model/ReminderService';
import DataTypeUtils from '../common/utils/DataTypeUtils';
import { GlobalContext } from '../common/utils/GlobalContext';
import PreferencesHandler from '../model/database/PreferencesHandler';

/**
 * Detail page view model description
 */
export default class DetailViewModel {
  static instant: DetailViewModel = new DetailViewModel();
  private reminderService: ReminderService;
  private alarms: Array<formDataType>;

  private constructor() {
    this.reminderService = new ReminderService();
    this.alarms = new Array<formDataType>();
  }

  /**
   * 设置提醒
   *
   * @param alarmItem AlarmItem
   */
  public async setAlarmRemind(alarmItem: formDataType) {

    let index = await this.findAlarmWithId(alarmItem.id);
    if (index !== CommonConstants.DEFAULT_NUMBER_NEGATIVE) {
      this.reminderService.deleteReminder(alarmItem.id);
    } else {
      index = this.alarms.length;
      alarmItem.notificationId = index;
      this.alarms.push(alarmItem);
    }
    alarmItem.dateTime={
      year: alarmItem.remindDay[0],
      month: alarmItem.remindDay[1],
      day: alarmItem.remindDay[2],
      hour: alarmItem.remindTime[0],
      minute: alarmItem.remindTime[1],
      second: 0
    }
    // @ts-ignore
    this.reminderService.addReminder(alarmItem, (newId: number) => {
      alarmItem.id = newId;
      this.alarms[index] = alarmItem;
      let preference = GlobalContext.getContext().getObject('preference') as PreferencesHandler;
      preference.set(CommonConstants.ALARM_KEY, JSON.stringify(this.alarms));

    })
  }

  /**
   * 删除提醒
   *
   * @param id number
   */
  public async removeAlarmRemind(id: number) {
    this.reminderService.deleteReminder(id);
    let index = await this.findAlarmWithId(id);
    if (index !== CommonConstants.DEFAULT_NUMBER_NEGATIVE) {
      this.alarms.splice(index, CommonConstants.DEFAULT_SINGLE);
    }
    let preference = GlobalContext.getContext().getObject('preference') as PreferencesHandler;
    preference.set(CommonConstants.ALARM_KEY, JSON.stringify(this.alarms));
  }

  private async findAlarmWithId(id: number) {
    let preference = GlobalContext.getContext().getObject('preference') as PreferencesHandler;
    let data = await preference.get(CommonConstants.ALARM_KEY);
    if (!DataTypeUtils.isNull(data)) {
      this.alarms = JSON.parse(data);
      for (let i = 0;i < this.alarms.length; i++) {
        if (this.alarms[i].id === id) {
          return i;
        }
      }
    }
    return CommonConstants.DEFAULT_NUMBER_NEGATIVE;
  }
}


export  interface  FormItemType{
  title:string;
  placeholder:string;
  type:string;
  key:string;
  isPicker:boolean;
  dicData?:string[]
  dicMap?:object
}


export  interface  formDataType{
  id:number;
  notificationId?:number;
  title:string;
  content:string;
  remindDay:number[];
  remindDay_text:string;
  remindTime:number[];
  remindTime_text:string;
  ringDuration:number;
  ringDuration_text:string;
  snoozeTimes:number;
  snoozeTimes_text:string;
  timeInterval:number;
  timeInterval_text:string;
  dateTime?:Object

}


export const FormList: Array<FormItemType> = [
  {
    title:"事项名称",
    placeholder:"请输入",
    key:"title",
    isPicker:false,
    type:"text"
  },
  {
    title:"事项描述",
    placeholder:"请输入",
    key:"content",
    isPicker:false,
    type:"text"
  },
  {
    title:"提醒日期",
    placeholder:"请选择",
    key:"remindDay",
    isPicker:true,
    type:"datePicker"
  },
  {
    title:"提醒时间",
    placeholder:"请选择",
    key:"remindTime",
    isPicker:true,
    type:"timePicker"
  },
  {
    title:"提醒时长",
    placeholder:"请选择",
    key:"ringDuration",
    isPicker:true,
    type:"TextPicker",
    dicData:['30秒','1分钟','5分钟'],
    dicMap:{
      '30秒':30,
      '1分钟':60,
      '5分钟':60*5,
    }
  },
  {
    title:"延迟提醒次数",
    placeholder:"请选择",
    key:"snoozeTimes",
    isPicker:true,
    type:"TextPicker",
    dicData:['1次','2次','3次','4次','5次','6次'],
    dicMap:{
      '1次':1,
      '2次':2,
      '3次':3,
      '4次':4,
      '5次':5,
      '6次':6,
    }
  },
  {
    title:"延迟提醒间隔",
    placeholder:"请选择",
    key:"timeInterval",
    isPicker:true,
    type:"TextPicker",
    dicData:['5分钟','10分钟','15分钟','30分钟'],
    dicMap:{
      '5分钟':5*60,
      '10分钟':10*60,
      '15分钟':15*60,
      '30分钟':15*60,
    }
  }
]

注意事项

  • 本项目用到了代理通知需要在module.json5 文件中 requestPermissions 中声明权限
{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone",
      "tablet"
    ],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:ability_desc",
        "icon": "$media:icon",
        "label": "$string:ability_label",
        "startWindowIcon": "$media:icon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ]
      }
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.PUBLISH_AGENT_REMINDER",
        "reason": "$string:reason",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "always"
        }
      }
    ]
  }
}
  • 本项目还用到了 应用上下文Context在入口文件EntryAbility.ets中注册
import type AbilityConstant from '@ohos.app.ability.AbilityConstant';
import display from '@ohos.display';
import hilog from '@ohos.hilog';
import UIAbility from '@ohos.app.ability.UIAbility';
import type Want from '@ohos.app.ability.Want';
import type window from '@ohos.window';
import PreferencesHandler from '../model/database/PreferencesHandler';
import { GlobalContext } from '../common/utils/GlobalContext';
/**
 * Lift cycle management of Ability.
 */
export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
    GlobalContext.getContext().setObject('preference', PreferencesHandler.instance);
  }

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

  async onWindowStageCreate(windowStage: window.WindowStage) {
    // Main window is created, set main page for this ability
    let globalDisplay: display.Display = display.getDefaultDisplaySync();
    GlobalContext.getContext().setObject('globalDisplay', globalDisplay);
    let preference = GlobalContext.getContext().getObject('preference') as PreferencesHandler;
    await preference.configure(this.context.getApplicationContext());

    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

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

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

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

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

以上,总结了应用程序的主要代码内容。相关代码我把他放在了github上有需要的小伙伴自己下载

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频学习教程+学习PDF文档

HarmonyOS Next 最新全套视频教程

  纯血版鸿蒙全套学习文档(面试、文档、全套视频等)              

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2083169.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【网络安全】分析cookie实现PII IDOR

未经许可,不得转载。 文章目录 正文正文 目标:公共电子商务类型的网站,每月有大约6万到10万访问者,注册用户大约有5万。 存在一个查询个人资料的端点/GetProfiledetails,以下是完整的请求和响应: 我发现,cookie非常类似于base64编码后的结果,于是我将其进行base64解码…

WEB开发---使用HTML CSS开发网页实时显示当前日期和时间

自己刚开始学习html css知识&#xff0c;临时做个网页&#xff0c;实时显示当前日期和时间功能。 代码如下&#xff1a; test.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport&q…

C#利用ffmpeg借助NVIDIA GPU实现实时RTSP硬解码+硬编码录制MP4

目录 说明 效果 项目 代码 下载 说明 利用周杰的开源项目 Sdcb.FFmpeg 项目地址&#xff1a;https://github.com/sdcb/Sdcb.FFmpeg/ 代码实现参考&#xff1a;https://github.com/sdcb/ffmpeg-muxing-video-demo 效果 C#利用ffmpeg借助NVIDIA GPU实现实时RTSP硬解码硬…

004-OpenFeign服务接口调用

文章目录 1 简介2 OpenFeign 通用步骤2.1 建module2.2 改POM2.3 写YML2.3 主启动类2.4 cloud-api-commons模块修改2.4.1 按照架构说明进行编码准备2.4.2 引入openfeign依赖2.4.3 新建服务接口PayFeignApi 2.5 编写controller2.6 测试2.7 总结 3 OpenFeign高级特性3.1 注意3.2 O…

QGC 修改为双路视频介绍

文章目录 一、效果图二、简要流程关于QGC地面站其它文章请点击这里: QGC地面站 一、效果图 右下角切换视频通道; 左下角切换地图与当前的视频 二、简要流程 ● C++ 后端 C++ 中利用 QGC 原有的红外热成像视频流,修改几处即可 // src\VideoManager\VideoManager.cc Vi…

快排找基准值之挖坑法

思路&#xff1a;把数组里第一个数据拿出来记为标准值&#xff0c;然后两边交替&#xff0c;从右往左找比基准值小的数据放到前面缺数据的坑里。放完后该位置也缺数据成为了新的坑&#xff0c;再把坑的下标移到新的位置。 从左到右找比基准值大的数据&#xff0c;后面同理。 …

装饰器(Decorators)的实现

1、Python 中的函数可以像普通变量一样当做参数传递给另外一个函数&#xff1b; 2、装饰器&#xff1a;不修改函数源码但是要实现给函数添加额外功能。python使用语法糖即来实现装饰器。 3、装饰器的作用&#xff1a; &#xff08;1&#xff09;抽离出大量函数中与函数功能本…

Spring框架 基础介绍

目录 Spring框架 IOC: AOP: 一站式&#xff1a; spring搭建 Maven 导入 spring 核心基础 jar 编写 spring 配置文件 编写一个 User 实体类 测试 spring IOC(控制反转) 依赖注入&#xff1a; 1、通过属性注入 2、通过构造方法注入 spring中bean管理 1、基于xml配置方…

AI模型:追求全能还是专精?

AI模型&#xff1a;追求全能还是专精&#xff1f; 近日&#xff0c;OpenAI预计在秋季推出代号为“草莓”的新AI。从专注于数学问题到处理主观营销策略&#xff0c;"草莓"模型展现出惊人的多样性。而这种全能型 AI 是否代表了未来趋势&#xff1f;相比专攻于某一领域…

I get HttpClient.Timeout Error in C# OpenAI library

题意&#xff1a;“我在 C# OpenAI 库中遇到 HttpClient.Timeout 错误。” 问题背景&#xff1a; I am using the OpenAI library in my c# project, but I get the following error if it does not receive a response for more than 100 seconds. I cannot add a custom htt…

宠物空气净化器应该怎么选择?希喂、IAM、有哈哪款性价比高

在当今社会&#xff0c;养宠已然渐渐成为现在年轻人生活中的一种标配。可爱的宠物们以它们的忠诚、活泼与温暖&#xff0c;给予像我们这类年轻人无尽的陪伴。这种陪伴在时光的消逝中渐渐升华&#xff0c;成为年轻人心灵的慰藉和生活中不可或缺的一部分。然而&#xff0c;在享受…

【软件测试】软件测试生命周期与Bug

目录 &#x1f4d5; 前言 &#x1f334;软件测试的生命周期 ​编辑&#x1f332;BUG &#x1f6a9; 概念 &#x1f6a9;描述bug的要素 &#x1f6a9;bug的级别 &#x1f6a9;bug的生命周期 &#x1f3c0;先检查自身&#xff0c;是否bug描述不清楚 &#x1f3c0;站在用…

JavaScript学习文档(9):事件流、事件委托、其他事件、元素尺寸与位置

目录 一、事件流 1、事件流的两个阶段 2、事件捕获 3、事件冒泡 4、阻止冒泡 5、解绑事件 &#xff08;1&#xff09;解绑事件 &#xff08;2&#xff09;鼠标经过事件区别 二、事件委托 1、优点 2、原理 3、实现 4、tab栏切换案例改造 三、其他事件 1、页面加载…

不可不知的HDMI之前世今生

1、HDMI的产生 2002年4月&#xff0c;来自电子电器行业的7家公司——日立、松下、飞利浦、SiliconImage、索尼、汤姆逊、东芝&#xff0c;共同组建了HDMI接口组织——HDMIFounders&#xff08;HDMI论坛&#xff09;&#xff0c;开始着手制定一种符合高清时代标准的全新数字化视…

nginx转发接口地址【非常实用】

使用场景 由于客户的需求是要访问一个外网接口 比如http://58.20.57.190:6652 实例 http://58.20.57.190:6652//uploadBasePatient?Barcode1000000073&customerCode1 比如外网才能访问&#xff0c;科室电脑是访问不了外网的 我们就需要中间在一个既有外网又有内网的前置…

数据结构(邓俊辉)学习笔记】串 09——BM_BC算法:以终为始

文章目录 1. 不对称性2. 善待教训3.前轻后重4.以终为始 1. 不对称性 上一节所介绍的 KMP 算法计算时间&#xff0c;在最坏情况下也可以保证不超过线性。这的确是一个好消息。然而&#xff0c;倘若我们因此就停下继续优化的脚步&#xff0c;那就大错特错了。 实际上&#xff0c…

如何在Java爬虫中设置代理IP:详解与技巧

在进行网络爬虫时&#xff0c;使用代理IP可以有效地避免被目标网站封禁&#xff0c;提升数据抓取的成功率。本文将详细介绍如何在Java爬虫中设置代理IP&#xff0c;并提供一些实用的技巧和示例代码。 为什么需要代理IP&#xff1f; 在进行爬虫操作时&#xff0c;频繁的请求可能…

深度学习基础—彩色图片的卷积运算

深度学习基础—卷积运算http://t.csdnimg.cn/2mRei 上篇文章卷积运算实际是灰度图像的运算&#xff08;2维空间&#xff09;&#xff0c;但是实际中我们彩色图片使用的更多&#xff0c;和灰度图像不同的是&#xff1a;彩色图片是由三原色&#xff08;红、绿、蓝&#xff09;组成…

C# 对桌面快捷方式的操作设置开机启动项

首先在项目中引入Windows Script Host Object Model&#xff0c;引入方式如下图。 对于桌面快捷方式的修改无非就是将现有的快捷方式修改和添加新的快捷方式。 1、遍历桌面快捷方式&#xff0c;代码如下。 string desktopPath Environment.GetFolderPath(Environment.Special…

机器学习:DBSCAN算法(内有精彩动图)

目录 前言 一、DBSCAN算法 1.动图展示&#xff08;图片转载自网络&#xff09; 2.步骤详解 3.参数配置 二、代码实现 1.完整代码 2.代码详解 1.导入数据 2.通过循环确定参数最佳值 总结 前言 DBSCAN&#xff08;Density-Based Spatial Clustering of Applications w…