深入解析 Odoo 在线客服模块 (im_livechat)

在这里插入图片描述

深入解析 Odoo 在线客服模块 (im_livechat)

Odoo Livechat 是一款集成于 Odoo 平台的实时在线客服系统,它赋予用户在网页界面上直接与客服人员进行即时沟通的能力。本文将逐步剖析 Livechat 的实现细节,从入口模板文件的加载机制,到后端初始化逻辑,再到前端客服服务的构建与交互,全方位揭示其内部运作机制。

相关功能介绍见:Odoo讨论+聊天模块

1. 入口模板文件:集成与数据传递

  • 网站与在线客服频道关联配置

每个网站可设置一个 im_livechat.channel 实例作为在线客服频道,通过字段 channel_id = fields.Many2one(‘im_livechat.channel’) 进行关联。此配置决定了特定网站的在线客服参数及客服机器人运行规则。

  • 模板集成与数据获取

website_livechat/views/website_livechat.xml文件中, Livechat 被无缝集成到 Odoo 网站模板中。具体操作如下:

  • 条件加载:通过 <t t-if="not no_livechat and website and website.channel_id"> 判断语句,确保 Livechat 只在未被禁用且已配置频道的页面上加载。
  • 数据传递:调用 website._get_livechat_channel_info() 函数获取频道信息,并将其传递给 im_livechat.loader 模板, 初始化 odoo.__session_info__.livechatData
    <!--Integrate Livechat in Common Frontend for Website Template registering all the assets required to execute the Livechat from a page containing odoo--><template id="loader" inherit_id="website.layout" name="Livechat : include loader on Website"><xpath expr="//head"><!-- Odoo机器人测试页面 chatbot_test_script_page 通过 <t t-set="no_livechat" t-value="True"/> 设置 no_livechat=True。调查问卷相关页面也设置 no_livechat=True。 --><t t-if="not no_livechat and website and website.channel_id"><script><t t-call="im_livechat.loader"><t t-set="info" t-value="website._get_livechat_channel_info()"/></t></script></t></xpath></template>
  • 后端数据初始化

在 Python 后端 im_livechat/models/im_livechat_channel.py,通过 livechatData = get_livechat_info() 函数,初始化并返回包含在线客服状态、服务器URL等关键信息的数据对象。其中,判断在线客服是否可用的依据是频道中设置的聊天机器人脚本数量或可用客服人数:

info['available'] = self.chatbot_script_count or len(self.available_operator_ids) > 0
  • 前端数据注入

模板文件 im_livechat/views/im_livechat_channel_templates.xml 负责将后端获取的 Livechat 数据注入到前端 JavaScript 环境:

    <!-- initialize the LiveSupport object --><template id="loader" name="Livechat : Javascript appending the livechat button"><t t-translation="off">odoo.__session_info__ = Object.assign(odoo.__session_info__ || {}, {livechatData: {isAvailable: <t t-out="'true' if info['available'] else 'false'"/>,serverUrl: "<t t-out="info['server_url']"/>",options: <t t-out="json.dumps(info.get('options', {}))"/>,},});</t></template>

2. 前端客服相关核心服务详解

在前端主要依赖以下三个核心服务:

  1. mail.thread: import(“@mail/core/common/thread_service”).ThreadService
  2. im_livechat.livechat: import(“@im_livechat/embed/common/livechat_service”).LivechatService
  3. im_livechat.chatbot: import(“@im_livechat/embed/common/chatbot/chatbot_service”).ChatBotService
  • im_livechat/livechat_service.js文件在前端注册 im_livechat.livechat 服务:

首先将后端传递的 options 和 isAvailable 状态赋值给 LivechatService 类,并实例化。接着,根据服务的可用性决定是否执行初始化操作。若服务可用,调用 LivechatService.initialize() 方法,发起 RPC 调用 /im_livechat/init,传入当前频道 ID,并在响应后标记服务为已初始化。最后,将 im_livechat.livechat 服务注册到前端服务注册表中。

伪代码如下:

    LivechatService.options = session.livechatData.optionsLivechatService.available = session.livechatData.isAvailable;livechat = new LivechatService()if (livechat.available) {LivechatService.initialize()-> this.rpc("/im_livechat/init", {channel_id})-> livechat.initialized = true}registry.category("services").add("im_livechat.livechat");
  • mail/thread_service.js 在前端注册 mail.thread 服务:
export const threadService = {dependencies: ["mail.store", "orm", "rpc", "notification", "router", "mail.message","mail.persona", "mail.out_of_focus", "ui", "user", "im_livechat.livechat", "im_livechat.chatbot"],/***@param {import("@web/env").OdooEnv} env* @param {Partial<import("services").Services>} services*/start(env, services) {return new ThreadService(env, services);},
};
registry.category("services").add("mail.thread", threadService);

3. 在线聊天按钮组件

LivechatButton 组件负责呈现在线聊天按钮并响应用户点击事件。点击按钮时,执行以下操作:

  • 调用 this.threadService.openChat() 打开聊天窗口。
  • 使用 getOrCreateThread({ persist = false }) 通过 RPC 调用 /im_livechat/get_session 获取或创建临时会话信息。此时,由于 persist = false,仅返回 type='livechat' 的临时会话,不立即创建 discuss.channel 记录。用户发送消息时会创建 discuss.channel 记录。
  • 如果聊天机器人可用则启动 chatbotService 服务。

伪代码如下:

export class LivechatButton extends Component {static template = "im_livechat.LivechatButton"onClick -> this.threadService.openChat()-> thread = getOrCreateThread({ persist = false }) -> rpc("/im_livechat/get_session")-> if (this.chatbotService.active) chatbotService.start()
}

4. 后端创建会话

当 Livechat 频道支持机器人且存在 chatbot_script 时,RPC 调用 /im_livechat/get_session 会触发 _get_livechat_discuss_channel_vals() 方法,用于在后端创建相应的会话记录。

5. 聊天窗口

  • 聊天机器人处理,伪代码如下:
// 无需用户回复时,直接触发下一步
chatBotService.start() -> _triggerNextStep() -> _getNextStep() -> this.rpc("/chatbot/step/trigger")
// 点击推荐回复选项时, 发送对应的回复
onclick -> this.answerChatbot(answer) -> this.threadService.post(answer)
  • 后端处理用户回复答案并返回下一步:
