Uniapp+SpringBoot+Vue全平台博客系统开发实战与避坑指南
2026/9/15 20:51:58 网站建设 项目流程

从博客系统这个老话题里做出新意,技术选型是最关键的环节。我这次没有走传统单体应用的老路,直接选了Uniapp + SpringBoot + Vue这套组合拳,把个人博客从纯PC浏览升级成了覆盖App、小程序、H5和PC管理后台的全平台方案。这篇文章不聊虚的,直接把我从数据库建模到三端联调、从打包上架到服务器部署的完整过程拆给你看,所有踩过的坑和对应的排查思路都会讲到。

如果你正准备做博客类系统,或者正在纠结毕业设计/个人项目的技术栈选型,这篇文章的内容应该能帮你节省大量的试错时间。我会把每个关键模块的设计原因、参数计算过程和在实际开发中遇到的典型问题全部记录下来,尽量保证你照着走能顺利复现,少走弯路。

1. 整体架构设计与技术选型背后的思考

1.1 为什么最终选择这套技术组合

先说结论:Uniapp解决多端复用,SpringBoot解决后端开发效率,Vue解决后台管理界面的交互复杂度。这三个框架单独拿出来都不是新东西,但组合在一起正好覆盖了个人博客系统的全部使用场景。

我之前用纯Vue + Node写过一版博客,老实说功能都能跑,但痛点很致命:手机浏览器看文章排版稀烂,也没法打包成App推送给朋友用。后来想补一个小程序版本,发现代码要全部重写,维护成本直接翻倍。正当我犹豫要不要放弃App端时,接触到了Uniapp,它的核心卖点就是一套代码编译到iOS、Android、H5以及各种小程序平台。这意味着我只需要维护一份移动端代码,就能覆盖绝大多数用户访问博客的场景。

后端选SpringBoot而不是Node或者Django,主要看中三点:第一,SpringBoot的生态太成熟了,做博客系统需要的权限认证、数据库操作、文件上传都有非常稳定的解决方案;第二,Java的静态类型系统在项目规模变大后优势明显,博客系统虽然看起来简单,但加上评论、标签、分类、用户体系后,涉及的表关系和业务逻辑并不少;第三,SpringBoot的自动配置极大简化了项目搭建过程,一个空的Web项目只需要几行依赖就能跑起来,这对追求快速开发的我来说很重要。

前端管理后台用Vue的理由就简单多了,它的响应式数据绑定和组件化开发方式,让我在写文章管理、评论审核这类密集表格交互页面时效率极高,加上Element Plus这类组件库已经提供了现成的表格、表单、弹窗组件,UI层面的工作量大大减少。

1.2 系统的模块划分与核心功能清单

在设计阶段,我把整个系统按使用角色和终端划分为三个端:

  • 用户端(Uniapp):面向普通访客,包括文章列表浏览、文章详情阅读、标签分类筛选、评论互动、个人中心、搜索功能等。
  • 管理端(Vue):面向博主本人,包括文章发布与编辑(支持富文本和Markdown两种格式)、文章分类和标签管理、评论审核与删除、站点统计仪表盘、个人资料设置等。
  • 服务端(SpringBoot):为上述两端提供统一RESTful API接口,负责数据处理、权限校验、文件存储、日志记录等。

数据库层面我设计了6张核心表:用户表(user)、文章表(article)、分类表(category)、标签表(tag)、文章标签关联表(article_tag)、评论表(comment)。另外加了两张辅助表:系统配置表(config)用于保存站点标题、公告等全局信息,操作日志表(log)用于记录后台的敏感操作。

这个表结构不是拍脑袋定的,而是根据博客系统的核心业务流程推导出来的。比如文章和标签的关系,一篇文章可能有多个标签,一个标签下有多篇文章,这就必须用中间表article_tag来维护多对多关系。分类则是典型的一对多,所以我直接在article表里冗余了一个category_id字段,查询时做一次JOIN就能拿到分类名,避免连表太深影响性能。

1.3 前后端分离架构下的接口设计约定

接口设计方面,我统一采用了RESTful风格,并且约定了一套固定的返回结构:

{ "code": 200, "message": "操作成功", "data": {} }

code为200表示成功,非200为失败,message是提示信息,data是实际数据。这样设计的好处是前端无论在哪一端(Uniapp或Vue),都能用同一个拦截器处理错误和加载状态,不用针对不同接口做特殊处理。

