☰
Java微信小程序名片管理系统:源码+数据库+教程全栈实战
2026/10/8 4:38:29 网站建设 项目流程

简介:这份资源是面向高校学生与Java初学者的小程序名片管理系统完整项目包,适用于课程设计、毕业设计及小程序开发练手场景。项目采用前后端分离思路,前端为微信小程序,后端基于SSM或SpringBoot框架,配套MySQL数据库脚本,开发环境涉及IDEA、微信开发者工具、Navicat与Maven,覆盖企业信息、注册用户、客户名片、联系人名片、新闻通知、留言板及系统文件等管理模块,功能完善且界面美观。压缩包共736个文件,约11.2MB,包含71个Java源文件、74个class、103个js、95个xml、60个html、42个css、36个wxss、28个wxml及71张png等,涵盖后端逻辑、前端页面、样式资源与数据库脚本,结构清晰便于二次开发。目前已有201人学习下载,适合需要完整项目参考、快速搭建运行环境并理解名片管理业务实现的学习者。

1. 从一张纸质名片到一套可运行的名片管理系统:这套 Java 小程序方案到底解决什么问题

展会现场换完一圈名片,回到工位发现口袋里塞了三十多张纸片,想找某个客户的联系方式得一张张翻——这个场景几乎每个做销售、做商务、做渠道的人都遇到过。基于微信小程序的名片管理系统,本质就是把「收名片、存名片、查名片、换名片」这条链路搬到手机上,用微信小程序做前端入口,用 Java 做后端服务,用数据库做持久化存储。用户打开小程序就能录入、检索、分享自己的名片,管理者在后台能看到全量数据。这套方案适合两类人:一类是想拿一个完整项目练手 Java Web + 小程序全栈开发的学生或转行者,另一类是中小团队想快速搭一套内部通讯录或客户名片库的开发者。标题里提到的源码、数据库、教程三件套,意味着这不是一个只讲思路的空壳,而是能跑起来、能改、能部署的完整工程。接下来我会按「先搞清楚架构和数据怎么设计,再动手把后端和前端跑通,最后说清楚哪些地方最容易翻车」的顺序,把这套东西拆开讲透。

2. 名片管理系统的技术选型与数据库设计:为什么是 Spring Boot + MySQL + 原生小程序

2.1 后端为什么选 Spring Boot 而不是裸 Servlet

拿到「Java + 小程序」这个组合,后端框架的选择其实不多。常见做法是 Spring Boot 打底,配 MyBatis 或 MyBatis-Plus 做持久层。原因很直接:小程序端要的是 JSON 接口,Spring Boot 的@RestController天然就是干这个的,一个注解把返回值序列化成 JSON,省掉手写response.getWriter().write()的麻烦。裸 Servlet 也能做,但你要自己处理 JSON 序列化、参数绑定、跨域、异常统一返回,写到最后会发现造了一遍 Spring MVC 的轮子。

MyBatis-Plus 在这里的价值更明显。名片实体字段不多但增删改查频繁,用 MyBatis-Plus 的BaseMapper可以直接省掉大量单表 CRUD 的 XML。热搜词里出现的「mybatisplus根据java实体类生成创建表的sql语句」正好对应这个场景——实体类定好,建表语句基本就出来了。我一般会先在实体类上把字段和类型敲定,再反推 DDL,这样能避免「表建好了发现字段对不上实体」的来回改。

依赖上核心就三块:spring-boot-starter-web提供 Web 能力,mybatis-plus-boot-starter提供持久层,mysql-connector-java提供驱动。版本上不用追最新,选一个 Spring Boot 2.7.x 的稳定版配对应的 MyBatis-Plus 3.5.x 即可,兼容性经过大量项目验证,踩坑概率低。

2.2 数据库表怎么设计:三张表撑起整个系统

名片管理系统的数据模型不复杂,但要想清楚「用户」和「名片」的关系。常见做法是拆成三张表:用户表存微信登录身份,名片表存名片内容,分类表存名片的标签分组。下面是我一般会用的建表结构。

