实战|环信 Vue2 uniapp Demo重构焕新!经典再升级!

news2024/11/16 3:39:58

项目背景

  • 当前环信 uni-app vue2 Demo 地址
  • 升级版本 Github 地址(临时)

原版本功能实现方式较混乱,代码逻辑晦涩难懂,不利于开发者参考或复用。此实战项目在确保原项目功能保留的情况下进行完全重写并新增大量功能,以确保未来项目的可维护性和扩展性。

重构目标

  • 本次重构中原始 Demo 代码逻辑完全重写,原有目录结构以及消息相关组件进行重新调整,在 SDK 的调用方式以及实现逻辑上也进行升级调整。
  • 此次重构中会话列表、消息列表等数据不再进行本地存储,而是远端进行拉取,更换设备也可以进行数据获取,并在原有功能上增加更多丰富的群组以及消息操作。
  • UI 风格按照最新 UIKit 进行同步更新,带来更清爽合理的 Demo 风格。
  • 用最新的 SDK 版本,恰当的实现,清晰的代码,展示有价值的功能使用示例。
  • 易于将内部的逻辑进行复用,后续的功能易于维护增加。
  • 将 UI 风格进行统一。
  • uni-app vue2 Demo 仍有一大部分的用户使用,可根据实践步骤进行相应升级。

核心重构动作

切换 SDK 引入方式

本地 JS 文件引入通常用于引入项目内的模块、工具函数等,而通过 node_modules 引入通常用于引入第三方模块,而 IM SDK 正是三方 js 包,并且会持续更新因此,这种引入形式更规范且方便一些。

原引入方式

本地引入:
import websdk from "../newSDK/Easemob-chat-4.5.0.js";

现引入方式

通过node_modules进行导入;
import EaseSDK from 'easemob-websdk/uniApp/Easemob-chat';

统一管理 IM 相关代码

原 uni-app demo 中将 IM SDK 中的实例化统一放在utils/WebIM.js中,将配置放在utils/WebConfig.js中,SDK 各接口的调用分布在各个组件或页面中,因此在后续接口更新或者维护中过于分散,如需复用则需要在各个页面中进行查找,因此将 IM 相关的调用实例化,以及 config 配置新建一个EaseIM文件进行统一管理,涉及 IM 相关大部分代码书写至此文件。

项目目录结构
EaseIM
├── config # IM 相关配置文件
├── constant # IM 相关常量文件
├── emApis # IM 所有调用 api 集合
├── emListener # IM 所有监听回调集合
├── utils # IM 相关所需工具方法
└── index.js # IM 引入 SDK 以及实例化导出文件
//index.js 示例代码
import EaseSDK from 'easemob-websdk/uniApp/Easemob-chat';
import { EM_APP_KEY, EM_API_URL, EM_WEB_SOCKET_URL } from './config';
let EMClient = (uni.EMClient = {});
EMClient = new EaseSDK.connection({
  appKey: EM_APP_KEY,
  apiUrl: EM_API_URL,
  url: EM_WEB_SOCKET_URL,
});
uni.EMClient = EMClient;
export { EaseSDK, EMClient };

本地数据管理方式变更

在部分用户基于原 uni-app demo 进行二次开发过程中,对于 IM 相关的本地数据(消息+会话+好友列表等)的处理一直是较为头疼的点,这种头疼主要体现在进行数据结构调整,以及逻辑复用,源码阅读时。

这种情况的产生是由于原 demo 中将消息的存储在了 localstorage 当中,并且对应的消息存储还进行了一系列的逻辑处理以及封装,这种封装还与原始的消息结构完全不同,从而导致改动异常困难,而原 demo 中的会话列表的实现也是基于 localstorage 中存储的数据形成,但环信已经针对会话列表,聊天消息,好友列表,用户属性都进行了远端存储,因此调用远端数据进行处理无疑是更简单的方法,所以调整后提供良好的示例尤为重要。

