第一个项目在2025年4月，做了一个健身房会员管理系统，用的是uniapp+Express+Mysql，从开发到部署将近两周时间。甲方要求的是一个微信小程序，但由于一些原因做成了一个h5网页版的。不过最后还是拿到了1000元劳务费。第二个项目以前对于老师们这种询问都是不予理睬的，但这次我参与了。这个项目是一个AI+智慧教学系统： 用的是Vue+Django+Mysql前后端分离。由于课堂管理工具课堂派开始收费，老师要做出一个替代它的，然后在基础功能上加一个学生上课状态的实时识别和一个思政课程智能推荐。说来也挺巧的，第一批功能开发中的两个队友都是熟人，一个是学号同桌，一个是邻班的老乡。第一批功能开发中我负责的是成绩管理模块，有出勤、答题、作业、小测成绩四部分，这个模块是最麻烦的一个，但好在都是增删改查，把数据库表设计清楚就好弄多了。带我们做的是老师的一个研究生学生，这上面是一个大致的要求，实际上还有一些功能，比如签到功能。第二批功能开发在暑假，接了一个作业模块模块。劳务费按照贡献度来分发，总共6.5我占了2.8。在刚接手项目的时候我还以为最后能拿个200块，没想到拿到了1600，一个月生活费了。正好要换手机，那这笔钱加上女朋友资助一些，换一个oppo FindX8手机。左边是新的x8，右边是正在用的x6。
基于Spring Boot + Vue.js的GPT AI聊天应用：从开发到部署的完整实践
前言一个人一个下午做成的，为了应对周四的作业答辩。🚀 项目概述本文将详细介绍一个基于Spring Boot后端和Vue.js前端的GPT AI聊天应用的完整开发流程。该项目集成了OpenAI、阿里云通义千问、DeepSeek、豆包、Kimi等多个主流AI平台，支持文本对话和图像生成功能，并实现了用户认证、会话管理、管理员面板、用户统计等完整的业务功能。体验地址：https://gpt.xuan.cyou/技术栈后端: Spring Boot 3.5.5, Java 17, SQLite, JPA/Hibernate前端: Vue.js 3, Vite, Pinia, JavaScript ES6+AI集成: OpenAI GPT-4, 阿里云通义千问, DeepSeek, 豆包, Kimi部署: 宝塔面板, Nginx, SSL证书📁 项目架构GPT-AI-Project/ ├── gpt-ai-java/ # Spring Boot 后端 │ ├── src/main/java/ │ │ ├── controller/ # REST API控制器 (用户, AI, 管理员等) │ │ ├── service/ # 业务逻辑层 (多AI平台服务, 用户统计等) │ │ ├── entity/ # 数据实体 (用户, 对话历史, 统计数据等) │ │ ├── config/ # 配置类 │ │ └── adopter/ # AI平台适配器 (OpenAI, Ali, DeepSeek, Kimi等) │ ├── src/main/resources/ │ │ └── application.yaml # 应用配置 │ └── pom.xml # Maven依赖配置 └── frontend/ # Vue.js 前端 ├── src/ │ ├── components/ # Vue组件 │ ├── views/ # 页面视图 (聊天, 图像, 管理面板, 用户后台等) │ ├── api/ # API调用封装 │ └── store/ # 状态管理 (Pinia) ├── public/ # 静态资源 └── package.json # NPM依赖配置🔧 核心功能实现1. 后端核心架构1.1 多AI平台集成项目支持OpenAI和阿里云通义千问两个AI平台，通过适配器模式实现：// AI控制器统一入口 @RestController @RequestMapping("/api/ai") public class AiController { @GetMapping("/chat/text/stream") public SseEmitter streamChat( @RequestParam String platform, @RequestParam String model, @RequestParam String message ) { // 根据平台选择对应的AI服务 (示例) if ("openai".equals(platform)) { return openAiController.sendTextStream(model, message); } else if ("ali".equals(platform)) { return aliAiController.sendTextStream(model, message); } // ... 此处添加对 deepseek, doubao, kimi 等平台的路由逻辑 return null; // or throw an exception } }1.2 流式响应实现使用Server-Sent Events (SSE) 实现实时流式对话：@PostMapping("/chat/text/stream") public SseEmitter sendTextStream(String model, String message) { SseEmitter emitter = new SseEmitter(0L); CompletableFuture.runAsync(() -> { try { // 流式读取AI响应并实时推送 openAIService.sendText(model, message, true, delta -> { emitter.send(SseEmitter.event().name("delta").data(delta)); }); emitter.send(SseEmitter.event().name("done").data("[DONE]")); emitter.complete(); } catch (Exception ex) { emitter.completeWithError(ex); } }); return emitter; }1.3 会话管理系统实现了完整的对话历史记录功能：@Entity public class ConversationHistory { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String userId; private String sessionId; private String userMessage; private String assistantReply; private LocalDateTime timestamp; // ... getters and setters }1.4 跨域配置为支持前后端分离和域名部署，配置了完整的CORS支持：@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns( "http://localhost:*", "https://gpt.xuan.cyou" ) .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowCredentials(true); } }2. 前端核心实现2.1 API客户端封装统一封装所有后端API调用：// 流式聊天 export function streamChat({ platform, model, message, onDelta, onDone }) { const url = `/api/ai/chat/text/stream?platform=${platform}&model=${model}&message=${encodeURIComponent(message)}`; const eventSource = new EventSource(url); eventSource.addEventListener('delta', (event) => { const data = JSON.parse(event.data); onDelta?.(data.delta); }); eventSource.addEventListener('done', () => { onDone?.(); eventSource.close(); }); } // 图像生成 export async function createImage(params) { const response = await fetch('/api/ai/create/image', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params) }); return await response.json(); }2.2 实时对话界面Vue.js实现的流式对话体验： AI正在回复... 2.3 状态管理 (Pinia)项目使用Pinia进行全局状态管理，取代了Vuex，提供了更简洁的API和完整的TypeScript支持。// src/store/index.js import { defineStore } from 'pinia' export const useMainStore = defineStore('main', { state: () => ({ user: null, currentSession: null, settings: { platform: 'openai', textModel: 'gpt-4o-mini', imageModel: 'dall-e-3', backendUrl: 'http://localhost:8083' } }), actions: { setUser(user) { this.user = user; }, updateSettings(settings) { this.settings = { ...this.settings, ...settings }; } } })🛠️ 开发环境搭建1. 后端开发环境# 1. 克隆项目 git clone <repository-url> cd gpt-ai-java # 2. 配置application.yaml server: port: 8083 spring: application: name: gpt-ai datasource: url: jdbc:sqlite:identifier.sqlite driver-class-name: org.sqlite.JDBC # 3. 运行项目 mvn spring-boot:run2. 前端开发环境# 1. 安装依赖 cd frontend npm install # 2. 配置开发代理 # vite.config.js const target = process.env.BACKEND_URL || 'http://localhost:8083' export default defineConfig({ server: { proxy: { '/api': { target, changeOrigin: true } } } }) # 3. 启动开发服务器 npm run dev📦 生产环境部署1. 后端打包部署# Maven打包 mvn clean package -DskipTests # 部署运行 java -jar target/gpt-ai-0.0.1-SNAPSHOT.jar # 或使用systemd守护进程 sudo systemctl start gpt-ai2. 前端构建部署# 构建生产版本 npm run build # 部署到服务器 # 将dist/目录下所有文件上传到 /www/wwwroot/gpt.xuan.cyou/dist/3. Nginx配置针对宝塔面板的完整Nginx配置：server { listen 80; listen 443 ssl http2; server_name gpt.xuan.cyou; root /www/wwwroot/gpt.xuan.cyou/dist; # SSL配置 ssl_certificate /www/server/panel/vhost/cert/gpt.xuan.cyou/fullchain.pem; ssl_certificate_key /www/server/panel/vhost/cert/gpt.xuan.cyou/privkey.pem; # API代理到后端服务 location /api/ { proxy_pass http://127.0.0.1:8083; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 支持SSE长连接 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; proxy_send_timeout 3600s; } # Vue.js SPA路由支持 location / { try_files $uri $uri/ @fallback; } location @fallback { rewrite ^.*$ /index.html last; } # 静态资源缓存优化 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; } }🎯 核心功能特性1. 多AI平台支持OpenAI集成: 支持GPT-4系列模型，DALL-E图像生成阿里云集成: 支持通义千问文本对话，通义万相图像生成新增平台: 已集成DeepSeek, 豆包, Kimi等更多主流AI模型统一接口: 通过platform参数无缝切换不同AI服务2. 实时流式对话SSE技术: Server-Sent Events实现实时数据推送流式体验: 模拟真实对话，逐字显示AI回复异常处理: 完善的连接错误和重连机制3. 会话管理系统历史记录: 完整保存用户对话历史会话分组: 支持多会话并行管理数据持久化: SQLite数据库存储4. 图像生成功能多平台支持: OpenAI DALL-E和阿里云通义万相参数配置: 支持不同尺寸和风格设置结果展示: 优雅的图像展示和下载功能5. 用户认证系统登录注册: 基本的用户认证功能权限控制: 用户数据隔离和访问控制设置管理: 个性化配置保存6. 管理员面板用户管理: 查看、管理所有注册用户系统监控: 实时查看系统使用统计数据数据分析: 提供用户行为和AI使用情况的图表分析7. 用户统计个人仪表板: 用户可以查看自己的AI使用历史和统计数据用量跟踪: 详细记录每日和累计的token使用量可视化图表: 直观展示个人使用趋势🔍 技术亮点1. 前后端分离架构RESTful API: 标准的REST接口设计跨域处理: 完善的CORS配置支持代理配置: 开发环境和生产环境的统一代理方案2. 响应式设计移动适配: 支持手机、平板等多种设备现代UI: 简洁美观的用户界面交互体验: 流畅的动画和反馈效果3. 性能优化静态资源缓存: 合理的缓存策略Gzip压缩: 减少传输数据量懒加载: 按需加载提升首屏速度4. 部署友好容器化支持: 可轻松容器化部署宝塔面板: 兼容主流服务器管理面板SSL支持: HTTPS安全访问🚨 常见问题解决1. API代理404错误问题: 前端API请求返回404错误解决: 确保Nginx配置了正确的API代理规则2. SSE连接中断问题: 流式对话中途断开解决: 配置Nginx的proxy_read_timeout参数3. 跨域问题问题: 浏览器CORS错误解决: 检查后端CORS配置和Nginx代理头设置4. 静态资源404问题: Vue路由刷新后404解决: 配置Nginx的try_files规则📈 未来扩展方向1. 功能增强语音对话: 集成语音识别和合成文件上传: 支持文档分析功能插件系统: 可扩展的功能插件架构2. 技术优化缓存机制: Redis缓存提升性能消息队列: 异步处理大量请求微服务架构: 拆分为多个独立服务3. 运维改进监控告警: 完善的系统监控日志分析: ELK日志分析栈自动部署: CI/CD持续集成部署💡 总结本项目展示了一个完整的现代Web应用开发流程，从技术选型、架构设计到开发实现和生产部署，涵盖了全栈开发的各个环节。通过Spring Boot和Vue.js的组合，实现了高效、可维护的前后端分离架构。项目的核心价值在于：技术栈现代化: 采用最新的技术栈和最佳实践功能完整性: 涵盖了AI对话应用的核心功能部署友好性: 支持多种部署方式和环境扩展性良好: 架构设计便于后续功能扩展希望本文能为想要开发类似AI应用的开发者提供有价值的参考和指导。Gitee仓库：前端 - https://gitee.com/wx6765/chatgpt_frontend后端 - https://gitee.com/wx6765/chatgpt_backend本文基于实际项目开发经验总结，代码示例均来自真实项目实现。
概述FriendsRSS 是一款专为 Typecho 博客系统设计的智能友链 RSS/Atom 聚合插件。它能够自动检测、解析和聚合您友链博客的最新文章，通过多种展示方式（短代码、API接口、RSS输出、前台页面）让您的博客成为友圈动态的聚合中心。插件功能核心功能友链来源集成：从 Typecho links 表的指定分类读取友链，自动参与聚合。兼容性增强：当指定分类不存在时，自动获取所有友链，完美兼容只有一种友链的主题博客。排除功能：支持配置排除检测的博客网址，每行一个，支持部分匹配。RSS 自动检测：采用"三轮检测"策略（常用路径/feed和/feed/ → HTML 解析 → 其他常见路径），并对检测结果进行 10天缓存（RSS地址相对稳定）。定时检测功能：支持配置定时检测RSS地址间隔（默认240小时/10天），RSS地址缓存时间自动与定时检测间隔保持一致。定时解析功能：支持配置定时解析间隔（默认6小时），提供独立的定时任务脚本，支持命令行和HTTP调用。手动配置 RSS：后台提供"选择友链 + 输入 RSS 地址"的表单，保存到 rss_config.json 作为覆盖与纠偏。短代码功能：在任意文章或页面中使用 rss 短代码来显示友链RSS聚合内容，支持 limit 参数控制显示文章数量。多格式支持：兼容 RSS 2.0 / Atom 1.0 / RSS 1.0，解析标题、链接、作者、摘要与发布时间等字段。文章聚合与排序：为每个博客限制抓取数量，并按发布时间 倒序 排列，支持聚合总数上限。缓存与并发保护：聚合结果持久化缓存，内置 锁文件 防止并发重复任务；可读取已有缓存以快速展示。日志记录：记录检测、解析与网络状态，便于排查异常与性能问题。输出功能RSS订阅地址：/action/friends-rss?do=rss - 生成标准RSS 2.0格式的聚合订阅源API接口地址：/action/friends-rss?do=page - 返回JSON格式的聚合文章数据前台展示页面：/action/friends-rss?do=pageview - 美观的前台展示页面，支持响应式设计[collapse status="false" title="示例图片查看"][/collapse]管理界面统计卡片：显示友链数量、文章数量、最后更新时间、已配置RSS数量等统计信息操作卡片：定时检测RSS地址：显示当前检测间隔和下次执行时间，提供"立即执行"按钮定时解析RSS内容：显示当前解析间隔和下次执行时间，提供"立即执行"按钮添加RSS地址：手动配置友链RSS地址的表单界面访问地址区块：显示所有可用的访问地址和友链分类信息友链与文章列表：显示友链列表和最新聚合文章，支持RSS配置状态标识日志显示区域：实时显示操作日志，支持自动滚动到最新内容短代码功能基本用法：在任意文章或页面中使用 rss 短代码来显示友链RSS聚合内容参数支持：支持 limit 参数控制显示文章数量块状设计：使用简洁美观的块状设计，包含博客信息、文章标题、摘要等响应式布局：自动适配移动端设备，优化显示效果网络与兼容性优化请求优化：优先使用 cURL，请求超时与重定向受控内容清理：自动清理与修复常见 XML/文本问题、摘要长度截断错误处理：完善的异常处理和日志记录机制并发保护：使用锁文件防止重复任务执行缓存策略：智能缓存管理，支持缓存统计和清理定时任务功能独立脚本：提供 cron.php 独立定时任务脚本HTTP调用：支持通过HTTP请求触发定时任务状态管理：记录定时任务执行状态和下次执行时间安全验证：支持密钥验证防止未授权访问Ajax功能实时检测：支持SSE（Server-Sent Events）实时显示RSS检测进度批量操作：支持批量检测RSS地址和清除缓存进度反馈：实时显示操作进度和结果错误处理：完善的错误处理和用户反馈机制安装与配置将插件文件夹上传到 usr/plugins/ 目录。在 Typecho 后台启用插件 FriendsRSS。在插件设置页配置友链来源、每个博客抓取文章数、聚合总数上限与缓存时间等参数。Github地址：[hide]https://github.com/BXCQ/FriendsRSS[/hide]配置参数说明友链分类：指定需要聚合的友链分组。兼容性说明：Handsome主题：填写"ten"获取全站链接，填写"one"获取内页链接，填写"good"获取推荐链接其他主题：通常留空即可，插件会自动获取所有友链当指定分类不存在时，插件会自动获取所有友链，确保兼容性排除检测的博客网址：每行一个网址，支持部分匹配。例如：example.com - 排除所有包含example.com的友链https://blog.example.com - 排除特定博客留空表示不排除任何友链每个博客文章数：每个博客抓取与展示的文章数量限制。最大文章数：聚合后的文章总数上限。定时解析间隔：定时解析友链RSS的间隔时间（小时），默认6小时，设置为0表示禁用。文章缓存时间会自动与定时解析间隔保持一致。定时检测间隔：定时检测友链RSS地址的间隔时间（小时），默认240小时（10天），设置为0表示禁用。RSS地址缓存时间会自动与定时检测间隔保持一致。启用前台展示：是否开放前台聚合页与 RSS 输出。使用方法检测 RSS：在后台点击"定时检测RSS地址"卡片中的"立即执行"按钮，自动为友链站点识别 RSS/Atom 地址并保存。定时检测：在后台点击"定时检测RSS地址"，按配置的间隔时间自动检测友链RSS地址。手动添加：在"添加 RSS 地址"区域选择目标友链并填写正确的订阅地址后保存。聚合文章：点击"定时解析RSS内容"，按时间排序聚合最新文章并写入缓存。访问输出：RSS订阅地址：/action/friends-rss?do=rssAPI接口地址：/action/friends-rss?do=page（需在设置中启用）前台展示页面：/action/friends-rss?do=pageview（需在设置中启用）性能与稳定性超时控制：为单次请求与连接设置合理超时并限制重定向次数。并发保护：使用锁文件/状态标记避免重复聚合任务并减少服务器压力。分轮检测：优先常用路径，其次 HTML 解析，最后其他常见路径以提升成功率与速度。缓存策略缓存类型缓存时间说明RSS地址缓存与定时检测间隔一致RSS地址相对稳定，与定时检测间隔同步，减少重复检测文章内容缓存与定时解析间隔一致确保定时解析有意义，避免缓存过期后立即重新解析失败结果缓存1小时避免重复请求失败的RSS源，减少服务器压力定时策略配置项默认值说明定时解析间隔6小时定时解析友链RSS的间隔时间，设置为0表示禁用文章缓存时间自动同步与定时解析间隔保持一致，确保定时解析有意义定时检测间隔240小时（10天）定时检测友链RSS地址的间隔时间，设置为0表示禁用RSS地址缓存时间自动同步与定时检测间隔保持一致，减少重复检测实际效果缓存过期时重新执行定时任务执行时缓存刚好过期，获取最新内容设计思路：以"逻辑一致性"为核心，确保定时任务与缓存时间同步，提升用户体验和系统效率。RSS短代码使用基本用法在任何文章或页面中使用 rss 短代码来显示友链RSS聚合内容，使用简洁的块状设计：rss默认显示10篇最新文章，使用块状设计。或者rss limit="5"只显示5篇最新文章。显示内容每个文章块包含：博客信息头部：博客名称、作者信息、发布时间文章标题：可点击跳转到原文文章摘要：自动截取的文章概要（200字符以内）参数说明limit类型: 数字默认值: 10说明: 显示文章的数量注意不要放置多个，会重复显示。设计特点块状设计简洁美观：每篇文章独立成块，清晰区分信息完整：包含博客、作者、时间、标题、摘要视觉层次：合理的间距和颜色搭配悬停效果：鼠标悬停时显示阴影效果响应式设计移动端优化：自动适配小屏幕设备布局调整：移动端下头部信息垂直排列字体缩放：移动端下适当调整字体大小注意事项缓存机制: 短代码使用缓存机制，文章更新可能有延迟性能考虑: 建议在高流量站点限制显示数量（limit参数）样式兼容: 短代码样式已针对常见主题优化，如有冲突可自定义CSS安全性: 所有外部内容都经过HTML转义处理错误处理与兼容性超时处理：如果你的友链过多且开启了CDN，可以在CDN中设置超时时间，从默认的30s到60s或者更高。网络异常与 HTTP 错误：会被记录并跳过，继续处理其他源。XML 内容清理：在解析前进行清理，尽量兼容 RSS 2.0、Atom 1.0 与 RSS 1.0 常见格式。扩展要求：建议启用 cURL、SimpleXML 等常用扩展并确保插件对缓存目录拥有写入权限。更新日志日期版本更新类型变更内容2025/8/31v2.2.1缓存机制修复• 缓存机制修复：修复缓存机制导致RSS配置无法及时更新的问题：增加配置文件时间戳检查，当配置文件比缓存文件更新时自动强制刷新缓存，确保能读取到最新的手动配置RSS地址• 调试信息增强：增强RSS配置读取调试信息：添加详细的配置文件读取日志，特别针对特定友链配置状态的检查和记录，便于问题排查• 思路：以"缓存一致性"为核心，确保配置文件更新后能及时生效，提升手动配置RSS的可靠性2025/8/31v2.2.0配置文件修复• 配置文件修复：修复RSS配置文件和缓存目录缺失导致的配置读取失败问题：在构造函数中自动创建rss_config.json文件和缓存目录，确保系统正常运行• 错误处理完善：完善错误处理机制：增加文件读取、JSON解析和缓存操作的详细错误日志记录，改进后台RSS配置保存逻辑• 思路：以"健壮性优先"为核心，确保插件在各种环境下都能正常工作，提升系统稳定性2025/8/25v2.1.6定时检测RSS功能• 定时检测RSS功能：新增定时检测RSS功能：支持配置定时检测RSS地址间隔（默认240小时/10天），RSS地址缓存时间自动与定时检测间隔保持一致• 定时检测界面：管理界面优化：新增"定时检测RSS地址"操作卡片，显示当前检测间隔和下次执行时间• 按钮简化：移除重复的"手动检测"按钮，只保留"立即执行"按钮，避免功能重复• 缓存策略：缓存策略优化：RSS地址缓存时间从固定7天改为与定时检测间隔同步，提升系统一致性• 短代码文档：新增RSS短代码使用说明，包含基本用法、参数说明、使用示例和设计特点• 思路：以"逻辑一致性"为核心，确保定时检测与RSS地址缓存同步，提升用户体验和系统效率2025/8/25v2.1.0RSS地址检测• RSS地址检测：新增RSS地址检测功能：自动检测友链博客的RSS地址，支持多种常见RSS路径，提供手动添加功能• 管理界面重构：管理界面重构：新增RSS地址管理、检测日志显示、定时任务设置等功能模块• 缓存机制优化：缓存机制优化：RSS地址缓存24小时，RSS内容缓存可配置，提升系统性能• 思路：以"自动化检测"为核心，减少手动配置工作量，提升用户体验2025/8/25v2.1.5友链分类兼容• 友链分类兼容：友链分类兼容性增强：当指定分类不存在时，自动获取所有友链，完美兼容只有一种友链的主题博客• 排除功能：新增排除功能：支持配置排除检测的博客网址，每行一个，支持部分匹配，提供更灵活的友链管理• 分类统计界面：管理界面优化：新增"可用分类"统计卡片，在访问地址区域显示所有可用的友链分类及当前使用状态• 配置界面优化：配置界面优化：友链分类设置项默认值改为空，添加详细的兼容性说明，帮助用户正确配置不同主题的友链• 思路：以"最大兼容性"为核心，确保插件能在各种Typecho主题环境下正常工作，提升用户体验2025/8/25v2.1.4日志显示修复• 日志显示修复：修复日志显示错误：将"用时20s"等错误显示修正为实际请求用时，使用microtime()精确计算请求耗时• 日志自动滚动：管理界面体验优化：日志显示区域自动滚动到最新内容，无需手动滚动查看最新日志• 策略文档：新增缓存策略和定时解析策略表格说明，更直观地展示系统设计思路2025/8/25v2.1.3缓存策略优化• 缓存策略优化：缓存策略优化：RSS地址缓存时间从24小时延长至7天（更符合RSS地址相对稳定的特点），文章内容缓存时间自动与定时解析间隔保持一致，移除独立的缓存时间设置项• 定时解析功能：新增定时解析功能：支持配置定时解析间隔（默认6小时），提供独立的定时任务脚本，支持命令行和HTTP调用• 配置界面简化：简化配置界面：移除"缓存时间"设置项，避免用户困惑，定时解析间隔设置中说明缓存时间会自动同步• 思路：以"逻辑一致性"为核心，确保定时解析与缓存时间同步，提升用户体验和系统效率2025/8/25v2.1.2界面优化• 缓存时间优化：RSS缓存设置优化：将缓存时间单位从秒改为天，默认值从360000秒（100小时）调整为10天，更符合用户使用习惯• 移除超时设置：移除请求超时设置项：删除未使用的timeout配置项，简化插件设置界面• 界面名称优化：管理界面名称优化：将"检测友链RSS"改为"检测友链RSS地址"，"定时解析控制"改为"定时解析RSS内容"，"前台展示页面"改为"前台接口地址"，"预览页面"改为"前台展示页面"• 思路：以"用户体验优化"为核心，简化配置项，统一命名规范，提升界面友好度2025/8/25v2.1.1管理界面优化• 预览页面地址：新增预览页面地址：在访问地址区域添加 /action/friends-rss?do=pageview 预览链接• 管理界面优化：管理界面优化：将定时解析控制块替换解析RSS功能，移除定时任务设置说明，简化操作流程• 移除预览页面块：移除预览页面块：简化管理界面，减少冗余展示区域• 错误日志显示：新增错误日志显示：在管理页面中显示 error.log 日志内容，便于问题排查• 思路：以"简化操作 + 增强监控"为核心，提升管理效率和问题诊断能力2025/8/24v2.0.4UI布局优化• UI布局优化：后台"添加 RSS 地址"区域 UI 调整：输入框与选择框长度比例优化（输入 65% / 选择 30%），取消选择框固定高度，移动端响应式更佳• select对齐修复：解决 Typecho 默认 select 固定高度 28px 导致与输入框不对齐的问题• 思路：表单布局采用 flex，优先保障主要输入区可用性与可读性，减少视觉噪点2025/8/24v2.0.3管理面板重构• 管理面板重构：管理面板重构：新增统计卡片、操作卡片与访问地址区块；友链与文章列表支持状态徽标，信息呈现更直观• 最后更新修复：修复"最后更新"在无缓存时显示 1970 的问题，改为显示"暂无"• 思路：采用 模块化卡片布局，将"检测/解析/添加"动作解耦，提升可维护性与扩展空间2025/8/23v2.0.2锁文件机制• 锁文件机制：新增聚合任务 锁文件 机制；提供缓存清理与缓存统计；聚合结果持久化读取，页面可直接消费缓存• 并发解析修复：修复并发解析导致缓存文件被覆盖/损坏的偶发问题；修复缓存为空时解析流程可能抛错的情况• 思路：从"实时拉取"转为 缓存优先 的数据策略，后台按需触发解析，降低对外部源的耦合与压力2025/8/23v2.0.1三轮RSS检测• 三轮RSS检测：引入 三轮 RSS 自动检测；为检测结果设置 24 小时缓存；细化请求超时与重定向限制，并记录响应头• 链接解析修复：修复部分站点 HTML 中相对链接解析错误；修正 Content-Type 判断大小写不一致导致的识别失败• 思路：采用"快速路径优先 → HTML 解析 → 兜底常见路径"的分轮策略，先提升成功率与速度，再做细化校验2025/8/23v2.0.0解析器升级• 解析器升级：解析器升级：支持 RSS 2.0 / Atom 1.0 / RSS 1.0；清理与截断描述文本；新增 rss_config.json 用于手动配置；按发布时间倒序聚合并限制总量• XML解析修复：修复特殊字符、BOM 与控制字符导致 XML 解析失败的问题；增强 HTML 实体修复• 思路：以"可靠解析"为核心：先清理再解析，最大化容错；将必要的清洗逻辑前置，保证数据质量2025/8/23v1.5.0RSS输出功能• RSS输出功能：提供聚合 RSS 输出 与可选 前台展示页面；后台展示最近文章与来源信息• 时间排序修复：修复文章缺失时间时排序不稳定，统一回退到当前时间• 思路：以 /action/friends-rss 为统一入口，降低主题/路由差异带来的接入成本2025/8/23v1.4.0多源聚合功能• 多源聚合功能：支持多源聚合与发布时间排序；可配置"每博客文章数"与"聚合总数"• 内存优化：优化大列表处理的内存开销，改为先按需切片再渲染• 思路：将"数量限制"前移到聚合阶段，减少后续处理成本2025/8/23v1.3.0摘要截断功能• 摘要截断功能：自动 截断摘要，保证列表简洁可读• HTML清理修复：修复未清理 HTML 标签导致的样式外溢问题• 思路：以摘要优先，保证信息密度与浏览效率2025/8/23v1.2.0手动添加RSS功能• 手动添加RSS功能：后台新增"手动添加 RSS"能力，为自动检测失败站点提供纠偏• JSON读写修复：修复首次保存配置时空文件读取异常，增强 json 读写容错• 思路：自动检测与 人工纠偏 并行，提高覆盖率与准确度2025/8/23v1.1.0性能参数配置• 性能参数配置：增加缓存时间、重定向上限与超时等基础性能参数配置• 超时设置修复：修复部分环境下超时设置不生效的问题• 思路：坚持"可控超时"原则，保证失败快速返回与系统整体可用性2025/8/23v1.0.0初始版本• 初始版本：初始版本：多源 RSS/Atom 聚合、后台基础管理与展示• Bug：无• 思路：提供最小可用集合，优先打通"来源 → 解析 → 聚合 → 展示"的闭环
前端开发中嵌入B站视频播放器：解决high_quality=1 失效的问题
问题背景在前端开发中，我们可能需要嵌入B站视频。使用B站官方播放器时，遇到了以下问题：默认画质被限制在360p高清画质需要登录B站账号部分视频需要大会员才能观看高清版本问题分析默认的B站原生播放器 存在的问题：画质限制：即使设置了high_quality=1和quality=720，实际播放时仍被限制在360p，这两个方法在22年就失效了登录要求：高清画质需要用户登录B站账号会员限制：部分视频的高清版本需要大会员权限解决方案改进后的高清解析播放器[hide] [/hide]优势：画质提升：可以直接获取视频源默认画质，1080p同样生效无需登录：用户无需登录B站账号突破限制：可以观看部分需要大会员的视频总结通过使用视频解析接口，成功解决了B站视频播放器的画质限制问题。希望这个解决方案对你有所帮助，有问题请在评论区交流。
智慧零工平台前端开发实战：从uni-app到跨平台应用
本文将详细介绍我如何使用uni-app框架开发一个支持微信小程序和H5的零工平台前端应用，包含技术选型、架构设计、核心功能实现及部署经验。前言在当今移动互联网时代，跨平台开发已成为提高开发效率的重要手段。本次我选择uni-app框架开发了一个智慧零工平台的前端应用，该平台致力于为零工与雇主搭建高效便捷的双向服务桥梁。项目概况项目名称: 智慧零工平台前端系统技术栈: Vue.js 2.6 + uni-app 3.0 + ColorUI支持平台: 微信小程序 + H5项目地址: GitHub - Gigplatform_front在线预览: https://lgpt.xuan.cyou/技术选型分析为什么选择uni-app？在众多跨平台解决方案中，我最终选择了uni-app，主要基于以下考虑：一套代码多端运行: 支持编译到微信小程序、H5、App等10+平台学习成本低: 基于Vue.js语法，前端开发者容易上手生态完善: 拥有丰富的组件库和插件市场性能优异: 接近原生应用的性能表现社区活跃: DCloud官方维护，社区支持良好核心技术栈{ "前端框架": "Vue.js 2.6.11", "跨平台框架": "uni-app 3.0", "UI组件库": "ColorUI 2.1.6", "样式预处理": "SCSS/SASS", "状态管理": "Vuex", "构建工具": "webpack", "开发工具": "HBuilderX" }项目架构设计整体架构项目采用模块化架构设计，清晰分离业务逻辑和技术实现：smart-gig-platform-front/ ├── api/ # API接口层 ├── components/ # 公共组件 ├── pages/ # 页面文件 ├── employerPackage/ # 雇主端分包 ├── static/ # 静态资源 ├── store/ # 状态管理 ├── colorui/ # UI组件库 └── utils/ # 工具函数分包策略为了优化小程序包体积，我采用了分包加载策略：{ "subPackages": [ { "root": "employerPackage", "name": "employer", "pages": [ "pages/center/index", "pages/postJob/index", "pages/resume/index" ] } ] }这样可以将雇主端功能独立打包，减少主包体积，提升首屏加载速度。核心功能实现1. 双重身份系统这是项目的一大特色功能，用户可以在零工和雇主身份间无缝切换： <view class="identity-switch"> <view class="switch-container"> <view class="switch-item" :class="{ active: currentRole === 'worker' }" @click="switchRole('worker')" > <image src="/static/img/worker-icon.png" /> <text>我是零工</text> </view> <view class="switch-item" :class="{ active: currentRole === 'employer' }" @click="switchRole('employer')" > <image src="/static/img/employer-icon.png" /> <text>我是雇主</text> </view> </view> </view> 2. 地理位置服务实现基于位置的工作推荐功能：// 获取用户位置 async getUserLocation() { try { const res = await uni.getLocation({ type: 'wgs84' }) this.userLocation = { latitude: res.latitude, longitude: res.longitude } // 获取附近工作 await this.getNearbyJobs() } catch (error) { console.error('获取位置失败:', error) uni.showToast({ title: '位置获取失败', icon: 'none' }) } }, // 计算距离 calculateDistance(lat1, lon1, lat2, lon2) { const R = 6371 // 地球半径(km) const dLat = (lat2 - lat1) * Math.PI / 180 const dLon = (lon2 - lon1) * Math.PI / 180 const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon/2) * Math.sin(dLon/2) const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)) return R * c }3. 智能搜索与筛选实现多维度的工作搜索功能： <view class="search-container"> <view class="search-bar"> 搜索 </view> <view class="filter-section"> <picker :value="selectedCategory" :range="categories" range-key="name" @change="onCategoryChange" > <view class="filter-item"> {{ selectedCategory ? categories[selectedCategory].name : '选择分类' }} </view> </picker> <picker :value="selectedSalary" :range="salaryRanges" @change="onSalaryChange" > <view class="filter-item"> {{ selectedSalary ? salaryRanges[selectedSalary] : '薪资范围' }} </view> </picker> </view> <scroll-view scroll-y class="job-list" @scrolltolower="loadMore" > <job-card v-for="job in jobList" :key="job.id" :job-data="job" @click="goToDetail(job.id)" /> </scroll-view> </view> 4. 实时消息系统实现雇主与零工间的实时通信：// WebSocket连接管理 class MessageService { constructor() { this.socket = null this.reconnectAttempts = 0 this.maxReconnectAttempts = 5 } connect(userId) { try { this.socket = uni.connectSocket({ url: `wss://lgpt.ybyq.wang/ws/${userId}`, header: { 'Authorization': uni.getStorageSync('token') } }) this.socket.onOpen(() => { console.log('WebSocket连接成功') this.reconnectAttempts = 0 }) this.socket.onMessage((res) => { const message = JSON.parse(res.data) this.handleMessage(message) }) this.socket.onClose(() => { console.log('WebSocket连接关闭') this.reconnect() }) this.socket.onError((error) => { console.error('WebSocket错误:', error) this.reconnect() }) } catch (error) { console.error('WebSocket连接失败:', error) } } sendMessage(message) { if (this.socket) { this.socket.send({ data: JSON.stringify(message) }) } } handleMessage(message) { // 处理接收到的消息 switch (message.type) { case 'chat': this.updateChatList(message) break case 'notification': this.showNotification(message) break case 'status_update': this.updateApplicationStatus(message) break } } reconnect() { if (this.reconnectAttempts < this.maxReconnectAttempts) { setTimeout(() => { this.reconnectAttempts++ this.connect() }, 2000 * this.reconnectAttempts) } } }响应式设计与适配多端适配策略为了确保在不同平台上的一致体验，我采用了以下适配策略：// 使用rpx单位进行响应式设计 .container { padding: 20rpx; // H5端特殊处理 /* #ifdef H5 */ max-width: 750rpx; margin: 0 auto; /* #endif */ // 微信小程序特殊处理 /* #ifdef MP-WEIXIN */ padding-top: calc(20rpx + env(safe-area-inset-top)); /* #endif */ } // 统一的颜色变量 $primary-color: #4CAF50; $success-color: #4CAF50; $warning-color: #FF9800; $danger-color: #F44336; // 字体大小规范 $font-size-xs: 22rpx; $font-size-sm: 24rpx; $font-size-base: 28rpx; $font-size-lg: 32rpx; $font-size-xl: 36rpx;组件化开发开发了一系列可复用组件，提高开发效率： <view class="job-card" @click="handleClick"> <view class="job-header"> <view class="job-title">{{ jobData.title }}</view> <view class="job-salary text-primary">{{ jobData.salary }}</view> </view> <view class="job-info"> <view class="company-name">{{ jobData.companyName }}</view> <view class="job-location"> <text class="icon-location"></text> {{ jobData.location }} </view> </view> <view class="job-tags"> <view v-for="tag in jobData.tags" :key="tag" class="tag" > {{ tag }} </view> </view> <view class="job-footer"> <view class="publish-time">{{ formatTime(jobData.publishTime) }}</view> <view class="apply-count">{{ jobData.applyCount }}人申请</view> </view> </view> 性能优化实践1. 图片优化// 图片懒加载 Vue.directive('lazy-load', { bind(el, binding) { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { el.src = binding.value observer.unobserve(el) } }) }) observer.observe(el) } }) // 图片压缩处理 function compressImage(file, quality = 0.8) { return new Promise((resolve) => { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') const img = new Image() img.onload = () => { canvas.width = img.width canvas.height = img.height ctx.drawImage(img, 0, 0) canvas.toBlob(resolve, 'image/jpeg', quality) } img.src = URL.createObjectURL(file) }) }2. 数据缓存策略// API数据缓存 class CacheManager { constructor() { this.cache = new Map() this.expireTime = 5 * 60 * 1000 // 5分钟 } set(key, data) { this.cache.set(key, { data, timestamp: Date.now() }) } get(key) { const item = this.cache.get(key) if (!item) return null if (Date.now() - item.timestamp > this.expireTime) { this.cache.delete(key) return null } return item.data } clear() { this.cache.clear() } } // 使用示例 async function fetchJobList(params) { const cacheKey = `job_list_${JSON.stringify(params)}` const cachedData = cacheManager.get(cacheKey) if (cachedData) { return cachedData } const response = await api.getJobList(params) cacheManager.set(cacheKey, response.data) return response.data }3. 代码分割与按需加载// 路由懒加载 const routes = [ { path: '/pages/jobDetail/jobDetail', component: () => import('@/pages/jobDetail/jobDetail.vue') }, { path: '/pages/employer/index', component: () => import('@/pages/employer/index.vue') } ] // 组件按需引入 export default { components: { JobCard: () => import('@/components/job-card.vue'), FilterPanel: () => import('@/components/filter-panel.vue') } }部署与发布H5部署构建生产版本npm run build:h5服务器配置server { listen 80; server_name lgpt.ybyq.wang; root /www/wwwroot/lgpt.ybyq.wang/h5; index index.html; # SPA路由支持 location / { try_files $uri $uri/ /index.html; } # API代理 location /api/ { proxy_pass https://lgpt.ybyq.wang:8081/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 静态资源缓存 location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } # Gzip压缩 gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; }微信小程序发布构建小程序版本npm run build:mp-weixin小程序配置{ "appid": "your-app-id", "projectname": "智慧零工平台", "setting": { "urlCheck": false, "es6": true, "postcss": true, "minified": true, "newFeature": true }, "compileType": "miniprogram" }发布流程使用微信开发者工具导入项目配置合法域名白名单进行真机测试提交审核并发布开发经验总结遇到的挑战跨平台兼容性问题不同平台API差异样式表现不一致事件处理机制差异性能优化挑战小程序包体积限制长列表渲染性能图片加载优化用户体验优化网络异常处理加载状态管理错误边界处理最佳实践代码组织// 统一的错误处理 Vue.config.errorHandler = (err, vm, info) => { console.error('Vue Error:', err, info) // 上报错误日志 reportError(err, info) } // 全局混入 Vue.mixin({ methods: { $showLoading(title = '加载中...') { uni.showLoading({ title }) }, $hideLoading() { uni.hideLoading() }, $showToast(title, icon = 'none') { uni.showToast({ title, icon }) } } })API请求封装// request.js const request = (options) => { return new Promise((resolve, reject) => { uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', 'Authorization': getToken(), ...options.header }, success: (res) => { if (res.data.code === 0) { resolve(res.data) } else { uni.showToast({ title: res.data.message, icon: 'none' }) reject(res.data) } }, fail: (error) => { uni.showToast({ title: '网络错误', icon: 'none' }) reject(error) } }) }) }状态管理// store/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { userInfo: null, userRole: 'worker', // worker | employer jobList: [], messageList: [] }, mutations: { SET_USER_INFO(state, userInfo) { state.userInfo = userInfo uni.setStorageSync('userInfo', userInfo) }, SET_USER_ROLE(state, role) { state.userRole = role uni.setStorageSync('userRole', role) } }, actions: { async login({ commit }, loginData) { try { const response = await api.login(loginData) const { token, user } = response.data uni.setStorageSync('token', token) commit('SET_USER_INFO', user) return response } catch (error) { throw error } } } })未来规划功能扩展增加语音通话功能实现AR技能展示加入AI智能推荐技术升级升级到Vue 3 + Composition API引入TypeScript增强类型安全使用Vite提升构建性能用户体验优化加入暗黑模式支持优化无障碍访问提升加载性能结语通过这次uni-app跨平台开发实践，我深刻体会到了现代前端技术的强大能力。uni-app让我们能够用一套代码覆盖多个平台，大大提高了开发效率。项目中最有价值的经验包括：合理的架构设计是项目成功的基础组件化开发能显著提高代码复用率性能优化需要从多个维度综合考虑用户体验是产品成功的关键因素希望这篇文章能为正在学习uni-app或准备进行跨平台开发的朋友们提供一些参考和启发。项目链接:🌐 在线预览: https://lgpt.ybyq.wang/💻 GitHub仓库: https://github.com/BXCQ/Gigplatform_front📚 技术博客: https://blog.ybyq.wang/如果您对项目有任何问题或建议，欢迎在GitHub上提Issue或通过邮箱 676567473@qq.com 联系我！
在微信小程序开发过程中，轮播图组件(swiper)是常用的UI元素，但在实际应用中经常遇到高度不匹配导致的空白问题。本文详细记录了一次轮播图高度优化的完整过程，特别是针对固定宽高比图片的精确适配方案。问题背景在开发"零工市场"微信小程序时，首页包含一个轮播图组件。轮播图使用了三张尺寸为516×145像素的图片，但在实际显示时，轮播图区域为414×207，导致底部出现了大量空白区域，影响了整体页面美观度。初步尝试方案1. 使用自动高度方式最初尝试使用mode="widthFix"来保持图片宽高比，但默认的轮播图组件不会自动适应图片高度，仍然会出现空白。<swiper class="screen-swiper square-dot" :indicator-dots="true" :autoplay="true" :circular="true" :interval="3000" :duration="500"> <swiper-item v-for="(item, index) in banners" :key="index"> <image :src="item.imageUrl" mode="widthFix" class="swiper-image"></image> </swiper-item> </swiper>效果：图片本身按原始比例显示，不会变形但轮播图容器的高度没有随图片高度自动调整，底部依然存在大量空白指示点位置可能不正确，过于靠下2. 尝试强制变形适应接着尝试使用scaleToFill模式强制图片填充整个容器区域，但这种方法会导致图片变形。<swiper class="screen-swiper" :indicator-dots="true" :autoplay="true" :circular="true" :interval="3000" :duration="500" :style="{height: bannerHeight}"> <swiper-item v-for="(item, index) in banners" :key="index"> <image :src="item.imageUrl" mode="scaleToFill" class="swiper-image"></image> </swiper-item> </swiper>效果：图片被强制拉伸填充整个容器，没有空白内容显示不完整视觉效果较差3. 尝试使用aspectFit模式为了保持图片比例不变形，尝试使用aspectFit模式，但这种方式会在上下两侧产生空白。<swiper class="screen-swiper" :indicator-dots="true" :autoplay="true" :circular="true" :interval="3000" :duration="500" :style="{height: bannerHeight}"> <swiper-item v-for="(item, index) in banners" :key="index"> <image :src="item.imageUrl" mode="aspectFit" class="swiper-image"></image> </swiper-item> </swiper>效果：图片保持原始比例，不会变形但图片两侧或上下出现空白，实际显示尺寸较小没有完全利用容器空间最终解决方案经过多次尝试后，找到了最佳解决方案：结合使用外层容器和精确比例计算。核心代码实现1. HTML结构改进：使用外层容器<view class="banner-wrapper" :style="{height: bannerHeight}"> <swiper class="screen-swiper" :indicator-dots="true" :indicator-color="'rgba(255, 255, 255, .3)'" :indicator-active-color="'#ffffff'" :autoplay="true" :circular="true" :interval="3000" :duration="500"> <swiper-item v-for="(item, index) in banners" :key="index" @tap="bannerTap(item)"> <image :src="item.imageUrl" mode="widthFix" class="swiper-image"></image> </swiper-item> </swiper> </view>效果：轮播图外层容器高度通过精确计算，刚好与图片等高图片完整显示，保持原始比例，不会变形完全没有多余的空白区域，图片宽度占满整个容器宽度整体显示效果紧凑、自然、美观，指示点位于图片内部底部在不同屏幕尺寸下都能保持正确的显示比例2. 精确计算容器高度// 预加载轮播图第一张图片以获取高度 preloadBannerImage() { // 直接根据已知图片比例计算 // 原图为516 × 145，比例约为3.559:1 const windowWidth = uni.getSystemInfoSync().windowWidth // 根据屏幕宽度和图片比例计算高度 const imgHeight = windowWidth / (516/145) this.bannerHeight = imgHeight + 'px' console.log("Banner height calculated:", this.bannerHeight, "Screen width:", windowWidth) }3. CSS样式优化.banner-wrapper { width: 100%; margin: 0; padding: 0; overflow: hidden; position: relative; } .screen-swiper { width: 100%; height: 100%; .swiper-image { width: 100%; display: block; } }方案原理解析这个解决方案的核心在于：使用外部容器控制高度：不直接设置swiper组件的高度，而是通过外部容器banner-wrapper控制整体高度。精确计算比例：根据原始图片的确切宽高比(516÷145)计算出在当前屏幕宽度下应该显示的准确高度。保持图片比例：使用mode="widthFix"确保图片保持原始比例显示，不会变形。图片填充整个区域：通过设置图片width: 100%确保图片横向完全填充容器。优势不需要复杂CSS：避免了使用复杂的CSS强制控制内部元素精确匹配：容器高度与图片实际显示高度完全一致不会变形：图片保持原始比例，不会被拉伸变形响应式适配：在不同宽度的设备上都能保持正确比例避免额外空白：完全消除了轮播图下方的多余空白完整实现效果经过优化后，轮播图区域高度刚好与图片显示高度一致，图片不变形，没有多余空白，整体页面布局更加紧凑美观。而且这种方案能够适应不同尺寸的设备，始终保持图片的正确显示比例。适用场景这种方案特别适用于以下场景：轮播图图片具有固定的宽高比需要精确控制轮播图区域高度要求图片不能变形或被裁剪页面布局需要紧凑，不允许有额外空白总结通过精确计算图片显示高度并使用外层容器控制轮播图区域，成功解决了微信小程序中轮播图高度自适应的问题。这种方案不仅保证了图片不变形，还确保了轮播图区域的高度精确匹配图片显示高度，从而提升了整体页面的美观度和用户体验。对于需要精确控制轮播图显示效果的小程序开发者，这是一个简单有效的解决方案。各次方案效果对比总结：widthFix：图片不变形但有空白。scaleToFill：无空白但图片变形。aspectFit：图片不变形但有边空白。最终方案（推荐）：图片不变形、无空白、比例自适应，最美观。
uniapp-文件查找失败: ‘@dcloudio/uni-ui/lib/uni-icons/uni-icons.vue‘
今天在HBuilderX中使用uniapp开发微信小程序时遇到了这个问题：uniapp-文件查找失败：'@dcloudio/uni-ui/lib/uni-icons/uni-icons.vue'，就是找不到uni-ui组件我当时创建项目，选择了一个中间带+的底部带选项卡模板，并没有选择内置uni-ui的项目模板，因为是第一次用，并不知道前者中没有引入uni-ui看了看官方文档，有三种导入方式1、新建项目时，直接选择uni-ui模板2、使用 uni_modules 方式安装组件库，可以直接通过插件市场导入3、使用npm+easycom引入，在终端执行npm i @dcloudio/uni-ui 或 yarn add @dcloudio/uni-ui报错原因我用的是npm+easycom引入的，pages.json中已经引入了easycom，但是文件夹中的内容是空的，找不到文件。解决办法注释掉easycom那几行代码，删掉左边的文件夹，然后使用上面说的第二种方式导入组件即可。
一、基础概念先搞懂第一次学html的时候就对"/"有疑惑，现在就以我的个人博客 https://blog.ybyq.wang 为例，带大家看看这个斜杠"/"到底有啥讲究。啥是URL？举个栗子，我博客里的文章地址 https://blog.ybyq.wang/archives/ 就是一个典型的目录型URL，而具体文章地址如 https://blog.ybyq.wang/2024/05/hello-world 就是资源型URL。目录和资源咋区分？目录型：以/结尾，比如我的技术笔记目录 https://blog.ybyq.wang/notes/资源型：不含末尾/，比如我的关于页面 https://blog.ybyq.wang/about二、带"/"和不带"/"的实战区别服务器处理实操假设你访问我的笔记目录：输入 https://blog.ybyq.wang/notes → 服务器发现notes是目录，立刻301重定向到 https://blog.ybyq.wang/notes/（自动补/）输入 https://blog.ybyq.wang/notes/ → 直接展示目录下的index.html相对路径大坑预警假设我的笔记目录里有张图片：正确姿势：在 https://blog.ybyq.wang/notes/ 页面使用 → 实际路径 https://blog.ybyq.wang/notes/diagram.png错误姿势：如果在 https://blog.ybyq.wang/notes 页面（未补/）使用相同代码 → 浏览器会错误解析到 https://blog.ybyq.wang/diagram.png（直接跑到根目录去了！）SEO影响如果谷歌同时收录了：https://blog.ybyq.wang/python-tipshttps://blog.ybyq.wang/python-tips/导致相同内容被判定重复页面，影响搜索排名。可以在Nginx加了这行配置解决：# 自动补全目录斜杠 if (-d $request_filename) { rewrite [^/]$ $uri/ permanent; }API开发在开发后端API时，假设有这样的接口：GET https://127.0.0.1/api/list → 返回所有内容GET https://127.0.0.1/api/list/ → 可能返回404（因为服务器误以为要访问list目录下的index.html）三、最佳实践手册博客这样配置所有目录链接强制带/，比如导航菜单生成链接时自动补/在Hexo静态生成器中设置permalink: :year/:month/:title/Nginx配置自动补全规则（上文已贴）开发者避坑指南写HTML时，内部链接统一用绝对路径： 而不是 href="notes"用JavaScript处理路径时，推荐使用new URL()方法：// 自动处理路径拼接 const baseUrl = new URL('https://blog.ybyq.wang/notes/'); const imgUrl = new URL('diagram.png', baseUrl).href; // 得到正确路径常用框架处理方式框架默认行为配置建议WordPress自动补全目录/在固定链接设置中勾选"添加斜杠"Hexo根据permalink配置决定在文章模板中统一添加/React路由严格匹配路径使用四、测试方案给你的网站做个全面检查：随便访问一个目录，比如 https://你的网站.com/category期望：自动跳转到/category/危险：显示404或列出目录文件在文章页按F12打开控制台，输入：console.log(new URL('./avatar.jpg', window.location.href).href)正确：生成完整路径如 https://blog.ybyq.wang/../../avatar.jpg错误：路径跳到上级目录用SEO工具检测：检查是否存在重复的带/和不带/页面确保sitemap.xml中所有URL格式统一五、总结升华URL末尾这个小小的/，就像代码里的分号，看着不起眼，但忽略它可能引发蝴蝶效应。我之前因为这个问题导致移动端CSS加载异常——仅仅因为一个缺失的斜杠，页面样式全崩了！记住这个口诀：目录带杠稳如狗，资源无杠天下走。服务器要配重定向，前端路由盯紧口。
1. 简介在 Uniapp 开发中，自定义 Tabbar 是一个常见的需求。本教程将详细介绍如何实现一个美观且功能完整的自定义 Tabbar，并分享在实现过程中可能遇到的问题和解决方案。2. 实现步骤2.1 创建自定义 Tabbar 组件首先，我们需要创建一个自定义的 Tabbar 组件。以下是完整的实现代码： <view class="custom-tabbar"> <view v-for="(item, index) in tabList" :key="index" class="tabbar-item" :class="{'tabbar-item-active': current === index}" @click="switchTab(item.pagePath)" > <van-icon :name="current === index ? item.selectedIcon : item.icon" class="tabbar-icon" :class="{'tabbar-icon-active': current === index}" /> <text>{{ item.text }}</text> </view> </view> 2.2 配置 pages.json在 pages.json 中需要禁用原生 Tabbar，并配置页面路由：{ "tabBar": { "custom": true, // 启用自定义 Tabbar "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/detail/detail", "text": "详情" }, { "pagePath": "pages/course/course", "text": "课程" }, { "pagePath": "pages/profile/profile", "text": "我的" } ] } }3. 实现过程中的关键点3.1 路由映射使用 TAB_ROUTES 对象来映射路由路径和对应的索引值，这样可以方便地管理当前选中的 tab：const TAB_ROUTES = { '/pages/index/index': 0, '/pages/detail/detail': 1, '/pages/course/course': 2, '/pages/profile/profile': 3 };3.2 样式设计使用 position: fixed 确保 Tabbar 固定在底部添加 env(safe-area-inset-bottom) 适配全面屏手机使用 box-shadow 添加阴影效果实现点击缩放动画效果添加底部指示条3.3 图标处理使用 van-icon 组件（来自 Vant UI）来显示图标，通过 :class 动态切换选中状态。4. 常见问题及解决方案4.1 页面跳转问题问题：使用 uni.switchTab 跳转时可能出现页面不更新的情况。解决方案：确保在 pages.json 中正确配置了 tabBar使用 uni.switchTab 而不是 uni.navigateTo在 watch 中监听路由变化，及时更新选中状态4.2 样式适配问题问题：在不同机型上可能出现底部遮挡或样式错乱。解决方案：使用 env(safe-area-inset-bottom) 适配全面屏设置合适的 z-index 确保层级正确使用 rpx 单位确保在不同设备上显示一致4.3 性能优化问题：频繁切换可能导致性能问题。解决方案：使用 v-for 时添加 :key合理使用 watch 监听路由变化避免在 Tabbar 中加载过多资源5. 扩展功能5.1 添加徽标可以在 Tabbar 项上添加徽标，显示未读消息数量等：<view class="tabbar-item"> <van-icon :name="icon" /> <text>{{ text }}</text> <view v-if="badge" class="badge">{{ badge }}</view> </view>5.2 添加动画效果可以添加更丰富的动画效果，如：.tabbar-item { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .tabbar-item:hover { transform: translateY(-5rpx); }6. 总结通过以上步骤，我们实现了一个功能完整、样式美观的自定义 Tabbar。这个实现方案具有以下特点：支持路由跳转适配不同机型提供良好的视觉反馈性能优化易于扩展
前期准备1. 注册微信开放平台访问微信开放平台注册开发者账号完成开发者资质认证创建网站应用2. 获取必要的参数AppID：应用的唯一标识AppSecret：应用密钥网站域名：需要配置的网站域名回调域名：用户授权后跳转的域名3. 配置安全域名登录微信开放平台进入"网站应用"管理添加网站域名下载验证文件并上传到网站根目录开发步骤1. 引入微信JS-SDK 2. 配置微信JS-SDKwx.config({ debug: false, // 开启调试模式 appId: '你的AppID', timestamp: '', // 生成签名的时间戳 nonceStr: '', // 生成签名的随机串 signature: '', // 签名 jsApiList: [] // 需要使用的JS接口列表 });3. 实现登录流程3.1 获取授权码function getWechatAuth() { const appid = '你的AppID'; const redirect_uri = encodeURIComponent('你的回调地址'); const scope = 'snsapi_login'; // 授权作用域 const state = '随机字符串'; // 防CSRF攻击 const url = `https://open.weixin.qq.com/connect/qrconnect?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`; window.location.href = url; }3.2 获取访问令牌async function getAccessToken(code) { const appid = '你的AppID'; const secret = '你的AppSecret'; try { const response = await fetch(`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${secret}&code=${code}&grant_type=authorization_code`); const data = await response.json(); return data; } catch (error) { console.error('获取访问令牌失败:', error); return null; } }3.3 获取用户信息async function getUserInfo(access_token, openid) { try { const response = await fetch(`https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openid}`); const data = await response.json(); return data; } catch (error) { console.error('获取用户信息失败:', error); return null; } }4. 处理回调// 在回调页面处理授权码 const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); const state = urlParams.get('state'); if (code && state) { // 验证state防止CSRF攻击 if (state === '你之前生成的随机字符串') { // 获取访问令牌 const tokenData = await getAccessToken(code); if (tokenData) { // 获取用户信息 const userInfo = await getUserInfo(tokenData.access_token, tokenData.openid); if (userInfo) { // 处理用户信息，如保存到本地存储或发送到服务器 handleUserInfo(userInfo); } } } }注意事项1. 安全相关state参数：必须使用随机字符串，防止CSRF攻击HTTPS：必须使用HTTPS协议域名验证：确保域名与开放平台配置一致密钥保护：AppSecret必须妥善保管，不要暴露在前端2. 用户体验登录按钮：使用微信官方提供的按钮样式错误处理：友好的错误提示加载状态：显示加载中的状态登录状态：合理处理登录状态过期3. 性能优化缓存处理：合理使用localStorage缓存登录状态请求优化：减少不必要的接口调用错误重试：适当的重试机制常见问题1. 授权失败检查域名是否配置正确验证AppID和AppSecret是否正确确认回调地址是否编码正确2. 获取用户信息失败检查access_token是否有效确认scope是否包含必要权限验证openid是否正确3. 跨域问题确保服务器配置了正确的CORS头检查请求头是否包含必要信息验证域名是否在允许列表中最佳实践1. 代码组织// 建议的代码结构 const WechatLogin = { config: { appId: '你的AppID', redirectUri: '你的回调地址' }, init() { // 初始化配置 }, login() { // 处理登录逻辑 }, handleCallback() { // 处理回调逻辑 }, getUserInfo() { // 获取用户信息 } };2. 错误处理function handleError(error) { const errorMap = { '40029': '无效的授权码', '40163': '授权码已使用', '41002': '缺少必要参数' }; const message = errorMap[error.errcode] || '未知错误'; console.error(`微信登录错误: ${message}`); // 显示错误提示给用户 }结论通过以上步骤，我们可以成功在H5网站中接入微信登录功能。在实际开发中，还需要根据具体业务需求进行适当的调整和优化。建议在开发过程中多参考微信开放平台的官方文档，确保功能的正确实现。参考链接微信开放平台文档微信JS-SDK说明文档微信登录开发指南