-- 用户表:存微信 openid 和基本信息 CREATE TABLE `sys_user` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `openid` VARCHAR(64) NOT NULL COMMENT '微信openid,唯一标识', `nickname` VARCHAR(64) DEFAULT NULL COMMENT '昵称', `avatar_url` VARCHAR(255) DEFAULT NULL COMMENT '头像地址', `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 名片表:核心业务表 CREATE TABLE `biz_card` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `user_id` BIGINT NOT NULL COMMENT '所属用户id', `name` VARCHAR(32) NOT NULL COMMENT '姓名', `company` VARCHAR(128) DEFAULT NULL COMMENT '公司', `position` VARCHAR(64) DEFAULT NULL COMMENT '职位', `mobile` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `email` VARCHAR(64) DEFAULT NULL COMMENT '邮箱', `address` VARCHAR(255) DEFAULT NULL COMMENT '地址', `category_id` BIGINT DEFAULT NULL COMMENT '分类id', `remark` VARCHAR(255) DEFAULT NULL COMMENT '备注', `is_public` TINYINT DEFAULT 0 COMMENT '是否公开 0否 1是', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='名片表'; -- 分类表:给名片打标签 CREATE TABLE `biz_category` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `user_id` BIGINT NOT NULL COMMENT '所属用户id', `category_name` VARCHAR(32) NOT NULL COMMENT '分类名称', `sort` INT DEFAULT 0 COMMENT '排序', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='名片分类表';

字段设计上有几个点值得说。openid加了唯一索引,因为微信登录时同一个用户可能多次请求,靠唯一约束兜底能防止重复插入。biz_card表上user_id和name都建了索引,前者用于「查我的名片」,后者用于「按姓名搜索」,这两个是最高频的查询路径。字符集统一用utf8mb4,因为名片里可能出现 emoji 或生僻字,用utf8会截断报错,这个坑我踩过不止一次。

提示:is_public字段控制名片是否对外可见,如果要做「交换名片」功能,查询时记得带上这个条件,否则会把别人的私密名片也查出来。

2.3 小程序端为什么用原生而不是 uniapp

热搜词里有「uniapp 微信小程序打包」,说明不少人在纠结要不要上 uniapp。我的判断是:如果这个项目只发微信小程序一端,原生开发更省心。原生小程序的wx.request、wx.login、wx.getUserProfile这些 API 直接可用,不用经过 uniapp 的编译层,调试时看到的报错就是微信开发者工具的原生报错,定位问题快。uniapp 的优势在多端复用,但你只发一端时,这个优势用不上,反而多了一层编译配置的心智负担。

原生小程序的目录结构也简单:pages放页面,utils放请求封装,app.js管全局登录态。请求封装我一般会写一个统一的request.js,把 baseUrl、token 注入、错误提示都收在一处,避免每个页面各写一遍wx.request。

3. 后端接口从零跑通:登录、名片增删改查的具体实现

3.1 微信登录换 openid 的完整链路

小程序端的登录不是账号密码,而是wx.login拿到临时code,传给后端,后端拿code加上小程序的appid和secret去微信服务器换openid和session_key。这条链路是整套系统的入口,走不通后面全白搭。

@RestController @RequestMapping("/api/auth") public class AuthController { @Value("${wx.appid}") private String appid; @Value("${wx.secret}") private String secret; @Autowired private SysUserService sysUserService; @PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO) { // 1. 用 code 换 openid String url = "https://api.weixin.qq.com/sns/jscode2session" + "?appid=" + appid + "&secret=" + secret + "&js_code=" + loginDTO.getCode() + "&grant_type=authorization_code"; String resp = HttpUtil.get(url); JSONObject json = JSONUtil.parseObj(resp); String openid = json.getStr("openid"); if (openid == null) { // 微信返回错误码,通常是 code 失效或 appid 配置错 return Result.fail("登录失败:" + json.getStr("errmsg")); } // 2. 查库,没有就注册 SysUser user = sysUserService.getByOpenid(openid); if (user == null) { user = new SysUser(); user.setOpenid(openid); user.setCreateTime(new Date()); sysUserService.save(user); } // 3. 生成 token 返回(这里用简单 UUID,生产建议 JWT) String token = UUID.randomUUID().toString(); RedisUtil.set("token:" + token, user.getId(), 7 * 24 * 3600); return Result.ok(MapUtil.builder() .put("token", token) .put("userId", user.getId()) .build()); } }

这段代码的逻辑分三步:换 openid、查库注册、发 token。参数上appid和secret从配置文件读,不要硬编码在代码里,否则换环境要改代码。grant_type固定是authorization_code,写错会直接报错。返回的 token 我用了 Redis 存映射关系,key 是 token,value 是 userId,过期时间七天。生产环境更推荐 JWT,省掉 Redis 依赖,但 JWT 的注销和续期要额外处理,看项目规模取舍。

小程序端调用就三行:

wx.login({ success: (res) => { // res.code 就是临时凭证,传给后端 wx.request({ url: 'http://localhost:8080/api/auth/login', method: 'POST', data: { code: res.code }, success: (r) => { wx.setStorageSync('token', r.data.data.token); } }); } });

wx.login拿到的code只能用一次,五分钟内有效,所以拿到后要立刻传给后端,不要缓存。wx.setStorageSync把 token 存本地,后续每个请求从 storage 里取出来塞进 header。

3.2 名片增删改查接口与分页查询

名片这块是业务核心,接口设计上遵循 RESTful 风格:POST 新增、PUT 修改、DELETE 删除、GET 查询。查询要支持分页和按姓名模糊搜索,这是名片管理最常用的功能。

@RestController @RequestMapping("/api/card") public class CardController { @Autowired private BizCardService cardService; @PostMapping public Result add(@RequestBody BizCard card, @RequestHeader("token") String token) { Long userId = RedisUtil.get("token:" + token); if (userId == null) { return Result.fail("未登录"); } card.setUserId(userId); card.setCreateTime(new Date()); cardService.save(card); return Result.ok(card.getId()); } @GetMapping("/page") public Result page(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String keyword, @RequestHeader("token") String token) { Long userId = RedisUtil.get("token:" + token); // 构造分页条件,按姓名或公司模糊匹配 Page<BizCard> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<BizCard> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(BizCard::getUserId, userId); if (StrUtil.isNotBlank(keyword)) { wrapper.and(w -> w.like(BizCard::getName, keyword) .or().like(BizCard::getCompany, keyword)); } wrapper.orderByDesc(BizCard::getCreateTime); return Result.ok(cardService.page(page, wrapper)); } }

分页用 MyBatis-Plus 的Page对象,pageNum和pageSize给了默认值,前端不传也不会报错。keyword用LambdaQueryWrapper的and嵌套,实现「姓名或公司包含关键词」的模糊匹配,注意这里必须用and包起来,否则or会和外层的eq(userId)平级,导致查出别人的名片——这是个非常隐蔽的权限漏洞,我见过真实项目里出过这个问题。

参数说明:pageNum从 1 开始,pageSize建议前端限制在 50 以内,防止有人传个 10000 把数据库拖垮。keyword为空时跳过模糊条件,走全量分页。

3.3 小程序端页面与请求封装

小程序端页面不多,核心就四个:登录页、名片列表页、名片编辑页、名片详情页。请求封装统一放在utils/request.js:

const BASE_URL = 'http://localhost:8080'; function request(options) { const token = wx.getStorageSync('token'); return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'content-type': 'application/json', 'token': token // 每个请求自动带上 token }, success: (res) => { if (res.data.code === 200) { resolve(res.data.data); } else if (res.data.code === 401) { // token 失效,跳回登录页 wx.redirectTo({ url: '/pages/login/login' }); reject(res.data); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); reject(res.data); } }, fail: reject }); }); } module.exports = { request };

封装的价值在于:token 注入只写一次,错误提示统一处理,401 自动跳登录。页面里调用就变成request({ url: '/api/card/page', data: { pageNum: 1 } }),干净很多。注意BASE_URL在开发时是localhost,但真机调试时手机访问不到电脑的 localhost,要换成电脑的局域网 IP,这个坑几乎每个人第一次都会踩。

4. 部署与联调避坑:那些让你卡半天的常见问题

4.1 小程序请求域名校验与本地调试

现象:开发者工具里请求正常,真机预览时报「不在以下 request 合法域名列表中」。

原因:微信小程序正式环境要求所有请求走 HTTPS 且域名在后台白名单里,本地http://localhost不在白名单。

解决:开发阶段在开发者工具「详情 → 本地设置」里勾选「不校验合法域名、web-view、TLS 版本以及 HTTPS 证书」,真机预览时打开调试模式也能跳过校验。上线前必须把后端部署到 HTTPS 域名并配置到小程序后台,这一步绕不过去。

4.2 数据库连接报时区错误

现象:启动后端时报The server time zone value 'xxx' is unrecognized。

原因:MySQL 8 的驱动对时区敏感,JDBC URL 没指定时区时用了系统默认值,服务器时区不标准就报错。

解决:JDBC URL 加上serverTimezone=Asia/Shanghai,完整写法jdbc:mysql://localhost:3306/card_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false。useSSL=false在本地开发时加上,避免证书警告。

4.3 中文乱码

现象:名片里存的中文在数据库里显示成问号,或者接口返回乱码。

原因:三个环节都可能出问题——数据库字符集、连接字符集、响应编码。

解决:数据库和表都用utf8mb4,JDBC URL 带characterEncoding=utf8,Spring Boot 的application.yml里配server.servlet.encoding.charset=utf-8和force=true。三处都对齐基本就不会乱码了。

4.4 token 丢失导致接口全部 401

现象:登录成功但后续请求全部返回未登录。

原因:小程序端wx.request的 header 里 token 没带上,或者后端从 header 取 token 的 key 和前端设置的不一致。

解决:检查前端header里的 key 和后端@RequestHeader("token")里的名字是否完全一致,大小写敏感。另外确认wx.setStorageSync存 token 的时机在请求之前,异步问题也会导致第一次请求拿不到 token。

4.5 分页查询 total 为 0 但列表有数据

现象:接口返回的列表有数据,但total是 0,前端分页组件显示异常。

原因:MyBatis-Plus 的分页插件没配置,Page对象不会自动执行 count 查询。

解决:加一个配置类注册MybatisPlusInterceptor并添加PaginationInnerInterceptor,指定数据库类型为 MySQL。漏了这一步分页功能就是残的,列表能出来但总数永远是 0。

5. 让这套系统真正好用:几个提升体验的进阶技巧

把基础功能跑通只是及格线,真正让名片管理系统好用,还得在几个细节上做文章。第一个是搜索体验。前面用的是like %keyword%,数据量小的时候没问题,但名片攒到几千张时全表扫描会明显变慢。我一般会加一个FULLTEXT索引或者引入简单的分词,把姓名、公司、备注拼成一个搜索字段,查询时走索引匹配。如果不想动数据库,退而求其次的做法是限制搜索范围,只搜姓名和公司,别把备注也扫进去。

第二个是名片分享。名片管理系统的价值一半在「管自己的」,一半在「换别人的」。实现上可以生成一张带参数的小程序码,参数里带上名片 id,别人扫码后直接打开名片详情页,点「保存到我的名片」就完成了一次交换。生成小程序码用微信的wxacode.getUnlimited接口,后端调一次拿到图片流返回给前端。这里要注意scene参数有长度限制,别把整个名片 JSON 塞进去,只放 id 就够。

第三个是数据导出。用户攒了一堆名片后会有导出需求,常见做法是后端用 EasyExcel 生成 xlsx 文件,接口返回文件流,小程序端用wx.downloadFile下载后wx.openDocument打开。导出时记得按当前用户的userId过滤,别把全库数据导出去了。

验证这套系统是否真的跑通,我的习惯是走一遍完整链路:新用户登录 → 新增三张名片 → 按姓名搜索 → 修改其中一张 → 删除一张 → 分页查看剩余。每一步都看数据库里的数据变化,而不是只看接口返回的code:200。接口返回成功但数据没落库的情况太常见了,尤其是事务没配好的时候。

最后说个我自己的教训:早期做这类项目时我总想着把功能堆全,结果每个功能都半吊子。后来改成先把「登录 + 名片增删改查 + 分页搜索」这条主链路打磨到没有明显 bug,再往上加分享、导出、分类这些锦上添花的东西,反而交付得更快。这套源码和数据库结构本身不复杂,难的是把每个环节的边界情况都想到——比如空列表的展示、超长文本的截断、并发写入的重复。把这些处理好了,它才是一个能拿得出手的项目,而不是一个只能跑 demo 的壳子。希望帮到你。

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

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

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

立即咨询