由于原始代码篇幅较长不过多的贴源代码进行比较,原 demo 本地数据管理核心分别在globaldatalocalstorage,处于实际场景考虑,以及缓存数据存在多页面复用,因此将本地缓存的数据管理改用为vuex进行全局状态管理,选用vuex作为全局状态管理首先便于将不同的状态数据切分为不同的 module,第二数据变更时的调用脉络清晰有利于后续维护以及增加可读性。

下图为改用 vuex 后的 store 的目录结构
image.png

下列示例代码以conversation模块为例,conversation 的 store 里面主要存储以及管理当前用户的会话列表信息。

import { emConversation, emSilent } from '@/EaseIM/emApis';
import { EMClient } from '@/EaseIM';
import { CHAT_TYPE } from '@/EaseIM/constant';
import Vue from 'vue';
import MessageStore from './message';
const {
  fetchPinConversationFromServer,
  pinConversationItem,
  fetchConversationFromServer,
  removeConversationFromServer,
  sendChannelAck,
} = emConversation();
const {
  getSilentModeForConversation,
  getSilentModeForConversationList,
  setSilentModeForConversation,
  clearRemindTypeForConversation,
} = emSilent();
const ConversationStore = {
  state: {
    chattingId: '', //进入聊天页面聊天中的目标聊天用户信息
    chattingChatType: CHAT_TYPE.SINGLE_CHAT, //当前聊天页面中的聊天类型类型
    chattingTypingStatus: false, //当前聊天页面中是否正在输入
    pinConversationList: [],
    conversationList: [],
    silentConversationMap: {},
  },
  mutations: {
    RESET_CONVERSATION_STORE: (state) => {},
    SET_CHATING_USER_INFO: (state, payload) => {},
    SET_CHATING_USER_INFO_TYPING_STATUS: (state, payload) => {},
    SET_PIN_CONVERSATION_LIST: (state, pinConversationList) => {},
    SET_SILENT_CONVERSATION_MAP: (state, payload) => {},
    UPDATE_PIN_CONVERSATION_ITEM: (state, conversationItem) => {},
    SET_CONVERSATION_LIST: (state, conversationList) => {},
    DELETE_CONVERSATION_ITEM: (state, conversationId) => {},
    UPDATE_CONVERSATION_ITEM: (state, payload) => {},
    SET_CONVERSATION_ITEM_READ_SATUS: (state, payload) => {},
  },
  actions: {
    //主动更新lastMessage
    updateConversationLastMsg: async ({ commit, dispatch }, payload) => {},
    fetchPinConversationList: async ({ commit }) => {},
    fetchConversationList: async ({ commit, dispatch }) => {},
    //处理会话置顶
    pinConversationItem: async ({ commit }, params) => {},
    deleteConversation: async ({ commit }, params) => {},
    sendConversatonReadedAck: async ({ commit }, params) => {},
    //获取会话免打扰状态
    fetchSilentConversationList: async ({ commit }, params) => {},
    //获取单个会话免打扰状态
    fetchSilentConversation: ({ commit }, params) => {},
    //设置会话免打扰
    setConversationSilentMode: async ({ commit }, params) => {},
    //调整会话已读状态
    setConversationReadStatus: async ({ commit }, params) => {},
  },
  getters: {
    //排序会话列表
    sortedConversationList(state) {},
    //排序指定会话列表
    sortedPinConversationList(state) {},
    //会话免打扰信息
    silentConversationMap(state) {},
    //会话未读总数
    calcAllUnReadNumFromConversation(state) {},
    //聊天中用户ID
    chattingId(state) {},
    chattingChatType(state) {},
    chattingTypingStatus(state) {},
  },
};
export default ConversationStore;

可以看到在此 store 文件中,有针对会话列表的常见增删改查,以及指定,会话列表的状态等进行了更为集中的管理,在各组件中都可以较为编辑的更新以及使用conversation相关的数据。

下图为原会话列表组件以及获取的代码,谁看谁麻。

image.png

image.png

得益于集中维护,且各组件需要编辑复用数据的特性,因此将原 demo 逻辑中的,涉及全局状态管理的数据以及不同组件会经常使用的数据逻辑全部调整为vuex进行管理,且vuex中的数据变更是可以做到响应式的引起视图数据的更新,因此充斥在原始 demo 中大量的发布-订阅模式的event bus代码可以进行删除,比如典型的这些代码。