# 调用处理用户回复路由:/chatbot/step/trigger, 处理存储用户回复并返回下一步对话
chatbot_trigger_step() -> next_step = _process_answer(channel, answer)-> if step_type in ['question_email', 'question_phone'] chatbot_message.write({'user_raw_answer': message_body}) # 存储用户回复-> _fetch_next_step() -> 'chatbot.script.step' # 下一步数据
# 处理下一步,如果下一步为切换到真人客服,则自动添加相关人员到频道中
posted_message = next_step._process_step(discuss_channel)-> if self.step_type == 'forward_operator' -> _process_step_forward_operator(discuss_channel)-> discuss_channel.add_members()-> channel._chatbot_post_message() -> message_post() # 发送消息
  • 用户输入检测
    使用 useEffect 监听用户输入变化,调用 self.detect() 和 getSupportedDelimiters() 检测是否包含特定指令字符(如 @、# 或 :)。
    // 用户输入时检测是否包含指令字符useEffect(() => self.detect() -> getSupportedDelimiters(),() => [selection.start, selection.end, textInputContent]);// 输入 @ 或 # 时触发搜索联系人或频道suggestionService.fetchSuggestions(self.search)// 点击推荐列表项设置 search 条件,例如选择 '/help'onSelect: this.suggestion.insert(option) -> this.search = {delimiter: "/", position: 0, term: "help "}// im_livechat 模块扩展 suggestionService 模块,支持冒号 ':' 命令getSupportedDelimiters(thread) {// 仅 livechat 频道支持通过 ':' 搜索内置的快速回复return thread?.model !== "discuss.channel" || thread.type === "livechat"? [...super.getSupportedDelimiters(...arguments), [":"]]: super.getSupportedDelimiters(...arguments);},

6. 前端发送消息

前端发送消息时执行以下操作:

  • 调用 post(thread, body) 方法,传入会话 thread 和消息内容 body。
  • 如果 thread.type 为 “livechat”,调用 livechatService.persistThread() 生成 ‘discuss.channel’ 记录。
  • 如果消息内容以 / 开头,识别为命令。从命令注册表 commandRegistry 中获取命令 command。如果找到命令,执行 executeCommand(command) 并返回结果;否则继续消息发送流程。
  • 否则,通过 RPC 调用 /mail/message/post 将消息发送到服务端。
  • 触发 chatbotService 上的 MESSAGE_POST 事件监听器。

伪代码如下:

post(thread, body) -> if (thread.type === "livechat") livechatService.persistThread()-> if (body.startsWith("/")) -> command = commandRegistry.get()-> if (command) executeCommand(command) return; // 执行命令返回结果-> else this.rpc('/mail/message/post') // 发送消息到服务端-> this.chatbotService.bus.trigger("MESSAGE_POST") // 触发 chatbotService 监听器

7. 后台消息处理

  • 路由 /mail/message/post 处理函数 mail_message_post, 根据 thread_id 查找对应的频道(discuss.channel)实例。调用 message_post() 发布消息到频道, 存储消息并根据需要通过多种方式发送通知(如短信、Web 推送、Inbox、电子邮件等)。其中 _message_post_after_hook(new_message, msg_values)是一个钩子函数,可能包含如 mail_bot 模块中用于用户与机器人私下交互的逻辑。
class Channel(models.Model):_name = 'discuss.channel'_inherit = ['mail.thread']# 1. 路由:/mail/message/post
mail_message_post() -> if "partner_emails" in post_data 创建联系人-> thread = env['discuss.channel'].search([("id", "=", thread_id)])-> thread.message_post() # 发布消息
# 2. 发布消息流程
message_post()-> new_message = self._message_create([msg_values]) # 存储消息-> self._message_post_after_hook(new_message, msg_values) # 钩子,如在 mail_bot 模块中添加mail_bot 模块添加用户与 odoo 初始化机器人私下交互的逻辑 `self.env['mail.bot']._apply_logic(self, msg_vals)`-> self._notify_thread(new_message) # 给相关收件人通过多种方式发送通知,如: _notify_thread_by_sms 或 by_web_push, by_inbox, by_email
  • _notify_thread 消息发送流程

构建通知数据,并通过 bus._sendmany(bus_notifications) 使用 PostgreSQL 的 pg_notify 发送异步通知。分发线程 (ImDispatch) 通过 监听 ‘imbus’ 通道,接收到此通知。如果是聊天频道或群组频道,调用_notify_thread_by_web_push() 发送 Web 推送通知。

# discuss.channel 扩展 mail.thread, 通过发送消息到前端
def _notify_thread(self, message, msg_vals=False, **kwargs):# 调用父类方法rdata = super()._notify_thread(message, msg_vals=msg_vals, **kwargs)message_format = message.message_format()[0]# 更新消息格式以包含临时IDif "temporary_id" in self.env.context:message_format["temporary_id"] = self.env.context["temporary_id"]# 生成通知数据payload = {"id": self.id, "is_pinned": True,"last_interest_dt": fields.Datetime.now()}bus_notifications = [(self, "discuss.channel/last_interest_dt_changed", payload),(self, "discuss.channel/new_message",{"id": self.id, "message": message_format}),]# 使用 PostgreSQL 的内置函数 pg_notify 发送异步通知, SELECT "pg_notify"('imbus', json_dump(list(channels)))# 其他客户端在该数据库连接中使用 LISTEN 命令监听 'imbus' 通道时,它们会接收到这个通知self.env["bus.bus"].sudo()._sendmany(bus_notifications)# 如果是聊天频道或群组频道,给相关人员发送 Web 推送通知if self.is_chat or self.channel_type == "group":self._notify_thread_by_web_push(message, rdata, msg_vals, **kwargs)return rdata
  • ImDispatch 分发线程

作为线程运行,监听 ‘imbus’ 通道上的数据库通知, 当接收到通知时,将其转发给订阅了相应通道的 WebSockets。

class ImDispatch(threading.Thread):'分发线程'def loop(self):_logger.info("Bus.loop listen imbus on db postgres")with odoo.sql_db.db_connect('postgres').cursor() as cr, selectors.DefaultSelector() as sel:cr.execute("listen imbus") # 监听while not stop_event.is_set():if sel.select(TIMEOUT):conn.poll()channels = []while conn.notifies:channels.extend(json.loads(conn.notifies.pop().payload))# 将 postgres 通知转发给订阅了相应通道的 websockets"websockets = set()for channel in channels:websockets.update(self._channels_to_ws.get(hashable(channel), []))for websocket in websockets:websocket.trigger_notification_dispatching()
  • 推送 Web 通知: _notify_thread_by_web_push
def _notify_thread_by_web_push(self, message, recipients_data, msg_vals=False, **kwargs):"""为每个用户的提及和直接消息发送 web 通知。:param message: 需要通知的`mail.message`记录:param recipients_data: 收件人信息列表(基于res.partner记录):param msg_vals: 使用它来访问与`message`相关联的值, 通过避免访问数据库中的消息内容来减少数据库查询次数"""# 提取通知所需的伙伴IDpartner_ids = self._extract_partner_ids_for_notifications(message, msg_vals, recipients_data)if not partner_ids:return# 前端 navigator.serviceWorker.register("/web/service-worker.js")# 以超级用户权限获取伙伴设备, service_worker.js 执行 jsonrpc 注册设备 call_kw/mail.partner.device/register_devicespartner_devices_sudo = self.env['mail.partner.device'].sudo()devices = partner_devices_sudo.search([('partner_id', 'in', partner_ids)])if not devices:return# 准备推送负载payload = self._notify_by_web_push_prepare_payload(message, msg_vals=msg_vals)payload = self._truncate_payload(payload)# 如果设备数量小于最大直接推送数量,则直接推送通知if len(devices) < MAX_DIRECT_PUSH:push_to_end_point()else:# 如果设备数量超过最大直接推送数量,则创建一个通知队列项并触发异步推送self.env['mail.notification.web.push'].sudo().create([{...} for device in devices])self.env.ref('mail.ir_cron_web_push_notification')._trigger()

8. chatbotService 处理用户与机器人对话时选择的答案

通过 MESSAGE_POST 事件监听器。如果当前步骤类型为 free_input_multi,调用 debouncedProcessUserAnswer(message) 处理用户输入。否则,调用 _processUserAnswer(message), 保存答案到后端(通过 /chatbot/answer/save RPC 调用)。调用 _triggerNextStep() 继续对话流程(参见标题5)。

this.bus.addEventListener("MESSAGE_POST", ({ detail: message }) => {if (this.currentStep?.type === "free_input_multi") {this.debouncedProcessUserAnswer(message)} else {this._processUserAnswer(message) -> this.rpc("/chatbot/answer/save") // 保存答案->_triggerNextStep() // 发送用户答案到后端继续下一步,上面标题5}
})

9. 前端接收及处理消息

  • simpleNotificationService 初始化时启动 busService,从指定 URL 加载 websocket_worker 源码。
  • websocket_worker 监听处理 message 事件,当接收到消息时触发 notificationBus 上的 notification 事件。
  • 根据不同的消息类型分别处理,如当接收到新的消息时:添加到相应频道的消息列表中,触发 discuss.channel/new_message 事件。
  • threadService 监听此事件,并调用 notifyMessageToUser(channel, message) 方法显示新消息。
simpleNotificationService -> busService.start()-> startWorker()// workerURL = http://localhost:8069/bus/websocket_worker_bundle?v=1.0.7"-> worker = new workerClass(workerURL, {"websocket_worker"});-> worker.addEventListener("message", handleMessage)-> handleMessage = () => notificationBus.trigger(type, payload)-> this.busService.addEventListener("notification", () => this._handleNotificationNewMessage(notify))-> channel.messages.push(message);-> this.env.bus.trigger("discuss.channel/new_message")-> this.env.bus.addEventListener("discuss.channel/new_message", () => this.threadService.notifyMessageToUser(channel, message))-> this.store.ChatWindow.insert({ thread });

消息数据 messageEv.data 内容示例:

   [{"type": "discuss.channel.member/seen","payload": {"channel_id": 35,"last_message_id": 658,"guest_id": 9},},{"type": "discuss.channel/last_interest_dt_changed","payload": {"id": 35,"is_pinned": true,"last_interest_dt": "2024-04-23 16:24:32"},},{"type": "discuss.channel/new_message","payload": {"message": {"body": "<p>hello!</p>","date": "2024-04-23 16:24:32","email_from": false,"message_type": "comment",}}}]

总结

本文深入剖析了 Odoo 在线客服功能背后的前后端消息通信的主要过程及细节。前端通过智能识别用户输入,区分命令与常规消息,发送消息到后端。MESSAGE_POST 事件触发 chatbotService 实时响应用户对机器人问题的选择。后端接收到消息后,存储、处理、转发并以多种方式通知相关收件人。前端通过 WebSocket 监听后端消息并展示。

相关功能介绍见:Odoo讨论+聊天模块


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

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

相关文章

Sectigo证书申请流程及价格介绍

Sectigo 是一家全球知名的数字证书颁发机构&#xff08;Certificate Authority, CA&#xff09;&#xff0c;自1998年起就开始提供 SSL 证书服务&#xff0c;是全球最早的 CA 机构之一。 一 Sectigo证书申请流程 1 确定证书类型 根据自身的需求确定证书的类型&#xff0c;一…

安全计算环境中,入侵防范的测评指标对应的测评对象包含哪些?

安全计算环境中&#xff0c;入侵防范的测评指标包括恶意代码防范、可信验证、数据完整性、数据保密性、数据备份恢复、剩余信息保护、个人信息保护等。 在安全计算环境中&#xff0c;入侵防范是确保系统不受未授权访问和破坏的关键措施。这涉及到多个方面的测评指标&#xff0…

IBM MQ 问题记录与解决

问题1&#xff1a; 报错&#xff1a; JMSWMQ2013:为队列管理器“QMXXX”提供的安全性认证无效&#xff0c;连接方式为“client”&#xff0c;主机名为“127.0.0.1(1417)”。 解决&#xff1a; (1)登录服务器 进入 IBM mq 所在目录 &#xff08;linux一般在/opt/mqm下 windo…

【001_音频开发-基础篇-专业术语】

001_音频开发-基础篇-专业术语 文章目录 001_音频开发-基础篇-专业术语创作背景术语表常见音源HDMI相关声音系统立体声2.1 声音系统5.1 环绕声系统5.1.2 环绕声系统7.1 环绕声系统7.1.4 环绕声系统9.1.4 环绕声系统 音质等级定义QQ音乐网易云音乐 创作背景 学历代表过去、能力…

如何启用启用WordPress调试模式

最近我们的WordPress网站在访问时&#xff0c;经常出现打不开的现象&#xff0c;我们向主机提供商Hostease咨询后&#xff0c;他们提到这是由于WordPress的某个插件导致的问题&#xff0c;我们在将插件版本升级至最新后&#xff0c;这个问题就消失了。为了方便后续的检查&#…

OceanBase 开发者大会 - 见闻与洞察

文章目录 前言主论坛见闻技术专场见闻产品技术专场技术生态专场 同行论道启发互动展区写在最后 前言 4 月 20 日&#xff0c;我有幸受邀参加了第二届 OceanBase 开发者大会。 50 余位业界知名数据库大咖和数据库爱好者&#xff0c;与来自全国近 600 名开发者相聚。共同探讨一体…

3DE DELMIA Role: EWD - Ergonomic Workplace Designer

Discipline: Ergonomics Role: EWD - Ergonomic Workplace Designer 设计安全高效的工作场所 Ergonomic Workplace Designer采用 Smart Posturing EngineTM技术&#xff08;用于自动和可重复的逼真人体模型定位&#xff09;和 Ergo4alITM技术&#xff08;用于应用程序内人体工…

2024年阿里云服务器最新活动价格表(含可选实例、配置、带宽)

2024年阿里云对云服务器活动中的价格和可选实例、配置、带宽和折扣等优惠政策又做升级调整&#xff0c;调整之后的云服务器最低价格只要99元起了&#xff0c;企业级2核4G5M带宽云服务器也只要199元/1年了&#xff0c;而且阿里云还额外推出了新购和续费同价的优惠政策。下面是小…

五一出游必备神器!华为nova 12 Ultra助你秒变旅行达人

五一假期终于要到了&#xff01;小伙伴们是不是都跃跃欲试&#xff0c;想要出去浪一浪呢&#xff1f;别急&#xff0c;别急&#xff0c;先让我给你安利一款出游神器——华为nova 12 Ultra&#xff0c;它可是集多种技艺于一身&#xff0c;让你在旅途中秒变旅行达人哦&#xff01…

CV每日论文--2024.4.23

1、InstantMesh: Efficient 3D Mesh Generation from a Single Image with Sparse-view Large Reconstruction Models 中文标题&#xff1a;InstantMesh&#xff1a;使用稀疏视图大型重建模型从单个图像高效生成 3D 网格 简介&#xff1a;本文介绍了InstantMesh&#xff0c;这…

Mysql用语句创建表/插入列【示例】

一、 创建表 COMMENT表示字段或列的注释 -- 新建student表 CREATE TABLE student (id BIGINT NOT NULL COMMENT 学生id, enroll_date DATE NOT NULL COMMENT 注册时间, NAME VARCHAR(18) DEFAULT NOT NULL COMMENT 学生姓名, deal_flag TINYINT(1) DEFAULT 0 NOT NULL COMM…

2024第十三届深圳国际营养与健康产业博览会

医疗展、健康展、养生展、医药展、中医展、艾灸展、营养展、保健展、康复展、医美展、养老展、婴童展、医美展、护理展、理疗展、智能中医、医疗器械、氢氧、营养保健、大健康、医药制药、养老康复、食药同源 2024第十三届深圳国际营养与健康产业博览会 THE 13TH SHENZHEN IN…

全新Storm Core API管理系统源码 免授权版

全新Storm Core API管理系统源码 免授权版 本系统为API系统,实现了api集成等基础功能,以后可能会更新key调用api,或者实现付费功能,敬请期待,前端模板均无加密,用户可自行二开,具体请看图 测试环境:PHP7.2+MySQL5.6 访问:http://你的域名/install 进行安装 伪静态…

树 —— 树和森林的遍历

一、树的遍历 &#xff08;1&#xff09;先根遍历 若树非空&#xff0c;则遍历方法为 &#xff08;1&#xff09;访问根结点。 &#xff08;2&#xff09;从左到右&#xff0c;依次先根遍历根结点的每一棵子树。 先根遍历序列为&#xff1a;ABECFHGD。 &#xff08;2&#…

齐护K210系列教程(二)_点亮板载灯

文章目录 1&#xff0c;程序的上传2&#xff0c;点亮板载R-G-B三色灯2-1 齐护编程块方式 2&#xff0c;红色呼吸灯&#xff08;渐亮渐灭&#xff09;4&#xff0c;RGB幻彩呼吸灯联系我们 1&#xff0c;程序的上传 接上节课&#xff0c;我们选择板卡【MicroPython[QDPK210_AIst…

Spring Boot入门(21):使用Spring Boot和Log4j2进行高效日志管理:配置详解

Spring Boot 整合 Log4j2 前言 Log4j2是Apache软件基金会下的一个日志框架&#xff0c;它是Log4j的升级版。与Log4j相比&#xff0c;它在性能和功能上有着极大的提升。Spring Boot本身已经默认集成了Logback作为日志框架&#xff0c;但如果需要使用Log4j2来替代Logback&#…

智慧校园:大数据助力校情分析

随着信息技术的快速发展&#xff0c;数据信息资源以井喷的姿态涌现。数据信息的大量涌现给人们带来丰富的数据信息资源&#xff0c;但面对海量的信息资源时&#xff0c;加大了人们对有效信息资源获取的难度&#xff0c;数据挖掘技术正是这一背景下的产物&#xff0c;基于数据挖…

小扎宣布开放 Meta Horizo​​n OS

日前&#xff0c;Meta以“混合现实的新时代”为题的博文宣布向第三方制造商开放Meta Horizon OS&#xff0c;包括华硕、联想和微软Xbox等等&#xff1a; Meta正在朝着为元宇宙建立一个更开放的计算平台的愿景迈出下一步。Meta正在向第三方硬件制造商开放赋能Meta Quest设备的操…

医院信创FTP要进行替代,有什么值得信赖的方案?

信创产业&#xff0c;即信息技术应用创新产业。其发展核心在于通过行业 应用拉动构建国产化信息技术软硬件底层架构体系和全生命周期生态体系&#xff0c;解决核心技术关键环节“卡脖子”的问题&#xff0c;为中国未来发展奠定坚实的数字基础。 2018 年 以来&#xff0c;受“华…

基于Springboot的网课管理系统

基于SpringbootVue的网课管理系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 课程表 论坛交流 学校公告 后端 学生管理 教师管理 班级管理 课程分类管理…