接口路径也做了明确的划分,以/api开头表示需要认证的接口,以/public开头表示公开接口。比如获取文章列表就是GET /public/article/list,发布新文章就是POST /api/article/save。这样的路径规则在后期的权限拦截器里非常好写,只需判断路径前缀即可区分是否需要校验Token。

分页参数统一使用pageNum(页码,从1开始)和pageSize(每页条数),返回的数据用PageResult对象包装,包括总条数total、总页数pages和当前页数据list三个字段。这样前端无论做下拉加载还是分页组件,拿到的数据格式都是固定的。

2. SpringBoot后端核心实现与避坑指南

2.1 项目初始化的版本选择与配置陷阱

创建SpringBoot项目是第一步,也是很多新手会卡住的地方。账号本身不是问题,问题往往出在版本选择上。如果你直接用Spring Initializr创建项目,默认拉取的SpringBoot版本可能是3.x甚至更高,而3.x版本要求JDK 17以上,并且很多第三方组件(比如某些生成验证码的库、老版本的MyBatis Plus)还没做好适配,运行时会冒出各种奇怪的异常。

我这次的推荐组合是:SpringBoot 2.7.x + JDK 1.8 + MyBatis Plus 3.5.x。这套组合非常稳定,网上能搜到的资料也多,踩坑成本最低。如果你非要用SpringBoot 3.x,那请注意JDK必须切到17,同时MyBatis Plus需要换用对应的3.5.3以上版本,否则启动直接会报ClassNotFound异常。

application.yml配置文件里有一个细节特别容易被忽略,就是日期序列化格式。如果你的表里有create_time这样的字段,默认情况下返回给前端的格式会是时间戳或者带时区的复杂字符串,Uniapp端解析起来很麻烦。建议在配置文件中指定统一的日期格式:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

同理,mybatis-plus的配置也要注意下划线转驼峰的开关,否则你的实体类字段和数据库列名无法自动映射:

mybatis-plus: configuration: map-underscore-to-camel-case: true

2.2 文章表设计与MyBatis Plus分页查询实现

文章表是博客系统的核心,它的字段设计直接影响了查询效率和后续功能的扩展空间。我最终的建表语句大致如下:

CREATE TABLE `article` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `title` varchar(200) NOT NULL COMMENT '文章标题', `summary` varchar(500) DEFAULT NULL COMMENT '文章摘要', `content` longtext NOT NULL COMMENT '文章正文', `cover` varchar(500) DEFAULT NULL COMMENT '封面图URL', `category_id` bigint(20) DEFAULT NULL COMMENT '分类ID', `author_id` bigint(20) DEFAULT NULL COMMENT '作者ID', `status` tinyint(4) DEFAULT '1' COMMENT '状态:0草稿,1发布', `is_top` tinyint(4) DEFAULT '0' COMMENT '是否置顶', `view_count` int(11) DEFAULT '0' COMMENT '浏览量', `like_count` int(11) DEFAULT '0' COMMENT '点赞数', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_category` (`category_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

正文用longtext而不是text,是为了支持较长的图文混排内容。分类ID加索引,是因为列表页最常见的查询就是按分类筛选文章。状态加索引,是因为前台默认只展示status=1的已发布文章。

分页查询我利用了MyBatis Plus提供的能力,但并没有直接用简单的selectPage,因为还需要关联查出分类名称和作者昵称。我的做法是先在Service层把分页条件拼好,然后用自定义SQL关联查询:

public PageResult<ArticleVO> getArticlePage(int pageNum, int pageSize, Long categoryId, String keyword) { Page<Article> page = new Page<>(pageNum, pageSize); QueryWrapper<Article> wrapper = new QueryWrapper<>(); wrapper.eq(categoryId != null, "a.category_id", categoryId) .like(StringUtils.isNotBlank(keyword), "a.title", keyword) .eq("a.status", 1) .orderByDesc("a.is_top") .orderByDesc("a.create_time"); Page<ArticleVO> result = articleMapper.selectArticlePage(page, wrapper); return new PageResult<>(result.getTotal(), result.getPages(), result.getRecords()); }

这里有个容易踩的坑:如果用Wrapper传条件给自定义SQL,XML里的${ew.customSqlSegment}才能正确拼接条件。如果你在XML里手写了WHERE 1=1再拼接参数,很容易出现SQL注入风险或者条件丢失,我的建议是直接用MyBatis Plus的机制,尽量少手写动态SQL。

2.3 基于JWT的登录认证与权限控制

