拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java课设名片管理系统:Spring Boot + 微信小程序从数据库到接口全解析

Java课设名片管理系统:Spring Boot + 微信小程序从数据库到接口全解析

简介:这是一份基于微信小程序与Java后台的名片管理系统完整项目包,面向正在准备课程设计或毕业设计的学生,也适合希望快速掌握小程序前后端联动开发的初学者。系统不仅包含小程序端完整页面,还提供了SSM/SpringBoot风格的后台逻辑、数据库脚本以及配套教程,整体经过严格调试,能够开箱即用。压缩包内共有七百三十六份文件,整体大小约十一点二兆,其中包含七十一个Java源文件、一百零三个JS脚本、三十六个WXSS样式表、二十八个WXML页面结构、九十五个XML配置文件、一个SQL初始化脚本,同时附带大量PNG、JPG图片和HTML页面等前端素材,目录区分清晰,方便按模块查阅。目前该项目已有两百人学习下载,常见业务模块如客户名片、联系人管理、新闻通知、留言板等均有具体实现,且支持二次开发。下载后可直接导入微信开发者工具和IDEA等环境中运行,也可作为课设或毕设的代码基线,能够显著节省从零搭建项目的时间与精力。

1. 名片管理系统这个 Java 课设:不只是“存个电话”那么简单

你参加过线下展会或者商务对接会吗?一天收二十张纸质名片,回到工位往抽屉一丢,等人真需要联系的时候,翻半天还找不着,只能拍照存手机相册,最后通讯录里多了一堆只有电话号码的陌生记录。用微信小程序做名片管理系统,本质就是把“收名片 - 存名片 - 找名片 - 交换名片”这条链路搬到线上:小程序端负责扫码录入、手动添加、分组管理和检索,Java 后端提供接口服务,MySQL 负责持久化存储。这不是一个只能交作业的玩具项目,它完整覆盖了前端展示、后端接口、数据库设计、微信登录鉴权四条线,非常适合当作 Java 课程设计、毕业设计或者练手项目。全文从源码落地出发,讲清楚这个项目怎么拆解、怎么跑通、怎么避坑。

2. 从数据库到接口:先把名片数据的地基打好

2.1 名片系统的 4 张核心表:字段怎么定、外键怎么连

不管界面做得多好看,一个名片管理系统的根都在数据库表设计上。常见的做法是拆成用户表、名片信息表、分组表、访问记录表这四张核心表,彼此通过外键关联。第一次做这个项目的人容易犯的错误是把名片字段全部塞进一张大表里,头像、地址、公司备注全堆在 card 表上,看起来简单,后期扩展分组、统计访问记录时就要返工。

我一般建议先建t_user、t_card、t_group、t_visit_log四张表。t_user存微信用户的基本信息,openid是唯一标识;t_card存名片内容,user_id指向所属用户;t_group是分组表,允许用户把名片按“客户”“供应商”“合作伙伴”归类,一张名片只能属于一个用户下的一个分组;t_visit_log记录名片被查看、被收藏、被转发的动作,方便后续做数据分析。

下面是建表的 MySQL 参考脚本,字段类型和注释已按线上项目习惯补充:

CREATE TABLE `t_user` ( `id` INT NOT NULL AUTO_INCREMENT, `openid` VARCHAR(64) NOT NULL COMMENT '微信openid,唯一', `nickname` VARCHAR(64) DEFAULT '' COMMENT '昵称', `avatar` VARCHAR(255) DEFAULT '' COMMENT '头像URL', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小程序用户表'; CREATE TABLE `t_group` ( `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL, `group_name` VARCHAR(32) NOT NULL COMMENT '分组名', PRIMARY KEY (`id`), KEY `idx_user` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分组表'; CREATE TABLE `t_card` ( `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL COMMENT '所属用户', `group_id` INT DEFAULT NULL COMMENT '所属分组', `name` VARCHAR(32) NOT NULL COMMENT '姓名', `company` VARCHAR(128) DEFAULT '' COMMENT '公司', `position` VARCHAR(64) DEFAULT '' COMMENT '职位', `phone` VARCHAR(32) DEFAULT '' COMMENT '手机号', `email` VARCHAR(64) DEFAULT '' COMMENT '邮箱', `address` VARCHAR(255) DEFAULT '' COMMENT '地址', `remark` VARCHAR(255) DEFAULT '' COMMENT '备注', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_group` (`user_id`,`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='名片表';

这里有一个关键参数说明:手机号字段不要用INT,要用VARCHAR(32)。原因很直接,INT存不了带加号的国际区号,比如+86,遇到座机号码带分机号的也存不下;另外手机号前端传过来可能带空格、横杠,用字符串类型处理起来省心得多。t_card表上的联合索引idx_user_group是给“按用户查分组下的名片”这条高频查询路径准备的,加上和没加在数据量到几千条以后差距非常明显。

2.2 DAO 层选择:JDBC、MyBatis 还是 Spring Data JPA

数据库表建好后要选数据访问层方案。课程设计最常见的组合是 Spring Boot + MyBatis,也有老项目用 JDBC 原生写或 Spring MVC + Hibernate。我的建议是:如果这个项目要作为毕设或者作品集展示,优先用 Spring Boot + MyBatis;如果只想最快跑通,原生 JDBC 也够用,后面换框架成本不高,因为核心业务逻辑在 Service 层,DAO 层只是替换实现。

以登录查询为例,用 MyBatis 的 Mapper 接口非常精简:

@Mapper public interface UserMapper { @Select("SELECT * FROM t_user WHERE openid = #{openid}") User findByOpenid(String openid); @Insert("INSERT INTO t_user(openid, nickname, avatar) " + "VALUES(#{openid}, #{nickname}, #{avatar})") @Options(useGeneratedKeys = true, keyProperty = "id") int insert(User user); }

逻辑说明:findByOpenid先查用户是否存在,微信登录时如果查不到就执行insert创建用户。@Options(useGeneratedKeys = true, keyProperty = "id")很关键,它让 MySQL 自增主键回填到 Java 对象的id属性上,这样后续业务可以直接用user.getId()往下走,不用再查一次。如果省略这行注解,插入成功后拿不到主键,你在 Service 层写“创建默认分组”的逻辑就会很别扭。

选择 MyBatis 而不是直接 JDBC 的理由是:减少样板代码、参数映射自动完成、SQL 和业务代码分离。对于名片管理这种以增删改查为主的系统,MyBatis 几乎没有学习曲线。而如果追求轻量、不想引入 Spring Boot 全家桶,JDBC 也没问题,代价是每条 SQL 都要手写PreparedStatement的参数绑定和结果集映射,代码量大约多三倍。

2.3 三层架构的接口约定:响应体、状态码、分页参数

数据库和 DAO 层确定后,需要先把接口层的“统一响应格式”定下来。前后端分离开发最怕各写各的,接口返回一会儿是{data: []},一会儿是{result: {list: []}},小程序端封装 request 时根本没法收敛。我一般固定用三字段响应体:code表示状态码,message给前端 toast 提示用,data放真正的业务数据。

public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> ok(T data) { Result<T> r = new Result<>(); r.code = 200; r.message = "success"; r.data = data; return r; } }

参数约定是关键:前端拿到code == 200才继续处理data;否则弹message内容。不要只在成功时返回前端需要的字段,失败时也得返回可读的提示信息,不然小程序端整天在“接口不通但控制台也没报错”的状态里翻车,排查全靠猜。

分页参数建议统一用pageNum和pageSize,pageNum从 1 开始。很多新手把 pageNum 从 0 开始,接口里面写LIMIT #{pageNum}, #{pageSize},前端分页组件传 0、1、2,后面对齐全靠大脑记忆,迟早要乱。我习惯在后端统一处理:

pageNum = Math.max(pageNum, 1); int offset = (pageNum - 1) * pageSize;

这样不管前端传 0 还是传 1,后端都按“第一页从 1 开始”的规则执行。分页返回结构也用固定对象:{list: [], total: 0, pageNum: 1, pageSize: 10},total在前端用来算总分页数,必须由后端的COUNT(*)查询得到,不要用list.size()糊弄。

3. 小程序端调用 Java 接口:登录、列表、收藏一次打通

3.1 wx.request 与 Java 后端对接:URL 设计和公共请求封装

小程序端与后端交互的核心 API 是wx.request,它相当于浏览器的XMLHttpRequest,但有几个固有差异:域名必须是 HTTPS 且在小程序管理后台配置合法域名(开发时可勾选“不校验合法域名”临时解决)、请求头需要显式设置Content-Type、并发请求数量有限制。我见过很多新手在页面里直接写wx.request,每个页面复制粘贴一份,改接口地址要全文搜索替换,这是最典型的翻车现场。

解决方式是封装一个公共的request方法,统一管理基础路径、token 注入、超时时间和错误提示:

const BASE_URL = 'http://localhost:8080/api'; function request(path, method, data) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method: method || 'GET', data: data || {}, timeout: 10000, header: { 'Content-Type': 'application/json', 'token': wx.getStorageSync('token') || '' }, success(res) { if (res.statusCode === 200 && res.data.code === 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.message || '请求失败', icon: 'none' }); reject(res.data); } }, fail(err) { wx.showToast({ title: '网络异常,请检查后端服务', icon: 'none' }); reject(err); } }); }); } module.exports = { request, BASE_URL };

逻辑说明:统一在header中注入 token,后端拦截器可以从请求头里解析用户身份;timeout: 10000是经验值,名片列表接口在正常网络下 1 秒内能返回,但图片较多的场景需要预留时间。参数说明:BASE_URL在本地开发时用局域网 IP,比如http://192.168.1.100:8080/api,真机预览时必须改成电脑的局域网地址,不能填localhost,因为手机访问的是电脑。这个坑后文会展开讲。请求失败时的wx.showToast是给用户的直接反馈,比默默 reject 体验好得多。

3.2 微信登录换 openid:核心步骤与后端校验

名片系统必须知道“当前操作的是谁”,否则用户 A 添加的名片会被用户 B 看到。微信小程序推荐的登录流程是:前端wx.login拿临时code,发送到后端,后端拿着code调微信接口换取openid和session_key,然后生成自定义登录态(token)返回给前端。

前端代码的核心部分如下:

function login() { return new Promise((resolve, reject) => { wx.login({ success: async (res) => { if (res.code) { const data = await request('/auth/login', 'POST', { code: res.code }); wx.setStorageSync('token', data.token); resolve(data); } else { reject(new Error('微信登录失败')); } } }); }); }

这段代码就是拿res.code换 token 的标准写法。登录成功后把 token 存到本地Storage,后续每次请求自动带上。这里需要说明:后端不能直接拿前端传来的openid当登录凭证,因为小程序端属于不可信环境,伪造openid的成本很低。正确做法是后端用code调微信接口换取openid,这个接口是https://api.weixin.qq.com/sns/jscode2session,需要传入小程序的appid和secret。如果用 Spring Boot,后端代码一般是:

@PostMapping("/auth/login") public Result<Map<String, Object>> login(@RequestBody LoginRequest req) { String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appid + "&secret=" + secret + "&js_code=" + req.getCode() + "&grant_type=authorization_code"; // 用 HttpClient 或 RestTemplate 请求该接口 // 获取 openid 后查 t_user,不存在则插入,再生成 token 返回 }

这块要注意:appid和secret不要硬编码在前端代码里,至少放到后端配置文件,或者通过配置中心管理,否则小程序包被反编译后密钥直接泄露。token 生成一般用 UUID 或 JWT,存入 Redis 或数据库;如果只是课设演示,简单点用 UUID 存数据库也能接受,但要给 token 加上有效期逻辑。

3.3 名片列表页与搜索:下拉刷新、筛选和空态处理

登录打通后,核心页面是名片列表。列表页要展示名片姓名、公司、职位、头像,支持按姓名或公司关键字搜索,支持下拉刷新和上拉加载更多。这里要特别注意微信小程序的渲染特性:setData更新数据是异步的,而且频繁setData会导致页面卡顿,尤其是名片列表动辄几十条数据时,不要一次性把所有字段塞进setData。

列表页的关键代码片段:

Page({ data: { list: [], pageNum: 1, pageSize: 10, keyword: '', loading: false, finished: false }, onPullDownRefresh() { this.setData({ pageNum: 1, finished: false, list: [] }); this.fetchList().finally(() => wx.stopPullDownRefresh()); }, onReachBottom() { if (!this.data.finished && !this.data.loading) { this.setData({ pageNum: this.data.pageNum + 1 }); this.fetchList(); } }, async fetchList() { this.setData({ loading: true }); try { const res = await request('/card/list', 'GET', { pageNum: this.data.pageNum, pageSize: this.data.pageSize, keyword: this.data.keyword }); this.setData({ list: this.data.pageNum === 1 ? res.list : this.data.list.concat(res.list), finished: this.data.list.length >= res.total }); } finally { this.setData({ loading: false }); } } });

逻辑说明:onPullDownRefresh重置分页参数后重新拉取第一页数据;onReachBottom触发加载下一页,finished字段控制是否还能继续加载;列表数据用concat拼接而不是覆盖,避免滚动位置丢失。这里有一个参数细节:finished不要用pageNum >= totalPage判断,因为totalPage需要额外计算,直接用list.length >= total更可靠:已经加载的数据条数不小于总数时,必然没有更多了。

搜索框单独讲一下:bindinput事件每输入一个字符都会触发,每次触发都去请求接口会打出大量无效请求,后端被刷爆不说,前端还会出现“最后一次请求先返回、前面的请求后返回”的错乱。常见做法是加 300ms 防抖:

onSearchInput(e) { clearTimeout(this._timer); this._timer = setTimeout(() => { this.setData({ keyword: e.detail.value, pageNum: 1, list: [] }); this.fetchList(); }, 300); }

这个 300 毫秒是经验值,太短防抖没效果,太长搜索有延迟感。记忆搜索关键字用Storage存一份,用户离开页面再回来能恢复搜索状态,体验会更像原生 App。

4. 把项目跑起来:源码导入、建库改配置、启动验证

4.1 本地环境:JDK、Tomcat、MySQL 这三件套的版本搭配

拿到这个项目的源码 zip 后,第一步不是急着打开 IDE,而是先检查本地环境是否匹配。如果源码用 Spring Boot 3.x 编写,那要求 JDK 17 起步;用 Spring Boot 2.x 的话 JDK 8 就行。Tomcat 版本同理:Spring Boot 内嵌的 Tomcat 不受外置 Tomcat 版本影响,但如果项目是传统的 WAR 包部署方式,Tomcat 8.5 和 Tomcat 10 对javax.servlet与jakarta.servlet的包名要求完全不同,直接决定你能不能跑起来。

我归纳了一张版本对照表,导入项目前先核对:

项目版本JDK 要求外部 TomcatMySQL备注
Spring Boot 2.xJDK 8/11不需要(内嵌)MySQL 5.7 / 8.0最常见,教程资料最多
Spring Boot 3.xJDK 17不需要(内嵌)MySQL 8.0新项目,注意 javax 改 jakarta
传统 SSM + WARJDK 8Tomcat 8.5/9MySQL 5.7老课设常见,需部署 war 包

版本不匹配的现象很典型:项目导入后大量红叉,或启动瞬间报java.lang.NoClassDefFoundError。看到这类报错先别急着改代码,检查 JDK 编译版本和依赖版本比改代码有效得多。MySQL 8.0 与 5.7 的差异集中在连接驱动和时区设置上,8.0 的连接 URL 需要显式加serverTimezone=Asia/Shanghai,不然 JDBC 驱动会把你当地的时区当成 UTC,时间字段全部差 8 小时,这种 bug 非常隐蔽。

4.2 导入源码后先改这 4 个文件

源码导入 IDE 后,先别急着按运行按钮,按下面这个顺序改文件,能省掉后面一大半排查时间。这 4 个文件是几乎所有 Java 课设项目的必改项:

第一个:application.yml或application.properties

数据库连接配置、Redis 配置、端口号都在这里:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/card_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 20MB

重点说明:useUnicode=true&characterEncoding=utf8必须写,否则中文名乱码、模糊搜索“张”返回空结果时,你还以为是自己 SQL 写错了。serverTimezone在 MySQL 8.0 下必须设置,不然报The server time zone value错误。密码改成自己本地的数据库密码,端口冲突时换掉 8080。

第二个:数据库初始化脚本

源码包里一般会附带card.sql或init.sql,用 Navicat 或命令行执行:

mysql -u root -p123456 < /path/to/card.sql

执行完检查一下表是否完整,用SHOW TABLES;看看是不是有 4 张核心表。

第三个:小程序端app.js或config.js里的BASE_URL

上一章封装的request方法里,BASE_URL默认指向localhost:8080,真机调试时必须改成电脑的局域网 IP。获取方法:Windows 下ipconfig,macOS 下ifconfig,找到无线网卡对应的 IPv4 地址。同时注意微信开发者工具的“不校验合法域名”选项必须勾上,否则真机预览时请求被拦截,页面一片空白。

第四个:图片上传路径

名片系统通常支持上传头像或名片照片,后端一般有个upload接口,保存路径写死成D:/upload或者/Users/xxx/upload。你没改就启动,上传功能必挂:

upload: path: /Users/你的用户名/upload # 改成你自己的目录,并且要先建好这个文件夹

为什么不自动创建?很多项目只写了file.transferTo(new File(path)),但没补File.mkdirs(),父目录不存在时就抛FileNotFoundException。如果你不想改代码,就手动创建这个目录再启动。

4.3 启动与验证:从控制台日志到 Postman 实测接口

改完配置后启动 Spring Boot 项目,正常启动的控制台日志最后几行类似:

Tomcat started on port(s): 8080 (http) with context path '' Started CardApplication in 3.2 seconds

如果看到“APPLICATION FAILED TO START”或“Error creating bean with name”,说明 Spring 容器初始化失败,重点查数据库连接和依赖注入相关的日志行。启动成功不代表接口没问题,先用 Postman 或 curl 验证一下免登录的接口,再验证需要登录的接口:

# 验证后端健康状态 curl -X GET http://localhost:8080/api/health # 验证名片列表接口(假设登录后才能访问) curl -X GET http://localhost:8080/api/card/list?pageNum=1&pageSize=10 \ -H "token: 你拿到的token"

如果健康检查接口返回 200 和统一响应格式,说明 Spring MVC 正常;名片列表接口报 401,说明拦截器生效,你需要先在微信开发者工具里跑一遍登录流程拿到 token。这条命令本身也是一种快速排查手段:接口通不通、拦截器有没有放行、token 有没有传对,一条指令就能看出结果。

5. 避坑:名片管理系统跑不通的 5 个经典翻车现场

5.1 启动报 ClassNotFoundException / NoClassDefFoundError

现象:Spring Boot 启动时报错,提示找不到某个类,最常见的是com.mysql.cj.jdbc.Driver或org.springframework.jdbc.core.JdbcTemplate。

原因:pom.xml里漏了依赖,或者依赖版本不匹配。MySQL 8.0 驱动的类名是com.mysql.cj.jdbc.Driver,老版本驱动是com.mysql.jdbc.Driver,如果代码里写旧类名 + 新驱动,同样报ClassNotFoundException。还可能因为你改了 JDK 版本,导致部分依赖的 jar 没有被重新编译。

解决:先检查pom.xml是否引入mysql-connector-java或mysql-connector-j,版本号和本地 MySQL 版本匹配;再执行mvn clean compile重新编译,刷新 IDE 的 Maven 项目索引,确认依赖全部下载完整。如果公司内网环境下载不了 Maven 依赖,检查 Maven 镜像配置,换成阿里云镜像再试。

5.2 小程序真机预览请求不到接口,模拟器却正常

现象:在微信开发者工具里,接口请求正常,数据都显示;一换成真机预览,页面转圈,接口全部失败,控制台报request:fail。

原因:真机上的小程序访问localhost指向手机自身,而不是电脑。模拟器因为跑在电脑上,localhost能访问你本机的后端;手机离开电脑独立运行,自然找不到后端服务。另外一个常见原因是手机和电脑不在同一网段:手机连的 Wi-Fi 网络跟电脑不是同一个路由器。

解决:把BASE_URL从localhost改成电脑的局域网 IP,比如http://192.168.31.45:8080/api。然后检查 Windows 防火墙是否拦截了 8080 端口,入站规则要放行 Java 进程的端口。真机预览时微信开发者工具也要开启“不校验合法域名”选项。手机和电脑连到同一个 Wi-Fi,然后用手机访问http://192.168.31.45:8080/api/health,浏览器能通就说明网络链路没问题。

5.3 数据库中文全部乱码,表单提交的名字显示为问号

现象:添加名片时填的“张三”,列表页显示“????”。数据库里直接查SELECT * FROM t_card;,name字段也是问号。

原因:数据库连接串的字符集不对,或表本身建表时用了latin1。连接串里characterEncoding=utf8管的是 JDBC 传输层的编码,如果表结构本身是latin1,数据直接以错误编码写入,读取时就无法还原。还有的乱码出现在 HTTP 层,Spring Boot 的server.servlet.encoding.force=true没配好。

解决:改两个位置。第一,建表语句统一加DEFAULT CHARSET=utf8mb4,之前建错的表用ALTER TABLE t_card CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;转换。第二,连接串里写全useUnicode=true&characterEncoding=utf8。如果前端传过来的名字在 Network 面板就是乱码,那是前端 JS 文件编码问题,把app.js保存为 UTF-8 编码即可。

5.4 openid 获取失败,登录接口报 invalid code

现象:小程序点击登录,后端日志提示invalid code或errcode 40029,登录失败。有时昨天还好好的,今天就挂了。

原因:wx.login返回的code是一次性的,使用过一次后立即失效。如果前端拿到 code 后调用了两次登录接口(比如一次在App.onLaunch,一次在页面onLoad),后一次必然报错。另一个常见原因是后端用了测试号或 appid 与 secret 不匹配,尤其是下载的源码里自带一个 appid,你没换成自己的小程序 appid。

解决:检查小程序管理后台的 appid 和 secret 是否与项目配置一致。前端代码里保证wx.login的code只被使用一次,进入页面时做一个标记,已登录则不再触发登录流程。后端也做一层保护,同一个 code 最多消费一次,避免接口被重放。如果只是本地调试,可以在后端写一个临时免登录逻辑,用前端传的openid直接登录,但正式上线前必须移除。

5.5 图片上传成功,页面却显示不出来,头像一直是灰色的

现象:上传头像接口返回成功,数据库里也存了 URL,但小程序页面<image>组件加载图片失败,一片空白或灰色占位。

原因:绝大多数情况是图片 URL 用了本地绝对路径,比如http://localhost:8080/upload/avatar.png。小程序真机访问这个地址时指向手机自己,和 5.2 的坑同源。还有一种情况是后端配置了静态资源映射/upload/**但没生效,或者上传目录里没找到对应文件。

解决:图片 URL 的域名部分和小程序请求接口的BASE_URL保持一致,统一改成局域网 IP。如果后端把图片存到了项目外的目录,需要配置静态资源映射,Spring Boot 里常见写法:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadPath + "/"); } }

这里的uploadPath必须带结尾斜杠,否则映射不生效。改完重启后端,再手动访问http://局域网IP:8080/upload/xxx.png看能不能直接打开,这能区分是映射问题还是小程序显示问题。

6. 进阶玩法:把名片系统从“能用”做成“好用”

核心功能跑通后,想让它变得真正可展示、可上线,我强烈建议按三个方向做增强:数据有效性、分享传播、性能优化。

数据有效性方向:给t_card表的phone字段加唯一索引吗?不行,因为不同用户可以录入同一个人的名片,错误地加唯一索引会导致录入失败。正确做法是为“同一个用户下的手机号”做去重校验,在后端 Service 层加一段逻辑:插入前查一次SELECT COUNT(*) FROM t_card WHERE user_id = ? AND phone = ?,存在则拒绝并提示“该名片已存在”。这一步防的是手动录入时重复存同一人的名片,是很常见的一个翻车点。

分享传播方向:微信小程序的button组件可以设置open-type="share",但系统默认分享卡片只带标题和图片,不带名片详情。进阶做法是用wx.showShareMenu开启分享,在onShareAppMessage里配上名片链接参数;接收方打开小程序后在onLoad里解析options中的 cardId,直接展示名片详情并提供“存入我的名片夹”按钮。注意分享页面的path要写成pages/card/detail?cardId=xxx格式,不然对方打开是首页而不是名片详情页。

性能优化方向:如果把名片列表的keyword搜索直接写成LIKE '%keyword%',在数据量过千后性能会明显下降。常见方案是给name和company字段加前缀索引,或者引入 Elasticsearch 做全文检索。对于几千条数据量级,更简单有效的做法是后端加一层本地缓存:支持按分组、按姓名首字母快速筛选,前端把常用的首屏数据缓存在 Storage 里,设置 10 分钟的缓存时间。微信小程序的Storage是本地缓存,读取比请求网络快一个数量级,结合页面骨架屏,几乎能做到秒开;代价是数据实时性变差,名片被修改后用户可能看到旧数据,所以缓存策略要带上“下拉刷新强制更新”的逻辑。

最后说一下我自己的血泪经验:我曾经给别人的名片系统加“批量导入”功能,用户上传一个 Excel 文件,后端用 POI 解析后批量入库。原计划半小时搞定,实际做了一天半,因为 Excel 里的手机号有科学计数法、有空格、有带引号的文本格式,清洗数据花的时间远超过导入本身。如果你的系统也要做导入导出,永远记住一句话:解析 Excel 前先统一字段格式,比解析后写一堆 if 判空靠谱得多。这个项目本身链路完整,从微信登录到数据库落库再到列表展示,把它吃透,Java 后端和小程序前端的基础都能打得很扎实,希望帮到你。

本文还有配套的精品资源,点击获取

返回列表