//disp.fire 发布事件
//disp.on 订阅事件
//disp.off 卸载订阅
WebIM.conn
  .open(opt)
  .then(() => {
    //token获取成功,即可开始请求用户属性。
    disp.fire('em.mian.profile.update');
    disp.fire('em.mian.friendProfile.update');
  })
  .catch((err) => {
    console.log('>>>>>token获取失败', err);
  });
//监听加好友申请
disp.on('em.subscribe', this.onChatPageSubscribe);
//监听解散群
disp.on('em.invite.deleteGroup', this.onChatPageDeleteGroup);
//监听未读消息数
disp.on('em.unreadspot', this.onChatPageUnreadspot);
//监听未读加群“通知”
disp.on('em.invite.joingroup', this.onChatPageJoingroup);
//监听好友删除
disp.on('em.contacts.remove', this.onChatPageRemoveContacts);
//监听好友关系解除
disp.on('em.unsubscribed', this.onChatPageUnsubscribed);
//页面卸载同步取消onload中的订阅,防止重复订阅事件。
disp.off('em.subscribe', this.onChatPageSubscribe);
disp.off('em.invite.deleteGroup', this.onChatPageDeleteGroup);
disp.off('em.unreadspot', this.onChatPageUnreadspot);
disp.off('em.invite.joingroup', this.onChatPageJoingroup);
disp.off('em.contacts.remove', this.onChatPageRemoveContacts);
disp.off('em.unsubscribed', this.onChatPageUnsubscribed);

chat 聊天组件重写

chat 聊天组件是指的原项目内的components下的chat组件,原 uni-app demo 中 chat 组件的核心作用为实现聊天页面,通过在pages中的某个页面级组件引入components/chat后渲染展示其整个聊天页面,下面是原引入代码。

<template>
<chat id="chat" :username="username" ref="chat" chatType="singleChat" @onClickInviteMsg="onClickMsg"></chat>
</template>

