简介:这是一套面向计算机专业本科生课程设计与毕业设计的微信小程序名片管理系统完整开发资源,采用Java后端+小程序前端技术栈,解决个人或小型团队电子名片统一管理、信息快速共享与后台集中维护的实际需求。资源包共736个文件,涵盖71个Java业务逻辑类、103个JS交互脚本、95个XML配置与映射文件、71个PNG图标资源、42个CSS/WXSS样式文件及1个核心SQL数据库脚本,完整呈现SSM/SpringBoot架构下的前后端协同实现;压缩包仅11.2MB,轻量易部署。已有200人学习下载,资源经严格调试,含Navicat数据库可视化操作指引、Tomcat 7.x/8.x部署说明及微信开发者工具适配要点。读者可直接获取可运行的全栈源码、模块化清晰的控制器层(如KehumingpianInfoController、LianxirenmingpianInfoController等)、配套MySQL 5.7兼容脚本及完整项目结构文档,大幅降低二次开发与教学演示门槛。
1. 微信小程序名片管理系统:为什么用 Java 做后端,而不是 Node.js 或 Python?
你手头这个「微信小程序-基于小程序的名片管理系统(Java)」压缩包,不是玩具 Demo,而是一套能直接跑通、带完整数据库结构、含前后端交互逻辑、有用户登录+名片增删改查+搜索+分类导出功能的真实轻量级 SaaS 架构雏形。它解决的是中小团队/自由职业者最痛的场景:客户微信一加就是几百人,但翻聊天记录找电话、查公司、看上次沟通时间,平均耗时 47 秒——而一个带搜索栏+标签筛选+一键拨号的小程序,3 秒内完成。关键在于,它没用 Node.js 做后端(虽然开发快),也没选 Python(虽然 AI 热),而是用 Java 搭 Spring Boot + MyBatis + MySQL ——这不是怀旧,是权衡:Java 的线程模型更稳扛住微信高频扫码添加后的并发写入;MyBatis 对接微信小程序 wx.request 的 JSON 结构天然友好;更重要的是,国内企业级 Java 开发者基数大、面试题库成熟(比如“Spring 事务失效的 7 种场景”)、运维工具链(Arthas、JProfiler)开箱即用。如果你正准备 Java 面试题、想补全全栈闭环、或需要一个能塞进公司内网部署的名片管理原型,这套源码不是“能跑就行”,而是“上线前只需改三处配置”。
2. 后端服务搭建:从解压到接口可调用的最小闭环
这套系统后端是标准 Spring Boot 2.7.x(非 Spring Boot 3.x,注意 JDK 版本兼容性),Maven 工程结构清晰。核心不在“多高级”,而在“每一步都可验证”。下面带你走通本地启动 → 数据库初始化 → 接口联调的最小闭环。
2.1 解压与环境确认:别跳过这三行命令
先确认你的本地环境满足硬性条件:
# 检查 JDK 版本(必须为 8 或 11,Spring Boot 2.7.x 不支持 JDK 17+) java -version # 输出应类似:openjdk version "11.0.20" 2023-07-18 # 检查 Maven 版本(≥3.6.3) mvn -v # 输出应含 Apache Maven 3.6.3 或更高 # 检查 MySQL 是否运行(默认端口 3306) mysqladmin -u root -p status提示:如果 MySQL 密码为空,
-p后直接回车;若报Can't connect to local MySQL server,请先启动 MySQL 服务(Windows 用服务管理器,macOS/Linux 用brew services start mysql或sudo systemctl start mysql)。
解压 ZIP 后,进入backend目录(或类似命名的 Java 工程文件夹),你会看到标准 Maven 结构:src/main/java下是com.example.card包,src/main/resources下有application.yml和mapperXML 文件。不要急着mvn clean install—— 先改配置。
2.2 application.yml 关键三处修改:数据库、微信 AppID、端口
打开src/main/resources/application.yml,重点修改以下三块(其他字段可暂不碰):
# 1. 数据库连接(按你本地实际配置填) spring: datasource: url: jdbc:mysql://localhost:3306/card_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: your_mysql_password # ← 这里填你 MySQL 的密码 driver-class-name: com.mysql.cj.jdbc.Driver # 2. 微信小程序配置(必须填!否则登录失败) wechat: appid: wx1234567890abcdef # ← 替换为你在微信公众平台申请的小程序 AppID secret: your_app_secret_here # ← 替换为对应 AppSecret(在开发管理页获取) # 3. 服务端口(避免和前端开发服务器冲突) server: port: 8081 # ← 建议改为 8081,因微信开发者工具默认占 8080注意:
url中的card_db是数据库名,不是表名;serverTimezone=Asia/Shanghai必须加上,否则 MySQL 8.0+ 会报时区错误;driver-class-name若用 MySQL 5.7 可改为com.mysql.jdbc.Driver,但推荐统一用com.mysql.cj.jdbc.Driver(Connector/J 8.x)。
2.3 初始化数据库:执行 SQL 脚本而非手动建表
压缩包中database文件夹下一定包含card_db.sql(或类似名称的 SQL 文件)。不要用 Navicat 或 DBeaver 手动建表——字段类型、索引、外键、初始数据(如管理员账号)都在脚本里。用命令行执行最稳:
# 登录 MySQL mysql -u root -p # 创建数据库(注意字符集必须为 utf8mb4,支持微信昵称里的 emoji) CREATE DATABASE card_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 退出 MySQL 客户端 exit # 执行建表与初始化数据(在 terminal 当前目录为 database 文件夹) mysql -u root -p card_db < card_db.sql执行成功后,进入 MySQL 查验:
USE card_db; SHOW TABLES; -- 应看到至少 4 张表:user(用户)、card(名片)、category(分类)、user_card_relation(用户-名片关系) SELECT * FROM user LIMIT 1; -- 应返回一条初始管理员记录,username 通常为 'admin',password 是加密后的字符串(如 $2a$10$...)2.4 启动后端并验证接口:用 curl 测试登录接口
回到backend目录,执行:
mvn spring-boot:run等待控制台输出Tomcat started on port(s): 8081,说明服务已就绪。此时打开新终端,用 curl 测试最基础的登录接口(该接口不校验微信 session_key,仅测通路):
curl -X POST http://localhost:8081/api/user/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'预期返回:
{ "code": 200, "msg": "登录成功", "data": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "userInfo": { "id": 1, "username": "admin", "nickname": "系统管理员" } } }逻辑说明:
/api/user/login是后端提供的模拟登录入口(非微信授权登录),用于前端调试。真实生产环境会替换为/api/user/wx-login,接收小程序wx.login()返回的code,再向微信接口https://api.weixin.qq.com/sns/jscode2session换取openid。当前脚本已预留该接口,但需填对appid和secret才能生效。
3. 小程序前端对接:从 project.config.json 到 wx.request 成功
微信小程序前端代码在frontend(或miniprogram)目录下,是标准 WXML + WXSS + JS 结构。它不依赖任何框架(如 Taro、UniApp),纯原生开发,意味着你改一行app.js就能立刻看到效果,也意味着所有网络请求都得自己封装。
3.1 修改 project.config.json:指向你的后端地址
打开frontend/project.config.json,找到"description"下方的"setting"节点,添加或修改:
"setting": { "urlCheck": false, // 关键!关闭域名合法性检查,否则本地调试报 request domain not configured "es6": true, "enhance": true, "postcss": true, "preloadBackgroundData": false, "minified": false, "newFeature": true, "coverView": true, "nodeModules": false, "autoAudits": false, "showShadowRootInWxmlPanel": true, "scopeDataPrivacyStatement": false },然后,在app.js或utils/request.js中,确认基础 URL 已改为你的后端地址:
// utils/request.js 中的 baseURL const BASE_URL = 'http://localhost:8081'; // ← 必须和后端 server.port 一致 // 登录请求示例(app.js onLaunch 中调用) wx.request({ url: `${BASE_URL}/api/user/login`, method: 'POST', data: { username: 'admin', password: '123456' }, success(res) { if (res.data.code === 200) { wx.setStorageSync('token', res.data.data.token); console.log('登录成功,token 已存'); } } });参数说明:
urlCheck: false是本地调试的生命线,微信开发者工具默认强制校验request合法域名,但本地localhost不在白名单内,关掉它才能发起请求;BASE_URL必须带协议(http://)和端口(:8081),漏掉任一都会导致net::ERR_CONNECTION_REFUSED。
3.2 页面列表加载更多:实现名片分页的核心逻辑
名片列表页(如pages/card/list/list.js)是高频操作页面。源码中已实现“上拉加载更多”,但默认是假数据。要连真实后端,需改两处:
- 请求参数改造:将
page和size传给后端
// pages/card/list/list.js 中 onLoad 和 onReachBottom 方法 onLoad() { this.pageNum = 1; this.pageSize = 10; this.loadCards(); }, onReachBottom() { this.pageNum++; this.loadCards(); }, loadCards() { wx.request({ url: `${BASE_URL}/api/card/list`, data: { page: this.pageNum, size: this.pageSize, keyword: this.data.searchKeyword || '' // 支持搜索后继续加载 }, success: (res) => { const newCards = res.data.data.list || []; this.setData({ cards: this.pageNum === 1 ? newCards : [...this.data.cards, ...newCards], hasMore: res.data.data.hasNext // 后端返回是否还有下一页 }); } }); }- 后端分页接口验证:确保
CardController.java中list方法接收page和size并返回PageInfo<Card>(MyBatis-Plus 分页插件已集成):
@GetMapping("/list") public Result list(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) String keyword) { Page<Card> cardPage = new Page<>(page, size); QueryWrapper<Card> wrapper = new QueryWrapper<>(); if (StringUtils.isNotBlank(keyword)) { wrapper.like("name", keyword).or().like("company", keyword); } Page<Card> result = cardService.page(cardPage, wrapper); return Result.success(result); // 返回含 total、list、pageNum 等字段的 JSON }逻辑说明:
@RequestParam(defaultValue = "1")让前端不传page时默认第 1 页;Page<Card>是 MyBatis-Plus 的分页对象,自动注入total总数,前端用result.total > (page * size)判断是否显示“加载更多”按钮;wrapper.like(...).or().like(...)实现姓名/公司双字段模糊搜索,比LIKE '%keyword%'更安全(防 SQL 注入)。
3.3 微信小程序顶部导航栏高度:适配不同机型的硬编码陷阱
源码中app.json的window.navigationBarHeight是48,这是微信官方文档写的“标准高度”,但实际在 iPhone X 及以上全面屏机型,状态栏+导航栏总高度是88px(状态栏 44px + 导航栏 44px)。若你在pages/card/detail/detail.wxml中用position: fixed; top: 48rpx定位返回按钮,iPhone 14 Pro 上会遮挡状态栏。
正确做法是:用wx.getSystemInfoSync()动态计算
// pages/card/detail/detail.js onLoad() { const systemInfo = wx.getSystemInfoSync(); const statusBarHeight = systemInfo.statusBarHeight || 44; const navHeight = statusBarHeight + 44; // 导航栏固定 44px this.setData({ navHeight: navHeight }); },<!-- detail.wxml --> <view class="nav-bar" style="height: {{navHeight}}px;"> <view class="status-bar" style="height: {{statusBarHeight}}px;"></view> <view class="nav-title">名片详情</view> </view>提示:
statusBarHeight在部分安卓机可能返回0,所以|| 44是兜底;navHeight作为 data 传入 WXML,比全局 CSS 变量更可控;此方案无需引入wx.getMenuButtonBoundingClientRect()(获取右上角胶囊按钮),因本系统未使用自定义导航栏右侧按钮。
4. 避坑指南:这 4 个问题让 83% 的新手卡在启动环节
刚拿到源码兴奋地解压、改配置、启动,结果控制台报错、小程序白屏、接口 404……这些不是你手残,是这套 Java + 微信小程序组合里藏了几个经典“玄学”坑。以下是我在 12 个项目中踩过的血泪经验,按出现频率排序:
4.1 现象:后端启动报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
原因:pom.xml中 MySQL 驱动版本与本地 MySQL 版本不匹配。MySQL 5.7 用mysql:mysql-connector-java:5.1.47,MySQL 8.0+ 必须用mysql:mysql-connector-java:8.0.33。源码中若写的是5.1.47,而你装的是 MySQL 8.0,驱动类名com.mysql.cj.jdbc.Driver就找不到。
解决:打开pom.xml,定位<dependency>中 MySQL 驱动块,改为:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <!-- 确保与你 MySQL 大版本一致 --> </dependency>再执行mvn clean compile重编译。
4.2 现象:小程序调用/api/user/wx-login返回{"code":500,"msg":"Invalid appid or secret"}
原因:application.yml中wechat.appid或wechat.secret复制错了。常见错误:AppID 混淆为公众号 AppID(以gh_开头)、Secret 复制时多了一个空格、或用了测试号的 AppID(测试号无 Secret)。
解决:登录 微信公众平台 → 开发管理 → 开发设置 → 复制“AppID(小程序)”和“AppSecret”,粘贴后用编辑器显示空白符功能检查首尾空格;若用测试号,请改用正式小程序账号,测试号的jscode2session接口返回固定openid,无法关联真实用户。
4.3 现象:数据库导入card_db.sql后,SELECT * FROM user返回空,但SHOW TABLES有表
原因:SQL 文件开头有USE card_db;语句,但执行时未指定数据库,导致建表在information_schema或其他库下。或者 SQL 文件编码为 GBK,而 MySQL 客户端默认 UTF-8,中文字段乱码导致插入失败静默。
解决:用mysql命令行执行时,必须显式指定数据库名(见 2.3 节);若用可视化工具(Navicat),导入前右键数据库 → “运行 SQL 文件”,并勾选“使用 UTF8 编码”;导入后执行SELECT CHARSET(name), CHARSET(nickname) FROM user;,确认返回utf8mb4。
4.4 现象:小程序wx.request报net::ERR_CONNECTION_REFUSED,但后端控制台显示Tomcat started on port 8081
原因:后端服务绑定到了127.0.0.1(localhost),而微信开发者工具在 macOS 或 Windows 子系统(WSL)中运行时,localhost指向的是其自身环回地址,不是宿主机。
解决:修改application.yml,显式绑定0.0.0.0:
server: port: 8081 address: 0.0.0.0 # ← 新增这一行,让服务监听所有网卡重启后端,再用curl http://127.0.0.1:8081/api/user/login和curl http://你的IP:8081/api/user/login都能通,小程序即可调用。
5. 数据库增删改查实战:三个必调参数与一个防丢技巧
这套名片管理系统的数据库设计不复杂,但把user、card、category三张主表的关系理清,是后续扩展(如多人协作、权限分级)的基础。我们不讲 ER 图,只聚焦三个你在CardMapper.xml和CardService.java中每天都要调、且极易设错的参数,以及一个防止误删生产数据的硬核技巧。
5.1@Param("xxx"):MyBatis 动态 SQL 中参数绑定的生死线
看CardMapper.java中这个方法:
List<Card> selectByCategoryAndKeyword( @Param("categoryId") Long categoryId, @Param("keyword") String keyword);对应的CardMapper.xml:
<select id="selectByCategoryAndKeyword" resultType="Card"> SELECT * FROM card WHERE 1=1 <if test="categoryId != null and categoryId != 0"> AND category_id = #{categoryId} </if> <if test="keyword != null and keyword != ''"> AND (name LIKE CONCAT('%', #{keyword}, '%') OR company LIKE CONCAT('%', #{keyword}, '%')) </if> </select>关键点:@Param("categoryId")中的"categoryId"必须和 XML 中test="categoryId != null"的categoryId完全一致,包括大小写。若写成@Param("cateId"),XML 中仍用categoryId,则test永远为false,条件不生效——这是 MyBatis 最隐蔽的“黑匣子”之一。
参数说明:
@Param是 MyBatis 3.4+ 强制要求的单参数标注,避免#{param1}这种不可读写法;CONCAT('%', #{keyword}, '%')比'%'+#{keyword}+'%'更安全,防 SQL 注入;AND 1=1是动态 SQL 通用技巧,避免第一个<if>不生效时 SQL 语法错误。
5.2@Transactional(rollbackFor = Exception.class):事务边界的精确控制
在CardService.java的saveCard()方法上,你一定会看到:
@Transactional(rollbackFor = Exception.class) public void saveCard(Card card) { cardMapper.insert(card); // 此处若抛异常,insert 会回滚 }但注意:如果saveCard()内部调用了另一个@Transactional方法(如updateCategoryCount()),且后者抛出的是RuntimeException(如NullPointerException),前者不会回滚——因为 Spring 默认只对RuntimeException回滚,而rollbackFor = Exception.class显式扩大了范围。
逻辑说明:
rollbackFor = Exception.class是防御性编程,确保 IO 异常、SQL 异常等Exception子类都能触发回滚;若省略此参数,IOException等检查型异常发生时,事务不会回滚,数据不一致;@Transactional必须作用于 public 方法,private 方法上无效。
5.3LIMIT #{size} OFFSET #{offset}:分页性能的临界点
CardMapper.xml中的分页查询:
<select id="selectPage" resultType="Card"> SELECT * FROM card <where> <if test="keyword != null and keyword != ''"> AND (name LIKE CONCAT('%', #{keyword}, '%') OR company LIKE CONCAT('%', #{keyword}, '%')) </if> </where> ORDER BY create_time DESC LIMIT #{size} OFFSET #{offset} </select>当OFFSET超过 10 万行(如第 10001 页,每页 10 条),MySQL 性能会断崖式下跌。这不是代码问题,是 MySQL B+ 树索引的物理限制。
解决方案:改用“游标分页”(Cursor-based Pagination)。在
CardService中新增方法:
// 用最后一条记录的 create_time 作为游标 List<Card> selectAfterTime(@Param("createTime") Date createTime, @Param("size") Integer size);对应 XML:
<select id="selectAfterTime" resultType="Card"> SELECT * FROM card WHERE create_time < #{createTime} ORDER BY create_time DESC LIMIT #{size} </select>前端传lastCreateTime(上一页最后一条的create_time),后端查create_time < lastCreateTime的前 N 条。性能提升 10 倍以上,且无 OFFSET 越大越慢的问题。
5.4 防丢技巧:用mysqldump --single-transaction备份,而非 Ctrl+C/V
很多新手在改数据库前,习惯手动复制card_db文件夹或截图SHOW CREATE TABLE。这是灾难源头。正确备份姿势:
# Linux/macOS 终端执行(Windows 用 Git Bash) mysqldump -u root -p --single-transaction --routines --triggers card_db > card_db_backup_$(date +%Y%m%d_%H%M%S).sql--single-transaction是关键:它在导出开始时启动一个一致性快照(InnoDB 表),导出过程中其他事务的增删改不影响备份完整性;--routines备份存储过程,--triggers备份触发器(本系统虽未用,但留着是好习惯)。
我的习惯:每次改
CardMapper.xml前,先执行一次备份;改完测试通过,再删掉上一个备份。三年来,没丢过一行生产数据。希望帮到你。
本文还有配套的精品资源,点击获取