首页
留言
导航
统计
Search
1
追番推荐!免费看动漫的网站 - 支持在线观看和磁力下载
777 阅读
2
PVE自动启动 虚拟机 | 容器 顺序设置及参数说明
472 阅读
3
一条命令,永久激活!Office 2024!
444 阅读
4
优选 Cloudflare 官方 / 中转 IP
324 阅读
5
[Windows] MicroSoft Office LTSC Professional Plus 2024 官方安装部署工具
323 阅读
默认分类
服务器
宝塔
VPS
Docker
OpenWRT
Nginx
群晖
前端编程
Vue
React
Angular
NodeJS
uni-app
后端编程
Java
Python
SpringBoot
SpringCloud
流程引擎
检索引擎
Linux
CentOS
Ubuntu
Debian
数据库
Redis
MySQL
Oracle
虚拟机
VMware
VirtualBox
PVE
Hyper-V
计算机
网络技术
网站源码
主题模板
登录
Search
标签搜索
Java
小程序
Redis
SpringBoot
docker
Typecho
Cloudflare
虚拟机
WordPress
uni-app
CentOS
docker部署
Vue
Java类库
群晖
Linux命令
防火墙配置
Mysql
脚本
计算机网络
流年微醺
累计撰写
255
篇文章
累计收到
8
条评论
首页
栏目
默认分类
服务器
宝塔
VPS
Docker
OpenWRT
Nginx
群晖
前端编程
Vue
React
Angular
NodeJS
uni-app
后端编程
Java
Python
SpringBoot
SpringCloud
流程引擎
检索引擎
Linux
CentOS
Ubuntu
Debian
数据库
Redis
MySQL
Oracle
虚拟机
VMware
VirtualBox
PVE
Hyper-V
计算机
网络技术
网站源码
主题模板
页面
留言
导航
统计
搜索到
27
篇与
的结果
2025-07-10
无感刷新Token:如何做到让用户“永不掉线”
没有什么比在用户操作得正嗨时,突然提示 “登录已过期,请重新登录” 的提示更让人沮丧的了。这种突兀的中断不仅破坏了用户体验,甚至可能导致未保存的数据丢失。然而,我们都知道,出于安全考虑,用于身份验证的 Token(通常是 Access Token) 必须有较短的有效期。那么,我们如何在保证安全的前提下,创造一种 “永不掉线” 的丝滑体验呢?问题的根源:Access Token 的“天生矛盾”首先,我们要理解为什么需要刷新 Token 。我们通常使用 Access Token 来验证用户的每一次 API 请求。为了安全,Access Token 的生命周期被设计得很短(例如 30 分钟或 1 小时)。如果有效期太长,一旦泄露,攻击者就能在很长一段时间内冒充用户进行操作,风险极高。这就产生了一个矛盾:安全性要求:Access Token 有效期要短。用户体验要求:用户不想频繁地被强制重新登录。为了解决这个矛盾,Refresh Token 应运而生。核心理念:双 Token 认证系统无感刷新机制的核心在于引入了两种类型的 Token:Access Token(访问令牌)用途:用于访问受保护的 API 资源,附加在每个请求的 Header 中。特点:生命周期短(如 1 小时),无状态,服务器无需存储。存储:通常存储在客户端内存中(如 Vuex/Redux),因为需要频繁读取。Refresh Token(刷新令牌)用途:当 Access Token 过期时,专门用于获取一个新的 Access Token。特点:生命周期长(如 7 天或 30 天),与特定用户绑定,服务器需要安全存储其有效性记录。存储:必须安全存储。最佳实践是存储在 HttpOnly Cookie 中,这样可以防止客户端 JavaScript 脚本(如 XSS 攻击)读取它。既然如此,为何不直接使用 Refresh Token 呢?Access Token 通常是无状态的,服务器无需记录它,也导致 JWT 无法主动吊销,而 Refresh Token 是有状态的,服务器需要一个列表(数据库中的“白名单”或“吊销列表”)来记录哪些 Refresh Token 是有效的,当用户更改密码、或从某个设备上“主动登出”时,服务器端可以主动将对应的 Refresh Token 设为无效。无感刷新的详细工作流下面是这个“魔法”发生的具体步骤:首次登录:用户使用用户名和密码登录。服务器验证成功后,返回一个 Access Token 和一个 Refresh Token。正常请求:客户端将 Access Token 存储起来,并在后续的每次 API 请求中,通过 Authorization 请求头将其发送给服务器。Token过期:当 Access Token 过期后,客户端再次用它请求 API。服务器会拒绝该请求,并返回一个特定的状态码,通常是 401 Unauthorized。拦截401错误:客户端的请求层(如 Axios 拦截器)会捕获这个 401 错误。此时,它不会立即通知用户“你已掉线”,而是暂停这个失败的请求。发起刷新请求:拦截器使用 Refresh Token 去调用一个专门的刷新接口(例如 /api/auth/refresh)。处理刷新结果:刷新成功:服务器验证 Refresh Token 有效,生成一个新的 Access Token(有时也会返回一个新的 Refresh Token,这被称为“刷新令牌旋转”策略,可以提高安全性),并将其返回给客户端。刷新失败:如果 Refresh Token 也过期了或无效,服务器会返回错误(如 403 Forbidden)。这意味着用户的登录会话彻底结束。重试与终结:若刷新成功:客户端用新的 Access Token 自动重发刚才失败的那个 API 请求。用户完全感觉不到任何中断,数据操作无缝衔接。若刷新失败:客户端清除所有认证信息,强制用户登出,并重定向到登录页面。实战演练:使用 Axios 拦截器实现无感刷新Axios 的拦截器是实现这一流程的完美工具。下面是一个完整且考虑了并发问题的实现方案。1. 创建 Axios 实例首先,我们创建一个单独的 Axios 实例,方便统一管理。// api/request.js import axios from 'axios'; const service = axios.create({ baseURL: '/api', timeout: 10000, }); // 请求拦截器 service.interceptors.request.use( config => { // 在发送请求之前,从 state management (e.g., Vuex/Pinia/Redux) 获取 token const accessToken = getAccessTokenFromStore(); if (accessToken) { config.headers['Authorization'] = `Bearer ${accessToken}`; } return config; }, error => { return Promise.reject(error); } );2. 核心:响应拦截器这是实现无感刷新的关键。// api/request.js (续) // 用于刷新 token 的 API import { refreshTokenApi } from './auth'; let isRefreshing = false; // 控制刷新状态的标志 let requests = []; // 存储因 token 过期而挂起的请求 service.interceptors.response.use( response => response, // 对成功响应直接返回 async error => { const { config, response: { status } } = error; // 1. 如果不是 401 错误,直接返回错误 if (status !== 401) { return Promise.reject(error); } // 2. 避免重复刷新:如果正在刷新 token,将后续请求暂存 if (isRefreshing) { return new Promise(resolve => { requests.push(() => resolve(service(config))); }); } isRefreshing = true; try { // 3. 调用刷新 token 的 API const { newAccessToken } = await refreshTokenApi(); // 假设 refresh token 通过 HttpOnly cookie 自动发送 // 4. 更新本地存储的 access token setAccessTokenInStore(newAccessToken); // 5. 重试刚才失败的请求 config.headers['Authorization'] = `Bearer ${newAccessToken}`; // 6. 重新执行所有被挂起的请求 requests.forEach(cb => cb()); requests = []; // 清空队列 return service(config); // 返回重试请求的结果 } catch (refreshError) { // 7. 如果刷新 token 也失败了,则执行登出操作 console.error('Unable to refresh token.', refreshError); logoutUser(); // 清除 token,重定向到登录页 return Promise.reject(refreshError); } finally { isRefreshing = false; } } ); export default service;代码解析:并发处理:isRefreshing 标志和 requests 数组是关键。当第一个 401 错误触发刷新时,isRefreshing 变为 true。后续在刷新完成前到达的 401 请求,都会被推进 requests 队列中挂起,而不是重复发起刷新请求。当刷新成功后,再遍历队列,依次执行这些被挂起的请求。原子操作:通过这种“加锁”机制,确保了刷新 Token 的操作是原子的,避免了资源浪费和潜在的竞态条件。优雅降级:当 Refresh Token 也失效时,系统会执行 logoutUser(),进行清理工作并引导用户重新登录,这是一个优雅的失败处理方案。无感刷新 Token 机制是现代 Web 应用提升用户体验的“标配”。它将身份验证的复杂性隐藏在后台,为用户提供了一个流畅、不间断的操作环境。实现这一机制,不仅仅是写几行代码,更是对认证流程、安全性和用户体验三者之间平衡的深刻理解。
2025年07月10日
2 阅读
0 评论
0 点赞
2025-07-07
禁止用户复制页面内容的多种方法
某些特定的业务场景下,我们可能会有禁止用户复制页面内容的需求。比如:付费内容保护:在线小说、付费课程等,希望防止内容被轻易拷贝和传播。试卷或答题系统:防止考生将题目复制出去寻求场外帮助。敏感信息展示:如银行卡号、个人身份信息等,减少被意外泄露的风险。虽然从技术上完全阻止一个“有心人”拷贝内容几乎是不可能的(毕竟还有截图、拍照等物理手段),但作为前端开发者,我们有多种方法可以极大地增加复制的难度,从而在一定程度上达到我们的目的。方法一:CSS 魔法 —— user-select: none这是最简单、最快捷,也是最常用的一种方法。CSS 的 user-select 属性可以控制用户是否能够选择文本。/* 应用到需要禁止选择的元素上 */ .no-copy { user-select: none; /* 兼容旧版浏览器 */ -webkit-user-select: none; /* Safari, Chrome */ -moz-user-select: none; /* Firefox */ -ms-user-select: none; /* IE10+ */ } /* 或者直接粗暴地应用到整个页面 */ body { user-select: none; }效果:当鼠标划过应用了该样式的区域时,将无法选中任何文本,光标也不会变成文本选择的 I 型。自然也就无法进行复制操作。优点:实现极其简单,一行 CSS 搞定。纯样式控制,没有 JavaScript 性能开销。缺点:防君子不防小人:任何一个懂点前端的开发者,都可以通过浏览器开发者工具,轻松地找到这个 CSS 属性并禁用它,然后就能愉快地复制了。体验不佳:用户无法选择文本,有时可能会影响正常的交互预期。方法二:JavaScript 事件监听 —— 控制 copy 和 contextmenu 事件如果想做得更“绝”一点,我们可以通过 JavaScript 来监听和阻止用户的复制行为。1. 禁用复制事件 (copy)当用户执行复制操作时(无论是通过 Ctrl+C 快捷键还是右键菜单),copy 事件会被触发。我们可以拦截它。document.addEventListener('copy',function(e){ // 阻止默认的复制行为 e.preventDefault(); // (可选)可以给用户一个提示 alert('内容受版权保护,禁止复制!'); });2. 禁用右键菜单 (contextmenu)为了防止用户通过右键菜单进行复制,我们可以把右键菜单也一并禁用掉。document.addEventListener('contextmenu', function(e) { // 阻止默认的右键菜单弹出行为 e.preventDefault(); });3. 禁用键盘快捷键 (keydown)更进一步,我们还可以监控键盘事件,直接禁用 Ctrl+C。document.addEventListener('keydown', function(e) { // 检查是否同时按下了Ctrl(或Cmd)和 C if((e.ctrlKey || e.metaKey) && e.key === 'c') { e.preventDefault(); // 可以在这里加个提示 } });组合使用:将以上 JS 代码和 CSS 的 user-select 结合起来,就能构建一个更强的 “防御体系” 。优点:比纯 CSS 更难破解,因为禁用 JS 比修改 CSS 要麻烦一些。可以提供自定义的交互反馈,比如弹窗提醒。缺点:依然可破:在浏览器开发者工具中禁用 JavaScript,所有这些限制都会失效。体验极差:粗暴地禁用右键和快捷键会严重影响用户的正常浏览习惯(比如右键刷新、打开新标签页等),可能会激怒用户。方法三:“温柔”的魔法 —— 在复制内容中“加料”有时候,我们不一定非要完全禁止复制,而是希望在用户复制的内容中追加一些我们自己的信息,比如版权声明或来源链接。这是一种更优雅、更尊重用户的做法。我们可以监听 copy 事件,然后通过 Clipboard API 修改剪贴板中的内容。document.addEventListener('copy', function(e) { // 获取用户选中的文本 const selection = window.getSelection().toString(); // 你想追加的版权信息 const copyright = `\n\n----------------\n来源博客站:blog.lysdad.cn`; // 阻止默认的复制行为 e.preventDefault(); // 将 "选中内容 + 版权信息" 写入剪贴板 e.clipboardData.setData('text/plain', selection + copyright); });效果:用户可以正常复制,但当他们粘贴时,会发现内容末尾自动附上了你的版权声明。优点:用户体验好:不影响用户的核心操作,只是附加了信息。君子协定:这是一种良性的引导,而非强制的封锁。思考:我们真的需要禁止复制吗?在实施这些技术方案之前,我们应该先问自己一个问题:禁止复制真的是最好的选择吗?技术上的徒劳:对于执意要获取内容的人来说,任何前端层面的限制都是纸老虎。他们总能通过禁用 JS、查看源代码、使用爬虫,甚至直接截图 OCR 来获取内容。用户体验的灾难:强行改变用户的操作习惯,尤其是禁用右键,是一种非常糟糕的设计。它破坏了 Web 的开放性和可访问性,可能会让你的网站流失大量用户。替代方案:水印:对于图片或重要文档,添加可见或不可见的数字水印,是更好的版权追踪方式。内容分段加载/懒加载:增加爬虫获取完整内容的难度。结尾作为前端开发者,我们掌握了“禁止复制”的屠龙之术,但更应该思考何时以及是否应该拔出这把剑。在大多数情况下,选择更开放、更友好的“修改剪贴板”方案,或者干脆相信用户的善意,可能是更明智的选择。
2025年07月07日
4 阅读
0 评论
0 点赞
2024-09-23
12 个强大而实用的 JavaScript 动画库
想要提升网页设计水平?那么 JavaScript 动画库一定是你的首选。这是一个可以将静态页面转换为动态的、吸引人眼球的秘密武器。无论你是经验丰富还是刚刚入门的前端开发人员,强大又包罗万象的 JavaScript 动画库都能帮助你将创意愿景变为现实。那么,你准备好了吗?让我们有请 2024 年名声大噪的 12 大 JavaScript 动画库隆重登场!1. GSAP被誉为动画库瑞士军刀的GSAP,用途十分广泛,深受全世界前端开发者的喜爱。使用示例:gsap.to(".box", {duration: 2, x: 300, rotation: 360, ease: "bounce"});只需要简简单单的一行代码,就能让box元素向右移动 300 像素,旋转 360 度,同时具有bounce效果。实在太便捷,太好用了。2. Anime.js:简单而强大我们常说,有的时候,少即是多,Anime.js 正是如此,它虽然轻巧但在能力上却毫不逊色。使用示例:anime({ targets: '.circle', translateX: 250, scale: 2, duration: 3000 });上面的代码表示动画会在 3 秒内平滑移动并放大一个circle元素。大家可以试一试,还蛮有意思的。3. Velocity.js:速度与优雅的结合Velocity.js 大大改善性能,同时又不牺牲功能。赞!使用示例:$(".element").velocity({ translateY: "200px", rotateZ: "45deg" }, 1000);元素将向下转换 200 像素,并在一秒钟内旋转 45 度。4. Three.js:引入 3D 世界Three.js 开辟了一个全新的维度!它是我们在浏览器中创建 3D 图形的门户。使用示例:const geometry = new THREE.BoxGeometry(); const material = new THREE.MeshBasicMaterial({color: 0x00ff00}); const cube = new THREE.Mesh(geometry, material); scene.add(cube);上面的代码创建了一个绿色的 3D 立方体,你可以随心所欲地进行操作和动画制作。5. Lottie:使动画变简单Lottie 将复杂的动画变成了小菜一碟。是你口袋里的专业动画师!使用示例:lottie.loadAnimation({ container: document.getElementById('lottie-container'), renderer: 'svg', loop: true, autoplay: true, path: 'data.json' });上面的代码从 JSON 文件加载和播放 Lottie 动画。是不是看着就感觉易如反掌!6. Popmotion:最佳的灵活性Popmotion 就像变色龙——它可以轻松适应任何 JavaScript 环境。使用示例:animate({ from: 0, to: 100, onUpdate: latest => console.log(latest) });这是一个从0计数到100、记录每个值的简单动画。7. Mo.js:使动态图形变简单Mo.js 使创建动态图形就像用蜡笔绘图一样简单,但是最后的成果却不一般,可谓精妙绝伦!使用示例:const burst = new mojs.Burst({ radius: { 0: 100 }, count: 5, children: { shape: 'circle', fill: { 'cyan' : 'yellow' }, duration: 2000 } });上面的代码创建了一个burst动画,包含五个扩大和变色的圆。8. Typed.js:让文本栩栩如生Typed.js 给冷冰冰的文本增添了人情味。Nice。使用示例:new Typed('#element', { strings: ['Hello, World!', 'Welcome to my website!'], typeSpeed: 50 });代码创建了一个在两个问候语之间交替的键入动画。9. AniJSAniJS 就像魔法棒一样——无需编写任何代码即可创建动画!使用示例:<div data-anijs="if: click, do: fadeIn, to: .target"></div>上面的HTML属性会在单击时创建淡入动画。10. Framer Motion:React 的动画超级英雄Framer Motion 是 React 工具包的完美补充,它对于 React 就像薯条和番茄酱,合在一起,美味无比。使用示例:<motion.div animate={{ x: 100 }} transition={{ duration: 2 }} />这个 React 组件会在 2 秒内向右移动 100 个像素。11. ScrollMagic:基于滚动的动画大师ScrollMagic 使滚动变得有趣起来。随着用户滚动浏览网站,一部迷你电影随之呈现!相信我,真的会被惊艳到。使用示例:new ScrollMagic.Scene({ triggerElement: "#trigger", duration: 300 }) .setTween("#animate", {scale: 2.5}) .addTo(controller);以上代码创建的动画会在用户滚动时缩放元素。12. Motion One:小而强浓缩的,才是精华的,Motion One 正是如此。轻量级的它,小小的身体充满了大大的能量!使用示例:animate("#box", { x: 100 }, { duration: 1 });这条简单的线会在一秒内将一个盒子向右移动 100 像素。结束语:我们的动画之旅将在这里启程!有了这 12 个令人惊叹的 JavaScript 动画库,我们的 web 项目就能脱胎换骨,从平平无奇进化成为光彩夺目:无论是创建简单的悬停效果还是复杂的 3D 世界,这些库都能满足我们的需求。谨记,哪个库最适合取决于特定的项目需求。多多尝试才能匹配到最完美的助手。心动不如行动。让我们一起试着用这些库制作动画吧!
2024年09月23日
15 阅读
0 评论
0 点赞
2024-07-31
还学鸿蒙原生?vue3 + uniapp 可以直接开发鸿蒙啦!
前言7月20号,uniapp 官网 “悄咪咪” 的上线了 uniapp 开发鸿蒙应用 的文档,算是正式开启了 Vue3 + uniapp 开发鸿蒙应用 的时代。开发鸿蒙的前置准备想要使用 uniapp 开发鸿蒙,我们需要具备三个条件:DevEco-Studio 5.0.3.400 以上(下载地址:https://developer.huawei.com/consumer/cn/deveco-studio/)鸿蒙系统版本 API 12 以上 (DevEco-Studio有内置鸿蒙模拟器)HBuilderX-alpha-4.22 以上PS: 这里不得不吐槽一下,一个 DevEco-Studio 竟然有 10 个 G......[安装好之后,我们就可以通过 开发工具 运行 示例代码]运行时,需要用到 鸿蒙真机或者模拟器。但是这里需要 注意: Windows系统需要经过特殊配置才可以启动,mac 系统最好保证系统版本在 mac os 12 以上windows 系统配置方式(非 windows 用户可跳过): 打开控制面板 - 程序与功能 - 开启以下功能Hyper-VWindows 虚拟机监控程序平台虚拟机平台注意: 需要win10专业版或win11专业版才能开启以上功能,家庭版需先升级成专业版或企业版启动鸿蒙模拟器整个过程分为三步(中间会涉及到鸿蒙开发者申请):下载 uni-app 鸿蒙离线SDK template-1.3.4.tgz (下载地址:https://web-ext-storage.dcloud.net.cn/uni-app/harmony/zip/template-1.3.4.tgz)解压刚下载的压缩包,将解压后的模板工程在 DevEco-Studio 中打开等待 Sync 结束,再 启动鸿蒙模拟器 或 连接鸿蒙真机(如无权限,则需要申请(一般 3 个工作日),申请地址:https://developer.huawei.com/consumer/cn/activity/201714466699051861/signup)配置 HBuilderX 吊起 DevEco-Studio打开HBuilderX,点击上方菜单 - 工具 - 设置,在出现的弹窗右侧窗体新增如下配置[注意:值填你自己的 DevEco-Studio 启动路径]"harmony.devTools.path" : "/Applications/DevEco-Studio.app"创建 uni-app 工程BuilderX 新建一个空白的 uniapp 项目,选vue3在 manifest.json 文件中配置鸿蒙离线SDK路径(SDK 路径可在 DevEco-Studio -> Preferences(设置) z中获取)编辑 manifest.json 文件,新增如下配置:然后点击 运行到鸿蒙即可最后这样我们就有了一个初始的鸿蒙项目,并且可以在鸿蒙模拟器上运行。关于更多 uniapp 开发鸿蒙的 API,大家可以直接参考 uniapp 官方文档:https://zh.uniapp.dcloud.io/tutorial/harmony/dev.html#nativeapi
2024年07月31日
20 阅读
0 评论
0 点赞
2024-05-28
实战:element-ui 树型控件自定义图标(给节点添加图标)
效果图:<el-input v-model="searchVal" placeholder="请输入关键词" clearable style="margin-bottom: 12px" /> <div class="treeBox"> <el-tree :data="sourceData" node-key="id" default-expand-all :expand-on-click-node="false" :props="defaultProps" @node-click="handleNodeClick" > // 重点:给节点添加图标 <span slot-scope="{ node, data }" class="slot-t-node"> <template> <i :class="{ 'el-icon-folder': !node.expanded, // 节点收缩时的图标 'el-icon-folder-opened': node.expanded, // 节点展开时的图标 'el-icon-user-solid': data.type === 2 // data.type是后端配合提供的识别字段,最后一级 }" style="color: #409eff;" // 图标颜色 /> <span>{{ node.label }}</span> </template> </span> </el-tree> </div>data() { return { searchVal: '', // 输入的关键词 defaultProps: { children: 'childrenList', // 将tree中的每项的 childrenList 映射为 children label: 'name' // 将tree中的每项的 name 映射为 label }, sourceData: [ // 树源数据 { id: null, name: '深圳市XXX有限公司', parentId: '0', childrenList: [ { id: null, name: '研发中心', parentId: '1', childrenList: [ { id: null, name: '研发1组', parentId: '2', childrenList: [ { id: null, name: 'IOS测试机', parentId: null, childrenList: null, type: 2 } ], type: 1 }, { id: null, name: '安卓测试机', parentId: null, childrenList: null, type: 2 } ], type: 1 }, { id: null, name: '产品中心', parentId: '1', childrenList: [], type: 1 }, { id: null, name: '员工1', parentId: null, childrenList: null, type: 2 }, { id: null, name: '员工12', parentId: null, childrenList: null, type: 2 }, { id: null, name: '员工13', parentId: null, childrenList: null, type: 2 } ], type: 1 } ] } }, methods: { // 节点点击时 handleNodeClick(data) { // 。。。 } }
2024年05月28日
16 阅读
0 评论
0 点赞
2024-05-28
使用OpenAI API搭建AI助理的JavaScript实现
前言今天我们来聊聊如何使用OpenAI和JavaScript的json-server技术来搭建一个独属于你的AI助理用来解决你提出各种请求,这项功能在传统的后端项目中是不可能实现的但是现在我们有了大模型这一利器我们可以使用大模型来快速的分析并处理我们给出的请求。在AI时代我们可以使用JavaScript来快速构建一个独属于我们的AI助理,使用json-server技术将json文件快速变成后端数据,后端再调用OpenAI的接口来对你所发出的请求进行处理。话不多说,直接开干。准备工作第一次看本文章的朋友们,建议先仔细阅读前文:使用OpenAI API进行情感分析的JavaScript实现一文教你使用Node.js脚本调用OpenAi API接口实现对话功能一: 创建项目文件打开vscode,新建三个文件夹。这三个文件夹的作用分别是: Ai服务,后端目录,前端目录 ,文件夹结构如图所示:二:初始化工程初始化后端项目工程 npm init -y创建好了项目所需的文件夹后,右键进入backend文件夹的终端,进入到终端。端输入指令 npm init -y 将项目初始为后端工程。初始化成功后会出现 packager.json 文件,如图所示:引入json-server继续在当前终端中输入指令 npm i json-server 导入成功后,我们会在 package.json 文件夹中看到已经成功引入了 json-server 的依赖。如图所示:导入后端数据打开 package.json 文件,将 "scripts" 中的内容修改为: "dev": "json-server users.json"新建一个名为 users.json 的文件,该文件中的内容则为本项目所需要用到的数据最终结构如下图所示:进入到 users.json 文件中输入需要用到的数据这里给出本项目中用到的数据:{ "users": [ { "id": 1, "name": "科比·布莱恩特", "hometown": "费城" }, { "id": 2, "name": "坤坤", "hometown": "温州" }, { "id": 3, "name": "阿伦·艾弗森", "hometown": "费城" }, { "id": 4, "name": "丁真珍珠", "hometown": "理塘" } ] }到此,该项目的后端数据就构建好了。此时,继续在命令行输入指令 npm run dev ,将后端项目运行起来。此时可以在控制台看到后端地址。打开浏览器,访问该地址可以看到 users.json 文件中的数据。访问该地址的不同 id 时,可以看到该id对应的人物信息OpenAI的接入来到 ai_server 文件夹,右键进入到该文件的终端。一. 初始化后端工程 npm init -y该操作和上一步一致二.导入项目所需要的库npm i openai该指令用于在项目中安装 openai 这个包,有了这个包就可以使用OpenAI的API接口了。 执行这个命令会做以下几件事:下载和安装包:从npm仓库下载 openai 包及其依赖到项目的 node_modules 目录。更新 package.json:执行完这个命令会将 openai 添加到 dependencies 字段,记录为项目依赖。如图所示:npm i dotenv该指令用于安装 dotenv 这个包,有了这个包,可以使用 .env 配置文件,加载 .env 文件中的环境变量到 process.env 对象中。将项目的私密内容封装到 .env 文件中不向外处暴露,可以很大的提高项目的安全性能。 如下所示:至此该项目所需的环境已经搭建完毕,下面我将为大家介绍如何编写代码实现。代码编写http服务的搭建(项目的核心部分)来到 ai_server 文件夹,新建一个名为 main.js 的 js 文件,输入以下js代码:// ai openai, :8888/users?question= // node 的内置模块 // - 搭建http服务 const http = require('http'); const url = require('url'); const OpenAI = require('openai'); require('dotenv').config(); const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, // proxy baseURL: 'https://api.chatanywhere.tech/v1' }) const server = http.createServer(async function (req, res) { res.setHeader('Access-Control-Allow-Origin', '*'); // 允许所有来源访问,也可以指定具体的域名,如'http://example.com' res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); // 允许的请求方法 res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // http 基于请求响应的简单协议 req 请求 res 响应 if (req.url.indexOf('/users') >= 0) { // users ai 服务 const parsedUrl = url.parse(req.url, true); // console.log(parsedUrl); const { question, users } = parsedUrl.query; console.log(question, users) const prompt = ` ${users} 请根据以上用户的json数据,回答${question}这个问题. 如果回答不了,就返回不清楚,谢谢。 ` const response = await client.chat.completions.create({ model: 'gpt-3.5-turbo', messages: [{ role: "user", content: prompt }], temperature: 0, // 控制输出的随机性,0表示更确定的输出 }); const result = response.choices[0].message.content || ''; console.log(result); let info = { message: result } res.statusCode = 200; res.setHeader('Content-Type', 'text/json'); res.end(JSON.stringify(info)) } }) server.listen(8888, function () { console.log('服务器启动了') })代码详解以上代码主要作用是搭建一个基于 Node.js 的简单 HTTP 服务器,使用了 Node.js 的内置 http 模块和第三方模块 url、OpenAI 以及 dotenv 来创建一个能够与 OpenAI 的 GPT 模型交互的服务,以处理特定的 HTTP GET 请求并返回 AI 生成的响应。下面是代码的详细解析:导入模块和配置http: Node.js的内置模块,用于创建HTTP服务器。url: Node.js的内置模块,用于URL解析。OpenAI: 块用于与OpenAI API交互,主要是用来调用gpt-3.5-turbo模型。dotenv: 用于加载.env文件中的环境变量,用于读取封装的OpenAI的API密钥。通过 require 语句导入这些模块,并使用 dotenv.config() 加载环境变量,然后实例化 OpenAI客户端,其中 baseURL 指定了代理的 API端点。创建HTTP服务器使用 http.createServer() 方法创建一个 HTTP服务器,传入一个异步函数作为回调处理请求和响应。设置响应头允许跨域访问,允许 任何源('*'),允许 GET、POST 和 OPTIONS 请求方法,以及指定允许的请求头。 res.setHeader('Access-Control-Allow-Origin', '*'); // 允许所有来源访问,也可以指定具体的域名 res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); // 允许的请求方法 res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');请求逻辑处理判断请求 URL 是否包含 /users 路径,如果是,则继续处理。if (req.url.indexOf('/users') >= 0)使用 url.parse() 解析请求 URL 的查询参数。const parsedUrl = url.parse(req.url, true);解构出 question 和 users 两个查询参数。 const { question, users } = parsedUrl.query;构造一个 prompt,包含提供的 JSON 数据和问题,要求 AI 模型根据这些信息回答提出的问题。使用 await client.chat.completions.create() 异步调用 OpenAI API,传递模型类型(gpt-3.5-turbo)、消息内容(包含构造的prompt)以及控制输出随机性的temperature参数。从API响应中提取第一选择的回复内容。将回复内容封装成 JSON 对象 info,设置响应状态码为 200(成功),响应头为 Content-Type: text/json,最后使用 res.end() 发送JSON格式的响应给客户端。启动服务器调用 server.listen(8888) 使服务器在本地 8888 端口上监听 HTTP 请求。总结该服务器的核心功能是接收包含用户数据和问题的 HTTP GET 请求,利用 gpt-3.5-turbo 模型生成针对该问题的回答,然后将这个回答作为JSON响应返回给客户端。用于构建HTML页面中的AI助理功能,根据用户提供的一系列用户数据和问题,返回相应的解答。前端代码来到 frontend 文件夹中,新建一个名为 index.html 的文件,在该文件中输入以下代码:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>AI全栈</title> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container"> <div class="row col-md-6 col-md-offset-3"> <h1>AI全栈</h1> <table class="table table-striped" id="user_table"> <thead> <tr> <th>ID</th> <th>姓名</th> <th>家乡</th> </tr> </thead> <tbody> </tbody> </table> <form name="aiForm" method="get" action="http://www.baidu.com"> <div class="form-group"> <label for="questionInput">向AI助理提问:</label> <input type="text" name="question" class="form-control" id="questionInput" placeholder="请输入您想问的users相关问题"> </div> <button type="submit" class="btn btn-default">提交</button> </form> <div class="row" id="message"></div> </div> </div> <script> const oMessage = document.querySelector('#message'); const oBody = document.querySelector('#user_table tbody'); const oForm = document.forms['aiForm']; let usersDate = [] fetch('http://localhost:3000/users') .then(data => data.json()) .then(users => { usersDate = users; oBody.innerHTML = users.map(user => ` <tr> <td>${user.id}</td> <td>${user.name}</td> <td>${user.hometown}</td> </tr> `).join('') }) oForm.addEventListener('submit', function (event) { // 阻止页面提交 阻止表单的默认行为 event.preventDefault(); // name 属性去找 性能更好 const question = this["question"].value.trim(); console.log(question); fetch(`http://localhost:8888/users?question=${question}&users=${JSON.stringify(usersDate)}`) .then(data => data.json()) .then(res => { // console.log(res); document.querySelector('#message').innerHTML = res.message; }) }) </script> </body> </html>代码详解该项目的 HTML 页面结合了 Bootstrap 框架和 JavaScript(使用fetch API)来实现一个用户信息展示和交互的示例。获取用户数据使用 fetch() 从http://localhost:3000/users(该地址为后端项目封装数据的地址,查询方式见上文) 获取数据。数据返回后,将其转换为JSON格式,并将结果赋值给 usersDate。遍历 usersDate,动态创建表格行()并添加到 oBody 中。表单提交处理给表单添加事件监听器到表单,当表单被提交时触发。使用 event.preventDefault() 阻止表单的默认提交行为(防止页面跳转)。获取用户在 questionInput 输入框中输入的问题。使用 fetch() 向http://localhost:8888/users发送问题和用户数据的GET请求。(注意:这里需要将用户数据信息转为json格式)fetch(`http://localhost:8888/users?question=${question}&users=${JSON.stringify(usersDate)}`)服务器返回的数据转为 JSON格式,并且从中获取 message 属性,最后将其内容显示在 oMessage 元素内。总结前端的 HTML 页面与后端的 Node.js 服务器代码配合工作,用户可以在界面上查看用户数据,输入问题,然后获取 AI 的回复。项目运行效果展示来到 ai_server 文件夹的终端,运行 main.js 来启动后端服务器。打开项目的前端页面,输入指令: 请问有哪些同学是老乡后端在接收到用户给出的请求时,调用 OpenAi 的接口立马给出了问题的答案。本篇文章就到此为止啦,希望通过这篇文章能对你了解使用 OpenAI API 搭建 AI助理 有所帮助,本人水平有限难免会有纰漏,欢迎大家指正。如觉得这篇文章对你有帮助的话,欢迎点赞收藏加关注,感谢支持🌹🌹。
2024年05月28日
8 阅读
0 评论
0 点赞
2024-03-18
vue2到vue3中插槽slot变化详解---从slot,slot-scope到v-slot的变化
前言vue 插槽,目前到3.0有2种方式,第一种,在2.6之前使用的是 slot 和 slot-scpe 2.6后已被官方废弃,但在2.x版本仍被支持,第二种是vue 在2.6版本后更新的新指令 v-slot 来替代slot 和 slot-scpe那么什么是插槽呢,作用又是什么插槽,简单说,插槽就是杯子,杯子里面装的是饮料还是牛奶,由外部倒入什么来决定 ,就好比下面的代码,我需要一个子组件,他有部分内容,需要根据我当前页面需要来展示,我如何将html模板传人到子组件就需要使用插槽。所以我定义了一个子组件item,我用solt标签定义了一个默认插槽,为在父组件使用时,需要传递到item组件的模板,占个位置, 这样我在组件,使用item子组件,在其中编写,html模板就会被渲染到子组件默认插槽//父组件 <template> <tab> <item > <div>装一杯牛奶</div> <item> <tab> </template> //item子组件 <template> <div> <slot ></slot>//默认插槽 在父组件使用item子组件,item标签包裹的内容将默认被渲染到子组件的 solt中 <h1> 我是杯子 </h1> </div> </template>这样的好处,显而易见,可以让组件模块化更清晰,同时复用性更高,不至于,我要一杯茶,我就要定义一个组件,我要一杯牛奶我又定义一个组件,有了插槽,我只需要定义一个杯子,要喝什么由使用的传人决定。上述代码也叫默认插槽,就是默认把模板全部渲染到solt中,如果需要指定渲染,就需要使用具名插槽,简单说就是起一个名字,告诉他小红该坐那儿,小明该坐那儿具名插槽//父级 <template> <div> <layout> <div solt="header">头部标题</div> <div >显示的内容</div> <div slot="footer">尾部</div> </layout> </div> </template> //layout子组件 <template> <div> <layout> <h1>layout子组件</h1> <slot name="header"></slot> //这种就叫具名插槽 <slot></slot> //如果不指定名字,就会将模板中未匹配到的内容渲染到默认插槽中,这里为显示的内容 <slot name="footer"></slot> </layout> </div> </template>上面已说, 具名插槽 简单说就是起一个名字,告诉他小红该坐那儿,小明该坐那儿tip: 当你的子组件中 如layout 中并不存在,slot这个元素,那么在父页面中 这个标签中的内容都会被抛弃作用域插槽父组件提供了模板给子组件,那么子组件如何反馈给父组件呢,例如:我定义了一个杯子,我需要告诉使用的人,我这个杯子,只能装300mL,这时我们就需要用slot-scope来接收子组件上通过v-bind绑定的值。作用域插槽,就是能让插槽内容访问到子组件中才有的数据//父级 <template> <div> <cup> <div solt="size" slot-scope="data"> {{data.msg}} </div> </cup> </div> </template> //cup子组件 <template> <div> <slot name="size" :msg="msg"></slot> </div> </template> <script> export default { data(){ return{ msg:'300mL大小的杯子' } } } </script>解构prop的写法下面写法等同上面//父级 <template> <div> <cup> <div slott="size" slot-scope="{msg}"> {{msg}} </div> </cup> </div> </template>上述是vue2.6之前的版本,之后vue官方废弃了上面的语法,改为v-solt来代替,然后大家就想知道,区别在哪呢首先就是 用一个指令合并了solt 和solt-scope2个attribute,写法更加简洁,其次就是语义化更明显2.6之前的写法会出现作用域混淆的问题例:<one> <two slot-scope="one"> //接受到的作用域,是外层one组件的,而不是当前组件two <three slot-scope="two"> <template slot-scope="three"> {{ one }} {{ two }} {{ three }} </template> </three> </two> </one>如上述代码一层子组件时,你能清晰的看清作用域是哪一个组件的,但多层嵌套后,每一层接收的作用域是外层组件的而不是当前组件的,这样,就会不清晰,所以vue希望能实现,当前组件,接受当前组件的作用域于是就有了v-solt 下面是改良后的代码,是否更加的清晰了<one v-slot="one"> <twotwo v-slot="two"> //接收到的作用域为当前two组件的 <three v-slot="three"> {{ one }} {{ two }} {{ three }} </three> </bar> </one>如果没看懂 那么下面来阐述,v-solt的使用变化,v-solt 默认插槽和原来不同便是,原来的solt属性可以定义在任何元素上,现在v-solt只能是template元素上,只有一种额外情况,就是独占默认插槽,我们先看常规情况。v-slot:default这种就是具名的写法//父组件 <template> <item > <template v-slot:default> // v-slot:default可以不加,只能定义在template上 <div>装一杯牛奶</div> </template> <item> </template> //item子组件 <template> <div> <slot ></slot>//默认插槽 <h1> 我是杯子 </h1> </div> </template>未具名的solt 元素,自动默认名为default 你可以不写,当然如果你要看的更清晰,独占默认插槽提供内容只有默认插槽,上述就满足此条件,所以我们可以这样写//父组件 <template> <item v-slot:default> //v-slot:default可以不加 <div>装一杯牛奶</div> <item> </template> //item子组件 <template> <div> <slot ></slot>//默认插槽 <h1> 我是杯子 </h1> </div> </template>v-solt具名插槽//父级 <template> <div> <layout> <template v-slot:header>//v-slot指令使用插槽 <div >头部标题</div> </template> <div >显示的默认内容</div> <!-- 或者 <template v-slot:default> <div >显示的默认内容</div> </template> --> <template v-slot:footer> <div >尾部</div> </template> </layout> </div> </template> //layout子组件 <template> <div> <layout> <h1>layout子组件</h1> <slot name="header"></slot> //这种就叫具名插槽 <slot></slot> //如果不指定名字,就会将模板中未匹配到的内容渲染到默认插槽中,这里为显示的内容 <slot name="footer"></slot> </layout> </div> </template>v-solt作用域插槽这是改动最大地方//父级 <template> <div> <cup> <template v-slot:default="data"> //具名写法 <div> {{data.msg}} </div> </template> <!-- 或者 <template v-slot="data"> <div > {{data.msg}} </div> </template> --> </cup> </div> </template> //cup子组件 <template> <div> <slot :msg="msg"></slot > </div> </template> <script> export default { data(){ return{ msg:'300mL大小的杯子' } } } </script>当为独占默认插槽时,v-solt可以省略default不写注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确下面是官方的例子<!-- 无效,会导致警告 --> <current-user v-slot="slotProps"> {{ slotProps.user.firstName }} <template v-slot:other="otherSlotProps"> slotProps is NOT available here </template> </current-user>所以当出现多个插槽的时候,请使用完整的基于 template 的语法解构props的写法这里使用上面cup组件的例子<template> <div> <cup> <template v-slot:default="{msg}"> //解构 <div> {{msg}} </div> </template> </cup> </div> </template>v-slot 的解构还提供 重命名的写法<template> <div> <cup> <template v-slot:default="{msg : size}"> //别名 <div> {{size}} </div> </template> </cup> </div> </template>动态插槽名v-slot 支持2.6的动态参数写法<layout> <template v-slot:[attributeName]> ... </template> </layout>这里的 attributeName 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的 Vue 实例有一个 data property attributeName,其值为 "header",那么这个绑定将等价于 v-slot:header。插槽的缩写2.6后插槽 可以把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #headerv-slot: 后面必须有值,不可写成#="{data}"<template> <div> <cup> <template #default="msg"> <div> {{size}} </div> </template> </cup> </div> </template>
2024年03月18日
39 阅读
0 评论
0 点赞
2023-07-21
Flex 布局教程:语法篇
Flex 布局教程:语法篇作者: 阮一峰日期: 2015年7月10日网页布局(layout)是 CSS 的一个重点应用。布局的传统解决方案,基于盒状模型,依赖 display 属性 + position属性 + float属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。2009年,W3C 提出了一种新的方案----Flex 布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。Flex 布局将成为未来布局的首选方案。本文介绍它的语法,下一篇文章给出常见布局的 Flex 写法。网友 JailBreak 为本文的所有示例制作了 Demo,也可以参考。以下内容主要参考了下面两篇文章:A Complete Guide to Flexbox 和 A Visual Guide to CSS3 Flexbox Properties。一、Flex 布局是什么?Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。任何一个容器都可以指定为 Flex 布局。 .box{ display: flex; } 行内元素也可以使用 Flex 布局。 .box{ display: inline-flex; } Webkit 内核的浏览器,必须加上-webkit前缀。 .box{ display: -webkit-flex; /* Safari */ display: flex; } 注意,设为 Flex 布局以后,子元素的float、clear和vertical-align属性将失效。二、基本概念采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size。三、容器的属性以下6个属性设置在容器上。flex-directionflex-wrapflex-flowjustify-contentalign-itemsalign-content3.1 flex-direction属性flex-direction属性决定主轴的方向(即项目的排列方向)。 .box { flex-direction: row | row-reverse | column | column-reverse; } 它可能有4个值。row(默认值):主轴为水平方向,起点在左端。row-reverse:主轴为水平方向,起点在右端。column:主轴为垂直方向,起点在上沿。column-reverse:主轴为垂直方向,起点在下沿。3.2 flex-wrap属性默认情况下,项目都排在一条线(又称"轴线")上。flex-wrap属性定义,如果一条轴线排不下,如何换行。 .box{ flex-wrap: nowrap | wrap | wrap-reverse; } 它可能取三个值。(1)nowrap(默认):不换行。(2)wrap:换行,第一行在上方。(3)wrap-reverse:换行,第一行在下方。3.3 flex-flowflex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。 .box { flex-flow: <flex-direction> || <flex-wrap>; } 3.4 justify-content属性justify-content属性定义了项目在主轴上的对齐方式。 .box { justify-content: flex-start | flex-end | center | space-between | space-around; } 它可能取5个值,具体对齐方式与轴的方向有关。下面假设主轴为从左到右。flex-start(默认值):左对齐flex-end:右对齐center: 居中space-between:两端对齐,项目之间的间隔都相等。space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。3.5 align-items属性align-items属性定义项目在交叉轴上如何对齐。 .box { align-items: flex-start | flex-end | center | baseline | stretch; } 它可能取5个值。具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下。flex-start:交叉轴的起点对齐。flex-end:交叉轴的终点对齐。center:交叉轴的中点对齐。baseline: 项目的第一行文字的基线对齐。stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。3.6 align-content属性align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。 .box { align-content: flex-start | flex-end | center | space-between | space-around | stretch; } 该属性可能取6个值。flex-start:与交叉轴的起点对齐。flex-end:与交叉轴的终点对齐。center:与交叉轴的中点对齐。space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。stretch(默认值):轴线占满整个交叉轴。四、项目的属性以下6个属性设置在项目上。orderflex-growflex-shrinkflex-basisflexalign-self4.1 order属性order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。 .item { order: <integer>; } 4.2 flex-grow属性flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。 .item { flex-grow: <number>; /* default 0 */ } 如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。4.3 flex-shrink属性flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。 .item { flex-shrink: <number>; /* default 1 */ } 如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。负值对该属性无效。4.4 flex-basis属性flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。 .item { flex-basis: <length> | auto; /* default auto */ } 它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。4.5 flex属性flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。 .item { flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ] } 该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。4.6 align-self属性align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。 .item { align-self: auto | flex-start | flex-end | center | baseline | stretch; } 该属性可能取6个值,除了auto,其他都与align-items属性完全一致。(完)
2023年07月21日
8 阅读
0 评论
0 点赞
2023-07-11
前后端分离,开源的 Spring Boot + Vue 3.2 的博客,泰裤辣!
WeBlog简介一款由 Spring Boot + Vue 3.2 开发的前后端分离博客。{mtitle title="Weblog 后台仪"/}后端采用 Spring Boot 、Mybatis Plus 、MySQL 、Spring Sericuty、JWT、Minio、Guava 等;后台管理采用 Vue 3.2 + Element Plus 纯手写的管理后台,未采用任何 Admin 框架;支持博客 Markdown 格式发布与编辑、文章分类、文章标签的管理;支持博客基本信息的设置,以及社交主页的跳转;支持仪表盘数据统计,Echarts 文章发布热图统计、PV 访问量统计;相关地址GitHub 地址:https://github.com/weiwosuoai/WeBlogGitee 地址:https://gitee.com/AllenJiang/WeBlog演示地址:http://118.31.41.16:8081/游客账号:test游客密码:test演示截图登录页仪表盘文章管理写博客前台首页博客详情功能前台功能是否完成首页✅分类列表✅标签标签✅博客详情✅站内搜索TODO知识库 WikiTODO博客评论TODO后台功能是否完成后台登录页✅仪表盘✅文章管理✅分类管理✅标签管理✅博客设置✅评论管理TODO模块介绍{mtitle title="WeBlog 项目模块一览"/}项目名说明weblog-springboot后端项目weblog-vue3前端项目后端项目模块介绍模块名说明weblog-module-admin博客后台管理模块weblog-module-common通用模块weblog-module-jwtJWT 认证、授权模块weblog-web博客前台(启动入口)技术栈后端框架说明版本号备注JDKJava 开发工具包1.8它是目前企业项目比较主流的版本Spring BootWeb 应用开发框架2.6.3主流框架Maven项目构建工具3.6.3企业主流的构建工具MySQL数据库5.7 Mybatis PlusMybatis 增强版持久层框架3.5.2 HikariCP数据库连接池4.0.3Spring Boot 内置数据库连接池,号称性能最强Spring Security安全框架2.6.3 JWTWeb 应用令牌0.11.2 Lombok消除冗余的样板式代码1.8.22 JacksonJSON 工具库2.13.1 Hibernate Validator参数校验组件6.2.0.Final Logback日志组件1.2.10 GuavaGoogle 开源的工具库18.0 p6spy动态监测框架3.9.1 Minio对象存储8.2.1用于存储博客中相关图片flexmarkMarkdown 解析0.62.2 前端框架说明版本号Vue 3Javascript 渐进式框架3.2.47Vite前端项目构建工具4.3.9Element Plus饿了么基于 Vue 3 开源的组件框架2.3.3vue-routerVue 路由管理器4.1.6vuex状态存储组件4.0.2md-editor-v3Markdown 编辑器组件3.0.1windicssCSS 工具类框架3.5.6axios基于 Promise 的网络请求库1.3.5Echarts百度开源的数据可视化图表库5.4.2
2023年07月11日
21 阅读
0 评论
0 点赞
2023-07-03
一款开源的匿名聊天工具,太牛了
今天推荐的这个项目是 「anonymous-chat-room 」 ,一个基于 livekit 和 Next.js 的匿名聊天室,可以进行文字、语音聊天,并支持语音录屏。特点部署简单:前端支持直接部署到 vercel ,后端可以直接使用 livekit cloud 的免费服务,也可以按照官方文档自建支持视频,语音聊天(默认只允许语音),无需登录支持文本聊天,也可以使用 emoji支持emoji表情搜索支持更多的消息类型,如图片、视频(开发中)支持浏览器直接录制麦克风,扬声器和屏幕(Chrome、 Edge 可以完全支持,safari 不支持扬声器录制)延迟测试(当前版本实现很简陋)设置房间密码前端可以选择使用多个 apikey ,通过轮询的方式选择可用的入口本地部署1、克隆或下载本仓库git clone git@github.com:velor2012/anonymous-chat-room.git cd anonymous-chat-room yarn install2、在 http://cloud.livekit.io 上创建一个新的 Project ,然后生成 apikey project settings 3、按照提示,修改 env.example 中的环境变量,重命名为 env.local 4、运行以下命令npm run dev之后就可以在 http://localhost:3000 打开。在线体验地址: https://chat.cwy666.eu.org/开源项目地址: https://github.com/velor2012/anonymous-chat-room
2023年07月03日
48 阅读
0 评论
0 点赞
1
2
3