马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
微信小程序+SSM后端联调实战:专利服务系统开发踩坑记
本文导读
做小程序+后端的项目,最折磨人的不是写代码,而是前后端联调——小程序端报个错,你不知道是前端参数传错了、后端接口有问题、还是网络配置不对。这篇文章我以一套"专利服务系统"为例(微信小程序端+SSM后端管理),把开发过程中踩过的6个大坑完整记录下来,每个坑都有现象、原因分析和解决方案。不管你是做课程设计、毕业设计,还是第一次接触小程序全栈开发,这些坑你大概率也会遇到,提前避坑能省不少时间。
一、项目概述:专利服务系统是什么
图1 微信开发者工具界面(小程序端开发用微信官方开发者工具,支持模拟器+调试器+编辑器三合一) 这套系统是面向专利申请人和知识产权服务机构的轻量化服务平台,分为两个端:微信小程序端(给普通用户用)和SSM后台管理端(给管理员用)。
端 | 技术栈 | 核心功能 | 目标用户 |
微信小程序端
|
微信小程序原生框架(WXML+WXSS+JS)
|
专利申请提交、专利查询、业务指导信息浏览、个人中心
|
专利申请人、企业、个人发明者
|
SSM后台管理端
|
Spring+SpringMVC+MyBatis+JSP+MySQL
|
用户管理、专利申请审核、业务信息管理、数据统计
|
系统管理员、专利服务机构人员
|
开发环境:JDK 1.8 + Tomcat 8.5 + MySQL 5.7 + IDEA + 微信开发者工具。数据库用MySQL,后端用SSM框架,前端小程序用原生开发,前后端通过HTTP接口(JSON格式)交互。
二、系统架构设计
图2 IDEA中SSM项目结构(Maven管理依赖,Spring+SpringMVC+MyBatis分层架构) 整体架构是典型的前后端分离(虽然后台管理端还是JSP,但小程序端是纯前端通过API和后端交互):
小程序端 → HTTPS请求 → SSM后端Controller → Service → MyBatis Mapper → MySQL数据库
后端分层:Controller层接收请求、参数校验、返回JSON;Service层处理业务逻辑;Mapper层操作数据库。小程序端不直接访问数据库,所有数据都通过后端API获取,保证了数据安全。
2.1 小程序端功能模块
模块 | 页面 | 功能说明 |
首页
|
index
|
轮播图、业务指导信息列表、快捷入口
|
专利申请
|
apply
|
填写专利信息(类型、申请人、说明书、图片),提交申请
|
专利查询
|
patent
|
查看已提交的专利申请列表和审核状态
|
个人中心
|
mine
|
用户信息、我的申请、设置、退出登录
|
2.2 后台管理端功能模块
模块 | 功能说明 |
用户管理
|
用户列表、新增/编辑/删除用户、用户类型管理
|
专利管理
|
专利申请列表、审核(通过/驳回)、专利详情查看
|
业务信息管理
|
业务指导信息的增删改查、轮播图管理
|
数据统计
|
申请量统计、用户量统计、按类型/时间筛选
|
三、联调踩坑实录:6个大坑及解决方案
图3 Postman接口调试(联调时先用Postman测后端接口,确认接口没问题再对接小程序端,能快速定位问题) 坑1:小程序request合法域名与HTTPS
现象:小程序端调用wx.request时,开发工具里能正常请求,但手机预览时报错"request:fail url not in domain list"或者直接请求失败。
原因:微信小程序对网络请求有严格的域名校验。正式版和体验版只能请求在微信公众平台配置过的"request合法域名",而且必须是HTTPS协议。开发工具里可以勾选"不校验合法域名",所以能跑通,但真机上不行。
解决方案:
① 买一个域名,备案(国内服务器必须备案),配置SSL证书(可以用免费的Let's Encrypt或阿里云免费证书);
② 登录微信公众平台 → 开发 → 开发管理 → 开发设置 → 服务器域名,把你的域名加到"request合法域名"里;
③ 开发阶段可以在开发者工具里勾选"不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书",但上线前必须配置好。
避坑提示:域名一个月只能修改5次,所以配置前想清楚。另外,域名不能带端口号(默认443),如果后端跑在8080端口,要用Nginx反向代理到443。
坑2:SSM后端跨域CORS问题
现象:小程序端请求后端接口,控制台报"Access-Control-Allow-Origin"相关错误,或者请求能发出去但拿不到响应数据。
原因:浏览器(包括小程序的webview内核)有同源策略限制。小程序端的"源"和后端服务器的"源"不同(域名、端口、协议任一不同就算跨域),后端如果没有设置CORS响应头,浏览器会拦截响应。
解决方案(SSM项目三种方式,推荐第一种):
方式一:SpringMVC配置类全局跨域(推荐)
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET","POST","PUT","DELETE","OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
方式二:在Controller类或方法上加@CrossOrigin注解
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/patent")
public class PatentController { ... }
方式三:写一个CORS过滤器(Filter),手动设置响应头。
避坑提示:SpringBoot和SSM的CORS配置方式不一样,SSM要确认Spring版本是否支持allowedOriginPatterns(Spring 5.3+才有),低版本用allowedOrigins("*")配合allowCredentials(false)。
坑3:文件上传(说明书PDF、专利图片)
现象:小程序端用wx.uploadFile上传文件,后端接收不到,或者报"Current request is not a multipart request"。
原因:小程序的wx.uploadFile和普通的wx.request不一样,它发送的是multipart/form-data请求。SSM后端需要配置MultipartResolver才能解析文件上传请求,而且Controller方法要用MultipartFile接收。
解决方案:
① Spring配置文件里配置MultipartResolver:
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760"/> <!-- 10MB -->
<property name="defaultEncoding" value="UTF-8"/>
</bean>
② pom.xml引入commons-fileupload依赖:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
③ Controller接收文件:
@RequestMapping("/upload")
@ResponseBody
public Map<String,Object> upload(
@RequestParam("file") MultipartFile file,
HttpServletRequest request) throws IOException {
String path = request.getSession().getServletContext()
.getRealPath("/upload/");
String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
File dest = new File(path, fileName);
if(!dest.getParentFile().exists()) dest.getParentFile().mkdirs();
file.transferTo(dest);
Map<String,Object> result = new HashMap<>();
result.put("code", 200);
result.put("url", "/upload/" + fileName);
return result;
}
④ 小程序端上传:
wx.uploadFile({
url: 'https://your-domain.com/api/patent/upload',
filePath: tempFilePath,
name: 'file', // 必须和后端@RequestParam的name一致
formData: { userId: '123' },
success(res) {
const data = JSON.parse(res.data);
console.log('上传成功', data.url);
}
});
避坑提示:wx.uploadFile的name参数必须和后端@RequestParam的value一致,否则后端接收不到。另外,上传文件也受域名校验,必须是HTTPS。
坑4:微信登录与自有用户体系对接
现象:小程序端调用wx.login拿到code,传给后端换openid,后端请求微信接口报"invalid code"或者返回的openid为空。
原因:wx.login拿到的code只能用一次,而且有效期5分钟。如果前端把code存起来重复使用,或者后端请求微信接口时AppID/AppSecret配错了,就会报错。另外,code必须是当前小程序的,不能用其他小程序的code。
解决方案:
① 小程序端登录流程:wx.login获取code → 调用后端登录接口传code → 后端用code+AppID+AppSecret请求微信接口换openid和session_key → 后端生成自定义token返回给小程序 → 小程序把token存在本地,后续请求带在header里。
② 后端请求微信接口(用HttpClient或RestTemplate):
String url = "https://api.weixin.qq.com/sns/jscode2session"
+ "?appid=" + APPID
+ "&secret=" + SECRET
+ "&js_code=" + code
+ "&grant_type=authorization_code";
// 发送GET请求,解析返回的JSON获取openid
③ 后端维护自己的用户表,用openid作为唯一标识关联微信用户和系统用户。第一次登录自动注册,后续登录直接查用户表。
避坑提示:AppID和AppSecret存在后端配置文件里,不要写死在代码里,更不要传到前端。session_key不要返回给前端,存在后端用于解密用户敏感数据(如手机号)。
坑5:前后端数据格式不一致
现象:小程序端提交表单,后端收到的日期是null或者格式不对;枚举值前端传的是中文,后端存的是数字;后端返回的null字段前端显示"null"字符串。
原因:前后端没有约定好数据格式,各搞各的。日期格式前端传"2024-01-01",后端用Date接收可能解析失败;枚举值前端传"发明专利",后端用Integer接收直接报错;Jackson序列化null字段时默认输出null,前端直接渲染就显示"null"。
解决方案:
① 日期统一用字符串传递("yyyy-MM-dd"或"yyyy-MM-dd HH:mm:ss"),后端用@DateTimeFormat注解接收:
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date applyDate;
② 枚举值统一用数字编码(1=发明专利,2=实用新型,3=外观设计),前后端维护一份编码对照表,前端显示时做映射。
③ 后端统一返回格式,null字段处理:Jackson配置@JsonInclude(JsonInclude.Include.NON_NULL),null字段不序列化;或者前端做容错处理:
// 前端容错
const name = res.data.name || '';
const list = res.data.list || [];
避坑提示:项目开始前一定要写接口文档(可以用Swagger或Postman文档),约定好每个字段的类型、格式、是否必填、示例值。前后端对着文档开发,能避免80%的联调问题。
坑6:小程序审核与发布注意事项
现象:小程序提交审核被拒,理由是"涉及专利代理/知识产权服务,需要特殊资质"或者"内容不符合平台规范"。
原因:微信对涉及专业服务(法律、知识产权、医疗等)的小程序有资质要求,需要提供相关许可证或营业执照。另外,小程序不能有诱导分享、虚拟支付(iOS)等违规行为。
解决方案:
① 确认小程序的服务类目,专利服务可能需要选择"商业服务-知识产权"或"工具-效率"类目,按要求上传资质;
② 毕业设计/课程设计用的小程序,可以选"工具"类目,功能描述写得简单一些,避免触发资质审核;
③ 提交审核前仔细阅读《微信小程序平台运营规范》,不要有敏感词、外部链接、诱导分享等内容;
④ 如果只是演示用,可以用"体验版"分享给指定用户(最多100人),不需要提交审核。
避坑提示:审核被拒后看清楚拒绝理由,针对性修改后再提交。反复被拒可能会影响账号信用。毕业设计项目建议直接用体验版演示,不一定要发布正式版。
四、核心代码实现
4.1 小程序端请求封装
每个页面都写wx.request太啰嗦,封装一个通用的请求方法,统一处理baseURL、token、错误提示:
// utils/request.js
const BASE_URL = 'https://your-domain.com/api';
function request(options) {
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': wx.getStorageSync('token') || ''
},
success(res) {
if (res.data.code === 200) {
resolve(res.data);
} else if (res.data.code === 401) {
wx.showToast({ title: '请先登录', icon: 'none' });
wx.navigateTo({ url: '/pages/login/login' });
reject(res.data);
} else {
wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' });
reject(res.data);
}
},
fail(err) {
wx.showToast({ title: '网络错误', icon: 'none' });
reject(err);
}
});
});
}
module.exports = { request };
4.2 SSM后端统一返回格式
后端所有接口统一返回格式,前端处理起来方便:
public class Result<T> {
private int code; // 200成功,401未登录,500错误
private String msg; // 提示信息
private T data; // 返回数据
public static <T> Result<T> success(T data) {
Result<T> r = new Result<>();
r.setCode(200); r.setMsg("success"); r.setData(data);
return r;
}
public static <T> Result<T> error(int code, String msg) {
Result<T> r = new Result<>();
r.setCode(code); r.setMsg(msg);
return r;
}
}
4.3 专利申请Controller
@RestController
@RequestMapping("/api/patent")
public class PatentController {
@Autowired
private PatentService patentService;
@RequestMapping("/list")
public Result<List<Patent>> list(
@RequestParam(defaultValue="1") int page,
@RequestParam(defaultValue="10") int size,
@RequestParam(required=false) Integer userId) {
List<Patent> list = patentService.findByPage(page, size, userId);
return Result.success(list);
}
@RequestMapping(value="/apply", method=RequestMethod.POST)
public Result<String> apply(@RequestBody Patent patent) {
patent.setApplyTime(new Date());
patent.setStatus(0); // 0待审核
patentService.save(patent);
return Result.success("申请提交成功");
}
@RequestMapping("/detail")
public Result<Patent> detail(@RequestParam int id) {
Patent patent = patentService.findById(id);
return Result.success(patent);
}
}
五、数据库设计要点
表名 | 核心字段 | 说明 |
user
|
id, username, password, openid, phone, userType, createTime
|
用户表,userType区分普通用户和管理员
|
patent
|
id, userId, title, patentType, applyType, coAuthor, categoryNo, keywords, description, fileUrl, imageUrl, status, applyTime, auditTime, auditRemark
|
专利申请表,status状态:0待审核1通过2驳回
|
news
|
id, title, content, image, type, createTime
|
业务指导信息表,首页展示的资讯内容
|
banner
|
id, imageUrl, linkUrl, sort
|
轮播图表,首页顶部轮播
|
设计要点:一是patent表的status字段用数字编码,配合字典表或前端映射显示中文状态;二是fileUrl和imageUrl存相对路径,前端拼接域名访问;三是所有表加createTime和updateTime字段,方便数据追溯;四是用户表的openid字段加唯一索引,防止重复注册。
六、部署与上线
开发完成后要部署到服务器,小程序才能真机访问。部署步骤:
1. 后端打包:IDEA里Maven打包成war包,上传到服务器Tomcat的webapps目录;
2. 数据库导入:本地MySQL导出sql脚本,在服务器MySQL里执行;
3. 域名和SSL:域名解析到服务器IP,配置Nginx反向代理,HTTPS证书配置好;
4. 小程序配置:微信公众平台配置request合法域名、uploadFile合法域名、downloadFile合法域名;
5. 上传体验版:微信开发者工具点击"上传",在公众平台设为体验版,扫码测试;
6. 提交审核(可选):确认没问题后提交审核,审核通过后发布正式版。
避坑提示:服务器时间要和北京时间一致,否则微信登录的timestamp校验会失败。Tomcat的URIEncoding要设为UTF-8,否则中文参数会乱码。
七、常见问题FAQ
Q:小程序端和后台管理端可以共用一套后端吗?
A:可以,而且推荐共用。后端Controller分两个路径前缀,/api/给小程序端用(返回JSON),/admin/给后台管理端用(返回JSP页面或JSON)。Service层和Mapper层完全复用,只是Controller层分开。这样一套后端维护两份前端,效率最高。
Q:SSM和SpringBoot选哪个?
A:如果是新项目,强烈推荐SpringBoot——自动配置、内嵌Tomcat、开发效率高。但如果学校课程要求用SSM,或者老项目维护,那就用SSM。SSM的核心是XML配置多一些,理解了IoC和AOP的原理,两者切换很容易。这篇文章的项目是SSM,因为很多高校的毕业设计要求用SSM。
Q:小程序原生开发和uni-app/Taro怎么选?
A:只做微信小程序,原生开发最稳定、性能最好、官方文档最全。如果要同时发布到支付宝、百度、抖音小程序,或者转H5/App,用uni-app(Vue语法)或Taro(React语法)更高效。毕业设计如果只要求微信小程序,用原生就行,学习成本低。
Q:联调时怎么快速定位是前端还是后端的问题?
A:三步定位法:① 用Postman直接调用后端接口,如果Postman也报错,那是后端问题;② Postman正常但小程序报错,看小程序控制台的Network面板,检查请求参数和响应;③ 参数对但数据不对,大概率是前后端字段名或格式不一致。养成先测后端再联调前端的习惯,能省很多时间。
Q:专利服务系统需要对接国家知识产权局的真实接口吗?
A:毕业设计/课程设计不需要,自己模拟数据就行。真实的专利查询和申请要对接国家知识产权局的系统,有严格的资质和接口规范。学生项目做一个模拟的专利申请流程和管理后台就够了,重点是展示技术实现能力,而不是业务真实性。
八、总结与延伸学习路径
小程序+SSM后端的项目,技术本身不难,难的是前后端联调的细节——域名、跨域、文件上传、登录鉴权、数据格式,每一个都可能卡住你。这篇文章总结的6个坑,是我做这类项目时反复遇到的,提前知道解决方案,开发效率能提升一倍。
如果想继续深入全栈开发,可以按这个路径学习:
1. 小程序进阶:自定义组件、云开发、性能优化、分包加载;
2. 后端进阶:SpringBoot、SpringSecurity/JWT鉴权、Redis缓存、MyBatis-Plus;
3. 工程化:Maven多模块、Docker部署、CI/CD、Nginx反向代理;
4. 接口文档:Swagger/Knife4j自动生成接口文档,前后端协作更高效;
5. 项目实战:多做几个完整项目,从需求分析到部署上线全流程走一遍,比看教程有用。
—— 全栈开发的核心不是什么都会,而是出了问题能快速定位在哪一层。
有问题欢迎评论区交流,需要更多小程序+后端项目实战案例可以关注后续更新~
|