<script>
import chat from "../../components/chat/chat.vue";
export default {
  data() {
    return {
      username: {
        your: ""
      }
    };
  },
  components: {
    chat
  },

尽管看起来只是简单的引入一个chat组件,传入少数参数,但实际在二次开发中需要大量修改chat中的逻辑。
由于本身聊天页就属于页面级组件因此在重构后将其“拎到”pages中,并进行了逻辑重写和大量的功能增加。

image.png

新增功能

会话列表

  • 置顶会话
  • 取消置顶
  • 会话标记未读
  • 会话标记已读
  • 会话免打扰
  • 会话取消免打扰

新邀请

  • 好友申请操作
  • 群组申请操作

群聊

  • 新建群组
  • 群组详情页
  • 群成员查看操作
  • 解散群组
  • 转让群组
  • 退出群组
  • 群内昵称设置
  • 消息免打扰
  • 群组名称修改
  • 群组描述修改
  • 群内成员新增或移出
  • 群 ID 复制

黑名单

  • 黑名单查看

我的

  • 头像展示
  • 昵称展示
  • 个性签名展示
  • 环信 ID 复制
  • 在线状态设置
  • 昵称&个性签名修改
  • 输入状态设置
  • 自动通过好友申请设置
  • 自动接收群组邀请设置
  • 消息免打扰设置
  • 隐私入口

聊天页

  • 复制消息
  • 回复消息
  • 编辑消息
  • 删除消息
  • 撤回消息

好友详情页

  • 消息免打扰
  • 拉黑
  • 好友备注

核心代码复用指南

在下面我们将以常见问题的形式指引大家如何去看到自己更加关注的代码逻辑,从而帮助我们找到集成中遇到的需要参考示例的代码片段。

如何在已有项目中实现单聊或群聊聊天功能?

假设我们只需要在已有项目中只需要实现核心的一个聊天页面功能,保障最基本的聊天功能即可,我们需要从这个 demo 中 copy 哪些代码就可以实现诉求功能呢?

  1. 在已有项目中执行,来安装所依赖的环信 SDK
npm install easemob-websdk
  1. 确保你的项目里面也安装了uview-ui库,安装方式可以查看uView官网。
  2. 确保你的项目里面安装z-paging插件。

    z-paging一个 uni-app 分页组件。
    全平台兼容,支持自定义下拉刷新、上拉加载更多,支持虚拟列表,支持自动管理空数据图、点击返回顶部,支持聊天分页、本地分页,支持展示最后更新时间,支持国际化等等。而在此示例中主要用到了该插件的聊天记录模式,来实现下拉加载更多聊天记录。

  3. copy 该示例 Demo 项目中的EaseIM文件至你的项目中,建议与你的项目目录中pages平级,在EaseIM下的configindex.js文件中,请将常量EM_APP_KEY的 appkey 修改为自己的 appkey、
  4. copy 该示例项目中的store至你的项目中,如果你的项目里面恰好也用到了vuex请自行进行合并。
  5. copy 该示例 Demo 项目中pages/emChatContainer并也放入你的项目中的pages目录下,不要忘记,在pages.json中进行对应页面的配置注册。
 {
      "path": "pages/emChatContainer/index",
      "style": {
        "navigationStyle": "custom",
        "navigationBarTextStyle": "white",
        "app-plus": {
          "bounce": "none"
        }
      }
    },
    {
      "path": "pages/emChatContainer/emSelectUserCard/index",
      "style": {
        "navigationStyle": "custom",
        "navigationBarTextStyle": "white"
      }
    },
  1. 完成上述核心步骤之后,我们需要在代码层面完成对 IM 的监听回调挂载,以及长连接的建联,下面是相关代码演示:

挂载监听回调

//App.vue
<script>
import { EMClient } from "@/EaseIM";
import { emConnectListener, emMountGlobalListener } from "@/EaseIM/emListener";
import { emConnect } from "@/EaseIM/emApis";
import { CONNECT_CALLBACK_TYPE, HANDLER_EVENT_NAME } from "@/EaseIM/constant";
import { emHandleReconnect } from "@/EaseIM/utils";
export default {
  onLaunch() {
    //传给监听callback回调
    const connectedCallback = (type) => {
      console.log(">>>>>IM连接回调", type);
      if (type === CONNECT_CALLBACK_TYPE.CONNECT_CALLBACK) {
        this.onConnectedSuccess();
      }
      if (type === CONNECT_CALLBACK_TYPE.DISCONNECT_CALLBACK) {
        this.onDisconnect();
      }
      if (type === CONNECT_CALLBACK_TYPE.RECONNECTING_CALLBACK) {
        this.onReconnecting();
      }
    };
    /* 链接所需监听回调 */
    emConnectListener(connectedCallback);
    /* 全局类型监听集合、消息、联系人、群组等... */
    emMountGlobalListener();
    this.handleAutoLoginEaseIM();
  },
  computed: {
    loginStoreStatus() {
      return this.$store.state.LoginStore.loginStatus;
    },
    loginStoreUserBaseInfos() {
      return this.$store.state.LoginStore.loginUserBaseInfos;
    },
  },
  methods: {
    onConnectedSuccess() {
      const { loginUserId } = this.loginStoreUserBaseInfos || {};
      const finalLoginUserId = loginUserId || EMClient.user;
      if (!this.loginStoreStatus) {
        this.fetchLoginUserNeedData();
        uni.hideLoading();
        console.log(">>>>>>开始跳转至会话列表页面");
        uni.redirectTo({
          url: "../home/home?myName=" + finalLoginUserId,
        });
      }
      this.$store.commit("SET_LOGIN_USER_BASE_INFOS", {
        loginUserId: finalLoginUserId,
      });
      this.$store.commit("SET_LOGIN_STATUS", true);
    },
    onDisconnect() {
      const { closeEaseIM } = emConnect();
      const { actionEMReconnect } = emHandleReconnect();
      //断开回调触发后,如果业务登录状态为true则说明异常断开需要重新登录
      if (!this.loginStatus) {
        uni.showToast({
          title: "退出登录",
          icon: "none",
          duration: 2000,
        });
        uni.redirectTo({
          url: "../login/login",
        });
        closeEaseIM();
      } else {
        console.log(">>>>>需执行重登逻辑");
        //执行通过token进行重新登录
        actionEMReconnect();
      }
    },
    onReconnecting() {
      uni.showToast({
        title: "IM 重连中...",
        icon: "none",
      });
    },
    //获取登录所需基础参数
    async fetchLoginUserNeedData() {
      await this.$store.dispatch("fetchFriendList");
      //获取好友用户属性
      await this.$store.dispatch("fetchFriendUserInfoCollection");
      //获取当前登录用户好友信息
      await this.$store.dispatch("fetchLoginUserProfile");
      await this.$store.dispatch("fetchBlockUserList");
      // 在线状态订阅
      await this.$store.dispatch("subscribePresenceStatus");
      this.fetchJoinedGroupList();
      //初始化缓存本地的新邀请列表
      this.$store.commit("INIT_RECEIVE_INVITE_LIST");
    },
    //获取加入的群组列表
    async fetchJoinedGroupList() {
      //获取群组列表
      await this.$store.dispatch("fetchJoinedGroupList", {
        isInit: true,
      });
    },
    //自动登录
    handleAutoLoginEaseIM() {
      const { actionEMReconnect } = emHandleReconnect();
      const loginInfos = uni.getStorageSync(`EM_LOGIN_INFOS`);
      if (!loginInfos) return;
      actionEMReconnect();
    },
  },
};
</script>

接着你需要代码层面实现与环信服务建立连接相关的逻辑相关逻辑,类似如下代码。

import { emConnect } from '@/EaseIM/emApis';
<script>
import { emConnect } from '@/EaseIM/emApis';
const { loginWithPassword, loginWithAccessToken } = emConnect();
export default {
  data() {
    return {
      /* 环信ID环信密码登录 */
      loginEaseIMId: '',
      loginEaseIMPassword: ''
    };
  },
  methods: {
    async loginWithUserId() {
      try {
        const res = await loginWithPassword(
          this.loginEaseIMId,
          this.loginEaseIMPassword
        );
        this.$store.commit('SET_LOGIN_USER_BASE_INFOS', {
          loginUserId: this.loginEaseIMId,
        });
        this.setEMUserLoginInfosToStorage(
          this.loginEaseIMId.toLowerCase(),
          res.accessToken
        );
      } catch (error) {
        console.log('>>>>>>', error);
        uni.showToast({
          title: '登录失败',
          icon: 'none',
        });
      } finally {
        this.loginEaseIMId = '';
        this.loginEaseIMPassword = '';
      }
    },
    setEMUserLoginInfosToStorage(userId, token) {
      const params = {
        key: `EM_LOGIN_INFOS`,
        data: { userId: userId, token: token },
      };
      uni.setStorage({ ...params });
    },
  },
};
</script>

在有效建立连接后会自动触发监听回调中的onConnected,同时也会触发上述的onConnectedSuccess方法,这个表明长连接已经建立,可以进行聊天,那么接下来你可以点击按钮跳转至emChatContainer组件也可以,在onConnected中进行路由跳转,成功跳转之后则会如下图页面。
IMG_93DDCEDC2038-1.jpeg

如何在已有项目中集成会话列表功能?

如果我们需要在自己的项目中通过引入该项目的会话组件进行二次开发,需要复用哪些代码完成一个会话列表界面,下面将简述一下需要依赖的代码逻辑。

  1. 确保如何在已有项目中实现单聊或群聊聊天功能?中的,1~7 已完成,然后在原项目中将pages/conversation组件按照原始目录 copy 至你的项目,并在pages.json完整相关路由注册。
  2. 修改登录(环信 IM 层面的登录)后的路由跳转至 conversation 页面。默认该组件会在加载时拉取远端环信 conversation 相关数据。接着没有报错的话你将会得到下图的页面。

IMG_3335.PNG

会话列表以及聊天页面是最常见的一些组件复用需求,至于如果你还有其他组件复用疑问或者需求则可以在评论区提出。

重构过程中遇到的问题

使用zpaging组件

该滚动插件在使用聊天记录模式模式时,可以追加消息,但为了实现编辑消息,不得不有一个replace的动作,因此不得不在其源码上新增了一个function,下面是修改的方法位置。

不推荐这么玩,因此如果不需要编辑消息可以忽略下面的修改代码。

 //监听编辑消息更新item
    onModifyMessage(data) {
      /**
       * @function updateChatRecordData
       * @changeSrc "data-handle.js uni_modules/z-paging/components/z-paging/js/modules"
       * @description 此方法为自己往插件库中新增的一个方法,主要为更新本地分页数据中已存在的消息体。
       */
      //    updateChatRecordData(data) {
      //   if (!this.useChatRecordMode) return;
      //   const _index = this.totalData.findIndex((o) => o.id === data.id);
      //   _index >= 0 && (this.totalData[_index] = data);
      // }
      this.$refs.paging.updateChatRecordData(data);
    },

会话列表使用 uviewui List 组件无法滚动

页面级滚动与 List 滚动重合冲突,因此 home 禁止滚动,调整了 List 中的高度,使其减去 navbar 以及顶部安全区,以及 tabbar 以及底部安全区。

 setConversationListHeigth() {
      uni.getSystemInfo({
        success: (res) => {
          //顶部安全区高度
          const statusBarHeight = res.statusBarHeight;
          //底部安全区高度
          const safeAreaBottom = res.safeAreaInsets.bottom;
          console.log('safeAreaBottom', safeAreaBottom);
          this.conversationListHeight =
            res.windowHeight -
            (tabBarHeight +
              navBarHeight +
              searchInputHeight +
              statusBarHeight +
              safeAreaBottom);
        },
      });
    },

uviewui u-index-list 制作联系人列表 tabbar 遮挡联系人最后一项

迫不得已,最终改了 u-index-list 组件代码 在 customNavHeigth 后面多减了 100

  init() {
      // 设置列表的高度为整个屏幕的高度
      //减去this.customNavHeight,并将this.scrollViewHeight设置为maxHeight
      //解决当u-index-list组件放在tabbar页面时,scroll-view内容较少时,还能滚动
      this.scrollViewHeight =
        this.sys.windowHeight -
        this.customNavHeight -
        this.sys.safeAreaInsets.bottom;
      if (this.sys.safeAreaInsets.bottom) {
        this.scrollViewHeight = this.scrollViewHeight - 100;
      } else {
        this.scrollViewHeight = this.scrollViewHeight - 50;
      }
    },

优化与改进

打包至微信小程序包大小严重超标

要求主包 1.5m 结果代码 6m
因此想了一系列优化手段

  1. static 图片二次压缩:原占比 230kb 经过压缩缩减至 73kb 效果明显。
  2. 运行至小程序时代码压缩,uniApp 运行至微信小程序勾选运行时是否压缩代码,这个效果很明显,直接至 2000+kb
  3. 微信小程序运行时勾选上传代码时自动压缩脚本文件,此效果也很明显再次缩减部分大小。
  4. 确保部分代码条件编译,从而时其在非微信小程序平台使用时不被打包。

最后展示的占比图:

企业微信截图_42db9ad5-5900-4a64-8505-31bfc250d711.png

image.png

写在最后

  • 在本次重构后,或多或多还有一些技术层面的问题,比如聊天页面在一些端上交互效果仍然有待提高,还需要后续想办法进行寻求优化,而uView2.x也出现了不再进行更新的情况,因此如果在进行参考或者复用逻辑进行二次开发的时候,也需要将此风险进行评估,不过如果在使用中有好的建议以及方案也欢迎进行友好交流。
  • 目前该项目的源码还在临时仓库,目前还需要进行最后的问题调整,以及 READEME.md 编写,编写后会交给测试人员进行提测,最终版本将会更新至官网 uniApp 相关官方仓库,临时仓库地址也会在下方贴出,欢迎大家体验并提出意见。

相关链接

  • 升级版本 Github 临时地址
  • 当前官网 uni-app vue2 demo 地址
  • 官网 uni-app vue3 demo 地址
  • 官网 uni-app 包含音视频示例 demo 地址
  • 注册环信 IM

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

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

相关文章

鸡肋的Git

1.前言 对于大多数开发人员来说&#xff0c;我们大多数在学习或者工作过程中只关注核心部分&#xff0c;比如说学习Java&#xff0c;可能对于大多数人而言一开始都是从Java基础学起&#xff0c;然后408&#xff0c;Spring&#xff0c;中间件等&#xff0c;当你发现很多高深的技…

ARM中汇编语言的学习(加法、乘法、除法、左移、右移、按位与等多种命令操作实例以及ARM的 N、Z、C、V 标志位的解释)

汇编概述 汇编需要学习的大致框架如下&#xff1a; 汇编中的符号 1.指令&#xff1b;能够北嘁肷梢惶?2bit机器码&#xff0c;并且能够被cpui识别和执行 2.伪指令&#xff1a;本身不是指令&#xff0c;编译器可以将其替换成若干条指令 3.伪操作&#xff1a;不会生成指令…

STL之set容器代码详解

1 基础概念 所有元素都会在插入时自动被排序 本质&#xff1a; set/multiset属于关联式容器&#xff0c;底层结构是用二叉树实现。 set和multiset区别&#xff1a; set不允许容器中有重复的元素&#xff1b; multiset允许容器中有重复的元素 。 2 代码示例 Talk is chea…

GO语言接入支付宝

GO语言接入支付宝 今天就go语言接入支付宝写一个教程 使用如下库&#xff0c;各种接口较为齐全 "github.com/smartwalle/alipay/v3"先简单介绍下加密&#xff1a; 试想&#xff0c;当用户向支付宝付款时&#xff0c;若不进行任何加密&#xff0c;那么黑客就可以任…

【牛客】VL76 任意奇数倍时钟分频

描述 编写一个模块&#xff0c;对输入的时钟信号clk_in&#xff0c;实现任意奇数分频&#xff0c;要求分频之后的时钟信号占空比为50%。模块应包含一个参数&#xff0c;用于指定分频的倍数。 模块的接口信号图如下&#xff1a; 要求&#xff1a;使用Verilog HDL语言实现&#…

2024最新版CleanMyMac X 4.15.1 Crack+激活码下载

CleanMyMac X 为您喜爱的事物腾出空间。 CleanMyMac 具有一系列巧妙的新功能&#xff0c;可让您安全、智能地扫描和清理整个系统、删除大型未使用的文件、减小 iPhoto 图库的大小、卸载不需要的应用程序或修复开始工作不正常的应用程序、管理所有应用程序您可以从一个地方进行扩…

html css 导航栏 2

鼠标划过会向上移动改变颜色 html文件 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>导航栏</title><link rel"stylesheet" href"css/dhl1.css" /></head><body><div …

分销商城微信小程序:用户粘性增强,促进复购率提升

在数字化浪潮的推动下&#xff0c;微信小程序作为一种轻便、高效的移动应用形式&#xff0c;正成为越来越多企业开展电商业务的重要平台。而分销商城微信小程序的出现&#xff0c;更是为企业带来了前所未有的机遇。通过分销商城微信小程序&#xff0c;企业不仅能够拓宽销售渠道…

揭秘税务信息接口:解读企业税务登记与纳税情况

导语&#xff1a; 随着社会经济的发展&#xff0c;税收对于国家财政收入的重要性不言而喻。税务登记信息和纳税情况对于企业和个人来说至关重要。在这个背景下&#xff0c;税务信息接口应运而生&#xff0c;为我们提供了便捷的查询途径。本文将以挖数据平台提供的税务信息接口…

【IPC】管道通信【命名管道】

文章目录 1.管道小总结2.命名管道2.1认识命名管道2.2命名管道的应用小场景2.3模拟命名管道1.Lod.hpp2.common.hpp3.server.cxx4.client.cxx 3.管道代码总结 1.管道小总结 linux-manualshouce 在Linux中&#xff0c;manual手册的编号用于区分手册的不同部分。这些编号通常用于ma…

个人商城系统开源(发送手机验证码!)

原文地址&#xff1a;个人商城系统开源&#xff08;发送手机验证码&#xff01;&#xff09; - Pleasure的博客 下面是正文内容&#xff1a; 前言 由于近期实在没有什么话题可写和一些有趣的项目教程可以分享。所以我只能决定将我自己亲手编写的一个迷你迷你商城系统进行开源…

3 模型评估

3 模型评估 在测试AI系统中的模型训练和评估阶段,需要使用准备好的数据集对AI模型进行训练和评估。在训练过程中,应该对模型进行监控和调整,以确保模型的准确性和效果。在评估过程中,需要使用测试数据集对模型进行测试,以验证模型的准确性和效果。模型的评估也分为离线评…

通过一篇文章带你玩转git和GitHub

Git和Github的基本用法 前言一、Git和Github的基本用法背景下载安装安装 git for windows安装 tortoise gitgit安装过程中的一些选项 tortoise git汉化教程下载tortoise git汉化安装包安装tortoise git汉化安装包 三、使用 Github 创建项目注册账号创建项目下载项目到本地 四、…

视频批量混剪剪辑,批量剪辑批量剪视频,探店带货系统,精细化顺序混剪,故事影视解说,视频处理大全,精细化顺序混剪,多场景裂变,多视频混剪

前言 工具的产生源于dy出的火山引擎的云视频混剪制作是按分钟数收费的&#xff0c;这个软件既能实现正常混剪也能避免二次收费。属于FFMPEG合成的。 欢迎大家给一些好的建议和功能&#xff0c;回复可见&#xff0c;附加了一些天卡&#xff0c;周卡&#xff0c;请大家不要一人占…

【C++】函数模板和类模板

目录 1.泛型编程 2.函数模板 2.1函数模板的定义格式 2.2函数模板的实例化 2.3函数模板参数的匹配原则 3.类模板 3.1类模板的定义格式 3.2类模板的实例化 3.3模板的分离编译 1.泛型编程 泛型编程&#xff1a;编写与类型无关的通用代码&#xff0c;是代码复用的一种手段…

新一代 Git 工具,AI 赋能!深度集成、简化操作 | 开源日报 No.194

gitbutlerapp/gitbutler Stars: 7.2k License: NOASSERTION gitbutler 是一个基于 Git 的版本控制客户端。旨在为现代工作流程构建一个全新的 Git 分支管理工具。 虚拟分支&#xff1a;可以同时在多个分支上工作&#xff0c;而无需不断切换分支简化提交管理&#xff1a;通过拖…

如何转行成为产品经理?

转行NPDP也是很合适的一条发展路径&#xff0c;之后从事新产品开发相关工作~ 一、什么是NPDP&#xff1f; NPDP 是产品经理国际资格认证&#xff0c;美国产品开发与管理协会&#xff08;PDMA&#xff09;发起的&#xff0c;是目前国际公认的唯一的新产品开发专业认证&#xff…

stm32普通定时器脉冲计数(发送固定脉冲个数),控制步进电机驱动器

拨码开关设置驱动器&#xff0c;细分 方法思路&#xff1a;用通用定时器TIM2&#xff0c;1ms产生一次中断&#xff1b;在中断里做IO反转&#xff1b; 发送10个脉冲信号

Docker部署SimpleMindMap结合内网穿透实现公网访问本地思维导图

文章目录 1. Docker一键部署思维导图2. 本地访问测试3. Linux安装Cpolar4. 配置公网地址5. 远程访问思维导图6. 固定Cpolar公网地址7. 固定地址访问 SimpleMindMap 是一个可私有部署的web思维导图工具。它提供了丰富的功能和特性&#xff0c;包含插件化架构、多种结构类型&…

【漏洞复现】大华ICC智能物联综合管理平台token弱口令漏洞

Nx01 产品简介 大华智能物联综合管理平台 iConnection Center&#xff08;以下简称&#xff1a;ICC平台&#xff09;&#xff0c;是一套基于智能物联的综合业务管理平台软件&#xff0c;具备强大的后台服务能力&#xff0c;配套了B/S管理员端、C/S客户端、移动APP终端、小程序等…