博客后台肯定不能允许任何人随便发文章,所以用户认证是必不可少的一环。我选用了JWT(JSON Web Token)方案,而不是传统的Session方案,主要原因是JWT无状态,适合前后端分离场景,服务端不需要维护会话信息,扩容也方便。

JWT的核心概念可以这样理解:它就是一张带了签名和过期时间的通行证,用户登录成功后,服务端把用户ID和过期时间加密生成一个字符串返回给前端。前端后续每次请求都在Header里带上这个字符串,服务端验证签名合法后就能确定请求者的身份。

具体实现上,我用的是jjwt这个库,生成和解析代码都不复杂:

// 生成Token String token = Jwts.builder() .setSubject(userId.toString()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); // 解析Token Claims claims = Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody(); Long userId = Long.parseLong(claims.getSubject());

注意这里的密钥secretKey一定不能硬编码在代码里,哪怕是个人博客也要养成好习惯,放到配置文件中,通过@Value注解注入。过期时间我设置成了7天,这个时长对个人博客来说比较合适,用户不需要频繁登录,同时又不至于太长导致安全隐患。

权限控制我用的是SpringBoot的拦截器机制。写一个AuthInterceptor,在preHandle方法里校验请求路径和Token。如果是/api/开头的路径且未携带有效Token,直接返回401错误码;如果是公开接口,直接放行。通过注册配置指定拦截路径和排除路径,代码非常清晰:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/public/**"); } }

3. Vue管理后台与Uniapp移动端的关键实现

3.1 Vue后台的搭建、路由设计与富文本编辑器集成

Vue管理后台我使用的是Vue 3 + Vite + Element Plus + Pinia这套比较现代的组合。Vite比Webpack最大的优势就是启动速度快,改代码热更新几乎无感,这对后台开发体验提升非常明显。

路由设计上,我用了嵌套路由加动态路由的方式。最简单的做法是直接在路由表里配好所有页面组件,然后在router.beforeEach里判断登录状态,未登录就跳转到/login。如果后续你想再加权限细分,可以改成从后端接口获取用户权限码,动态过滤路由并添加,我这次是直接在静态路由表上做的,对个人博客来说够用了。

富文本编辑器的选择上,我起初直接用Element Plus的输入框,结果发现文章排版简直就是灾难。后来换成了wangeditor,这是一款国产开源富文本编辑器,对Vue3有官方支持,集成的代码量很少:

import WangEditor from '@wangeditor/editor-for-vue'; const editorConfig = { placeholder: '请输入内容...', MENU_CONF: { uploadImage: { server: '/api/file/upload', fieldName: 'file', headers: { Authorization: 'Bearer ' + getToken() } } } };

编辑器上传图片的接口通常需要携带Token认证,所以在MENU_CONF里配置请求头很重要,不然图片传不上去接口会直接报401,排查了半天才意识到是这里的问题。

在有移动端的情况下,我强烈建议你在后台发布文章时同时支持正文的HTML格式存储。因为Uniapp端渲染文章详情时,如果内容是纯文本会非常单调,如果内容是HTML可以用rich-text组件直接渲染,省去自己写解析器的麻烦。

3.2 Uniapp跨端开发的环境准备与目录结构规划

Uniapp开发前需要先通过HBuilderX创建一个默认模板项目,或者在命令行用vue create结合@dcloudio/uni-preset-vue模板创建。我用的是HBuilderX,对新手最友好,因为一键就能运行到微信开发者工具或者浏览器,调试效率很高。

创建完项目的目录结构大致是这样的:

├── pages # 页面文件 │ ├── index # 首页(文章列表) │ ├── detail # 文章详情 │ ├── category # 分类页 │ ├── search # 搜索页 │ └── mine # 个人中心 ├── static # 静态资源 ├── utils # 工具函数(请求封装、格式化等) ├── api # 各模块的接口定义 ├── App.vue # 应用入口文件 ├── main.js # 主入口 ├── manifest.json # 应用配置(AppID、权限、SDK配置等) └── pages.json # 页面路由与导航栏配置

pages.json相当于小程序里的app.json,所有页面的路由都要在这里注册,同时可以配置导航栏标题、背景色、tabBar等。一个经常被忽略的配置是globalStyle里的navigationBarTextStyle,如果你导航栏背景是深色,文字必须配成白色,否则会出现黑色文字在黑色背景上完全看不清的情况。

manifest.json的作用在Uniapp里极其重要,它控制着应用在不同平台的差异化配置。比如你要打包成微信小程序,需要在这里配置小程序的AppID;要打包成App,需要配置App图标、启动图、权限声明等。我在配置iOS打包时,就因为没有正确填写Bundle Identifier导致云端打包一直失败,后来才发现是manifest里的AppID和证书不匹配。

3.3 Uniapp请求封装与文章详情页的渲染与视频播放

Uniapp的请求API是uni.request,但它不像Axios那样自带拦截器,所以需要自己封装一层。我封装了一个request.js,统一处理BaseURL、Token注入、响应拦截和错误提示:

const BASE_URL = 'https://api.example.com'; export function 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': uni.getStorageSync('token') ? 'Bearer ' + uni.getStorageSync('token') : '' }, success: (res) => { if (res.data.code === 200) { resolve(res.data.data); } else { uni.showToast({ title: res.data.message, icon: 'none' }); reject(res.data); } }, fail: (err) => { uni.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); }

一个必须提前规划的细节:真机调试时,localhost127.0.0.1指向的是手机本身而不是开发电脑,所以BaseURL必须换成电脑在局域网内的IP地址。我在联调阶段经常遇到“App里请求失败但浏览器正常”的情况,90%都是这个原因。

文章详情页是移动端最核心的页面。正文如果是HTML片段,可以直接用内置的rich-text组件渲染。但如果正文里包含视频(m3u8格式)和图片,处理起来就要多做一步了。m3u8是流媒体常用的索引文件格式,在微信小程序里原生video组件不支持直接播放m3u8,需要通过腾讯云视立方插件或者videojs的H5方案来实现。

我在做这个项目时,考虑到要兼顾H5和小程序,没有引入太重的播放器SDK,而是做了个折中方案:在文章详情页检测到m3u8链接时,H5端使用video.js播放,小程序端则提示用户跳转到浏览器访问。为了识别文章内容中的视频链接,我在后端存储文章时额外维护了一个video_url字段,列表接口返回时直接带上,前端拿到后统一封装成播放器组件:

<template> <view v-if="videoUrl"> <!-- 根据平台条件编译 --> <video v-if="!isH5" :src="videoUrl" controls></video> <videojs-video v-else :src="videoUrl" /> </view> </template>

这样虽然多写了几行平台判断代码,但避免了在小程序端因无法播放m3u8导致整个页面白屏的尴尬局面。

3.4 Uniapp的打包发布流程与自定义功能配置

Uniapp项目做好后,打包发布是绕不开的一步。这里我按目标平台说几个关键注意点。

打包成微信小程序:在HBuilderX中选择“运行到小程序模拟器-微信开发者工具”,前提是先安装微信开发者工具并且开启服务端口。打包上线前,需要在小程序后台配置服务器域名,必须是HTTPS且已备案的域名,否则真机预览时所有请求都会被拦截。我一开始用IP:端口的方式请求后端,小程序里直接报“不在以下request合法域名列表中”,后来切换成HTTPS域名才解决。

打包成App:在HBuilderX中点击“发行-原生App-云打包”,这里需要配置Android和iOS的证书。Android的证书可以用Android Studio自己生成一个签名文件,iOS则需要Apple开发者账号生成描述文件和p12证书。如果你没有Mac电脑,iOS的证书生成确实比较麻烦,但云打包服务支持在Windows上提交打包请求,只是证书部分需要提前准备好。

打包成H5:在HBuilderX中发行到H5后,要特别注意相对路径还是绝对路径的问题。如果你的H5要部署在Nginx的子目录下,需要在manifest.json中配置h5.router.base为对应的子目录路径,否则刷新页面直接404。

关于manifest.json中的权限配置,我用到了定位功能(在公众号H5中获取用户地理位置),需要在App模块配置里勾选Geolocation,同时在小程序后台申请对应的接口权限。这里有一步特别容易漏掉:在微信公众平台里还需要配置JS接口安全域名,否则H5调用定位接口会被微信拦截。

4. 三端联调中的典型问题与排查实录

4.1 跨域问题引发的“接口老是报错”和解决方案

前后端分离架构下,跨域问题几乎是逃不掉的。开发环境下,我用Vue的代理配置解决(Uniapp的H5端也同理):

Vite的vite.config.js里这样配置:

server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

这样前端代码里的/api/xxx请求会被Vite开发服务器转发到后端,浏览器看到的是同源请求,不会触发CORS拦截。

生产环境下,问题就转移到Nginx层了。我的建议是直接在Nginx配置反向代理,把/api开头的请求转发到SpringBoot服务,同时配置好proxy_set_header传递用户IP等头信息:

location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }

如果你不想配置Nginx,也可以在后端加一个CorsFilter,允许指定域名跨域,但从安全角度我不太推荐直接放开所有来源,个人博客可以只允许自己的域名访问。

4.2 真机调试连不上后端,原来是IP和端口惹的祸

这个问题我前面简单提到过,这里详细说一下排查思路。真机调试时,手机和电脑必须在同一个局域网内,然后后端服务的启动地址要监听0.0.0.0,不能只监听127.0.0.1。SpringBoot默认是监听所有网卡的,这点倒没啥问题。

真正的坑在于防火墙。Windows电脑的防火墙默认会拦截外部设备对8080端口的访问,导致手机访问不到。我当时排查了半小时,一度以为是代码写错了,最后发现把防火墙入站规则放行8080端口后,问题瞬间消失。

另外一个小技巧:手机端请求的BaseURL尽量不要写死IP,因为家里的路由器和办公室路由器分配给电脑的IP可能不同。我在request.js里做了一个自动获取逻辑:开发环境下通过uni.getSystemInfo判断平台,H5端直接读当前域名;App端则把BaseURL做成可在“个人中心-设置”里手动修改的配置项,这样换网络环境时不用重新打包。

4.3 文章图片上传后显示不出来,数据库存路径还是存URL

图片上传是博客系统的刚需。最初我在文章表里存的是相对路径/upload/xxx.jpg,结果是后台管理端(Vue)能正常显示,因为后台和图片服务在同一个域名下。但Uniapp端一旦编译成小程序,相对路径就完全失效了,因为小程序没有域的概念,只会把/upload/xxx.jpg当成页面内路径去解析。

正确的做法是:后端上传接口返回完整的图片URL(协议+域名+相对路径),数据库里存的也是完整URL。这样无论哪个端拿到数据后直接赋值给image组件的src属性,都能正常显示。

具体实现时,我封装了一个FileController,上传成功后根据配置的file.base-url拼接出完整URL返回:

String relativePath = "/upload/" + fileName; String fullUrl = fileBaseUrl + relativePath; return Result.success(fullUrl);

这里的fileBaseUrl在开发环境配成http://localhost:8080,生产环境配成https://your-domain.com,不同环境的切换通过SpringBoot的Profile机制实现,省心不少。

4.4 Vue打包后布局异常,Uniapp小白常犯的样式隔离问题

热搜词里有个“vue打包后布局异常”,我正好遇到过一回。原因是后台项目里有个组件的样式写在了非scoped的全局样式中,开发模式下因为组件渲染顺序的问题正好被覆盖,一切正常。打包压缩后CSS文件的合并顺序发生了变化,全局样式优先级覆盖了组件局部样式,页面布局直接乱掉。

排查方法很简单:在浏览器开发者工具里查看元素的最终计算样式,看是哪条CSS规则覆盖了预期样式,然后在代码里显式提高选择器优先级或者补一条!important。但更根本的解决办法是所有组件内样式都加上scoped属性,全局公共样式放到单独的common.css里,按需引入。

Uniapp的样式隔离踩坑更隐蔽:Uniapp的样式是全局共享的,如果你在页面A的<style>里定义了一个.title类,页面B里也可能受影响。建议所有页面级样式都包裹在页面根节点的自定义class下,别用太通用的类名。

5. 部署上线与日常运维的实用建议

5.1 SpringBoot项目打包与服务器部署的完整过程

后端部署我用的是最经典的方案:Maven打包成Jar包 + Systemd守护进程。打包命令很简单,在项目根目录执行mvn clean package -DskipTests,然后会在target目录下生成一个blog-server.jar

服务器上我提前装好了JDK8和MySQL8,然后把Jar包传到服务器某个目录,用Systemd配置成系统服务:

[Unit] Description=Blog Server After=network.target [Service] Type=simple User=root WorkingDirectory=/opt/blog ExecStart=/usr/bin/java -Xms512m -Xmx512m -jar /opt/blog/blog-server.jar Restart=on-failure [Install] WantedBy=multi-user.target

-Xms512m -Xmx512m是我根据博客流量配置的堆内存大小,个人博客512MB完全够用,不用给太多,省得内存被占满影响同服务器上的其他服务。

数据库的备份我用了crontab定时执行的方案,每晚自动导出SQL文件并保留最近7天:

0 2 * * * mysqldump -uroot -p'password' blog > /backup/blog_$(date +\%Y\%m\%d).sql find /backup -name "blog_*.sql" -mtime +7 -delete

这套自动化备份流程看起来很简单,但真到了数据被误删需要恢复的那天,你会感激当时顺手写下的这几行命令。

5.2 Vue和Uniapp的构建产物怎么部署到Nginx

Vue管理后台构建后生成的是静态文件,部署起来最简单。把dist目录里的内容传到服务器的/usr/share/nginx/html/admin目录,然后在Nginx配置里做一个location指向:

location /admin/ { alias /usr/share/nginx/html/admin/; try_files $uri $uri/ /admin/index.html; }

注意try_files那行很关键,否则你在管理后台里刷新某个子路由(比如/admin/article/edit/1)时,Nginx会返回404,因为服务器上并没有这个物理路径对应的文件。

Uniapp打包的H5产物如果也想部署到同一个Nginx下,思路一样,只不过要注意它打包出来的首页文件通常是index.html,并且里面的静态资源路径默认是根路径/static/,如果你把它部署到子目录,就一定要在打包前改掉manifest.json里的base路径。

5.3 H5嵌入微信公众号的定位授权配置备忘

热词里提到了“uniapp开发h5嵌入微信公众号中获取定位”,这个场景我实际做的时候确实遇到了不少坑。用微信内置浏览器打开H5页面时,如果页面需要使用地理位置相关功能,统统要依靠微信JS-SDK的授权流程来完成,而不是直接在页面里调用浏览器的Geolocation API。

整体流程分三步:第一步,后端通过AppID和AppSecret调用微信接口获取access_token;第二步,后端拿着access_token和当前页面的URL去生成JS-SDK所需的签名参数;第三步,前端引入weixin-js-sdk,通过wx.config配置签名信息,然后在wx.ready回调里调用定位接口。

这里有个非常容易踩的雷:签名用的URL必须是当前页面完整的URL(包括协议、域名、路径和查询参数),并且不能做任何encode处理。如果你在使用history路由,微信的签名URL会比hash路由更难处理,因为URL会随着路由变化而变化,每次进入新页面都需要重新算签名。我当时直接把H5定位功能放在了独立的单页里避免这个问题,否则每次路由跳转都要重新请求后端获取签名,体验会很差。

另外记得在微信公众平台的“公众号设置-功能设置-JS接口安全域名”里加上你的H5域名,否则wx.config直接失败,wx.error里会提示“invalid signature”。

6. 个人心得与几个值得留意的细节

整个项目从数据库设计到三端上线,前后花了两周多的业余时间。回头看,这套Uniapp + SpringBoot + Vue的组合对个人博客来说确实是一个非常有性价比的技术方案。Uniapp让移动端覆盖变成了“写一遍到处编译”,SpringBoot让后端开发保持着非常顺畅的节奏,Vue则让管理后台的界面做得既能看又能用。

有几个细节如果你正在做类似项目,可以提前留意一下。

一是数据库字段尽量设计得宽裕一点。比如文章摘要字段,我一开始只给了200字符,结果后来想展示更长的摘要时发现不够用,又要改表结构。这种改动虽然不复杂,但在生产环境涉及到已有数据的转换,还是很麻烦的。

二是接口的返回时间格式一定要统一。我在最初几个接口里直接在实体类上用了@JsonFormat注解,后来才发现有的接口返回的是时间戳,有的返回的是格式化后的字符串,前端对时间做排序和显示时逻辑非常混乱。后面我统一在配置文件里设置全局格式,并且规定所有接口一律返回yyyy-MM-dd HH:mm:ss格式的字符串,这个问题才彻底解决。

三是如果你也用Uniapp,一定要尽早规划条件编译的使用场景。Uniapp支持在代码中通过注释条件编译来区分平台,比如#ifdef H5#ifndef MP-WEIXIN等。我在做分享功能时,H5端和小程序端的分享API完全不同,如果没有条件编译,代码会写得很丑陋。用上条件编译后,每个平台只保留自己需要的逻辑,可读性大大提升。

四是流量统计别忽略了。在文章详情页加入浏览量计数功能时,我只在进入页面时调了一次后端接口累加view_count,后来发现刷一次就算一次,数据太虚。改进方案是通过后端接口判断同一个IP在5分钟内只算一次浏览,虽然实现稍微复杂了一点,但统计结果可靠很多。

这个项目后续如果继续扩展,我准备加上文章的全站搜索(目前只有标题模糊搜索)、文章的定时发布功能,以及通过邮件发送评论回复通知。这些都是在不改变现有架构的情况下就能做的增量功能,等做完后再找机会把完整的扩展经验整理出来分享给大家。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询