

🔥个人主页:北极的代码(欢迎来访) 🎬作者简介:java后端学习者 ❄️个人专栏:苍穹外卖日记,SSM框架深入,JavaWeb ✨命运的结局尽可永在,不屈的挑战却不可须臾或缺!
前言:前面我们简单介绍了如何调用第三方的接口,微信小程序的接口,这里我们将具体的实现微信登录的业务操作。
小程序登录流程时序:

需求分析(类比)
基于微信登录实现小程序的登录
如果是新用户需要自动完成注册
第一部分:需求分析(老板在想什么?)
老板开网吧前,得先想清楚:我这个网吧到底要提供什么服务?给谁用?
1. 用户角色划分(谁进网吧?)
在苍穹外卖中,主要有两类人:
-
普通用户(C端 – 顾客):就是来上网点餐的“你”。他们用微信小程序。
-
需求: 浏览菜单、加购物车、下单支付、查看订单状态。
-
-
商家/管理员(B端 – 网管/老板):管理网吧的人。他们用电脑浏览器(后台管理系统)。
-
需求: 菜品管理(上架下架)、套餐管理、订单处理(接单、完成)、营业数据分析。
-
2. 核心业务流程(网吧怎么运转?)
把业务流程串起来看:
顾客浏览菜单 → 加购物车 → 下单 → 支付 → 商家接单 → 制作 → 配送/自取
每一步背后,都需要系统提供支持。
3. 功能需求与非功能需求(必须有的和最好有的)
-
功能需求(必须有):
-
用户端:登录授权、菜品浏览、购物车、下单支付、历史订单。
-
管理端:菜品管理、员工管理、分类管理、订单管理、数据统计。
-
-
非功能需求(最好有的):
-
性能:中午高峰期(比如11:30-12:30),几百人同时下单,系统不能卡死。
-
安全:不能让别人随意修改别人的订单。
-
可用性:7×24小时稳定运行。
-
第二部分:接口设计(把想法画成图纸)
想清楚了要做什么,接下来就是具体怎么实现。接口设计就是定义“前后端之间怎么对话”。
还是用“点餐”这个核心功能来举例,看看从需求到接口是怎么一步步落地的。
案例:C端用户点餐
1. 需求描述
用户打开小程序,看到菜品列表,点击“加入购物车”,然后去结算,下单,最后支付。
2. 接口设计概览(设计图纸长什么样?)
接口文档通常包含以下要素:
-
请求路径:URL地址(如 /user/cart/add)
-
请求方式:GET(获取)、POST(新增/提交)、PUT(修改)、DELETE(删除)
-
请求参数:需要传什么数据给后端?
-
返回数据:后端会返回什么结果?
3. 具体接口举例
接口一:获取菜品列表
-
需求分析:用户想看“川菜”分类下有什么菜。
-
接口设计:
-
路径:/user/dish/list
-
方式:GET
-
参数:categoryId(分类ID,比如川菜的ID是101)
-
返回:菜品的数组(包含名称、图片、价格、口味等)。
-
形象解释: 你问网管:“有没有川菜菜单?”网管把川菜单页递给你。
-
接口二:加入购物车
-
需求分析:用户看中了“鱼香肉丝”,想点一份。
-
接口设计:
-
路径:/user/shoppingCart/add
-
方式:POST
-
参数:dishId(菜品ID)、dishFlavor(口味,比如“微辣”)
-
返回:提示成功或失败。
-
形象解释: 你对网管喊:“帮我记一下,鱼香肉丝,微辣,我要了!”网管拿小本本记下来。
-
接口三:提交订单
-
需求分析:用户选完了,准备结账。
-
接口设计:
-
路径:/user/order/submit
-
方式:POST
-
参数:amount(总价)、addressId(收货地址)、payMethod(支付方式)、remark(备注,比如“不要香菜”)
-
返回:订单ID、预支付信息。
-
形象解释: 你拿着小本本去前台:“就这些,算账,我用微信支付。”
-
接口四:商家接单
-
需求分析:网管/后厨看到新订单,准备制作。
-
接口设计:
-
路径:/admin/order/confirm
-
方式:PUT
-
参数:orderId(订单ID)
-
返回:操作成功。
-
形象解释: 网管扫了一眼电脑屏幕,点了一下“确认接单”,厨房打印机开始出票。
-
第三部分:设计规范与原则(画图纸的规矩)
为了保证画出来的图纸大家都能看懂,苍穹外卖通常会遵循一些规范:
RESTful 风格:
-
用请求方式区分动作。
-
GET /orders(查列表)
-
POST /orders(新建)
-
GET /orders/{id}(查某个具体订单)
-
PUT /orders/{id}(修改某个具体订单)
路径前缀:
-
/user/:给小程序用户用的接口。
-
/admin/:给后台管理员用的接口。
-
这样做便于区分权限。形象解释: 顾客走前门(/user),员工走后门(/admin)。
统一返回结果:
-
所有接口都返回固定格式的数据,比如:
json
{
"code": 1, // 1成功,0失败
"msg": "操作成功", // 提示信息
"data": { … } // 真正的数据
}
JWT Token 验证:
-
除了登录接口,其他接口都需要在请求头里带上 token(刚才说的网吧会员卡),用来识别身份。
总结
-
需求分析是“做什么”——决定菜单上有鱼香肉丝,而且可以选辣度。
-
接口设计是“怎么做”——定义清楚顾客怎么点餐(传什么参数,走哪个窗口)。
在苍穹外卖中,当我们看到类似这样的结构时,就能对应起来了:
-
需求:用户登录 -> 接口:POST /user/user/login (参数:code)
-
需求:管理员新增菜品 -> 接口:POST /admin/dish (参数:json格式的菜品信息,头:token)
小程序登录的解释:
第一步:出示身份证(获取code)
你走到前台,对网管说:“老板,开台机子。” 网管不会直接给你开机,他会先问你:“身份证带了吗?” 你把身份证递给他。
对应技术动作:
-
你 = 微信小程序
-
身份证 = wx.login() 接口
-
网管要身份证 = 小程序调用微信官方接口,获取一个临时的、一次性的凭证,叫做 code。
第二步:网管去公安局系统核验(后端请求微信服务器)
网管拿到你的身份证后,他不会凭肉眼看看就相信你,他会把身份证号输入到 公安内部系统 里进行查询。 这个公安系统里记录了你的真实姓名、年龄、是否有案底等信息。 公安系统验证通过后,会给网管返回一个确认信息:“此人真实有效,可以上网。”
对应技术动作:
-
网管 = 苍穹外卖的后端服务器(Java程序)
-
公安系统 = 微信服务器
-
输入身份证号查询 = 后端拿着小程序传来的 code,加上自己的 AppID 和 AppSecret(相当于派出所的接入密码),去请求微信服务器。
-
返回确认信息 = 微信服务器返回 openid(用户的唯一身份证号)和 session_key(会话密钥)。
第三步:网管给你办一张“网吧会员卡”(下发自定义登录态)
公安系统确认你是好公民后,网管不会每次都让你把身份证拿出来刷,那样太慢了。 所以,网管会在他的网吧系统里,给你建立一个档案(如果第一次来的话),然后发给你一张 “网吧临时会员卡”。 这张卡上有你的信息(比如卡号 9527),但最重要的是,这张卡只有在这家网吧(苍穹外卖)才有效。下次你来,直接刷卡就行了,不用再出示身份证。
对应技术动作:
-
建立档案 = 后端数据库查询 openid,如果没找到,就在 user 表里新注册一条记录。
-
制作会员卡 = 后端根据 user_id 生成一个自定义的登录凭证,比如 jwt_token(JSON Web Token)。这个 token 就相当于网吧的会员卡。
-
递给你卡 = 后端把这个 token 返回给小程序。
第四步:拿着卡去上网(携带token访问业务)
拿到会员卡后,你走到电脑前,刷一下卡(或者输入卡号),电脑解锁,你就可以点炒饭、点可乐了。
对应技术动作:
-
刷一下卡 = 小程序后续请求(比如点餐、查询订单)的请求头中,都会带上这个 token。
-
电脑解锁 = 后端每次收到请求,先验证 token 是不是真的、有没有过期。验证通过,就知道你是哪个用户了(user_id 是多少),然后才给你处理具体的业务逻辑(比如把订单关联到你的ID上)。
总结一下这个流程的要点:
为什么不用微信直接登录? 因为安全问题。直接传微信号太危险,所以微信设计了一个 “介绍信机制”:小程序拿着 code(介绍信)给后端,后端拿着 code 去微信官方那里换 openid(用户真实身份)。这样 openid 永远只保存在后端,小程序端是拿不到的,保证了安全。
苍穹外卖的具体实现:
-
Controller 接收:LoginController 接收小程序传来的 code。
-
Service 调用微信 API:LoginService 调用 WeChatUtil 的工具类,通过 code 请求微信的 jscode2session 接口,拿到 openid。
-
数据库操作:根据 openid 查询 user 表,如果没有就创建新用户。
-
生成 JWT:用用户的ID生成一个 JWT Token。
-
返回结果:将 token 返回给小程序,小程序存起来,后续请求都带上。
这样一来,小程序用户就在苍穹外卖系统里完成了一次丝滑的匿名转实名的登录过程。
微信登录在小程序里扮演什么角色?
微信登录,就是这个小程序平板用来确认你是谁的一种方式。
没有微信登录会怎样?
你坐在餐桌前,点了一堆菜,然后平板问:"请先登录"。 你说:"怎么登?" 平板说:"输入用户名密码,或者手机号注册一下。" 你心想:"吃个饭还要注册?太麻烦了,换一家吧。"
这就是没有微信登录的问题——用户流失率会非常高。
有了微信登录会怎样?
你打开平板,上面只有一个按钮:"微信一键登录"。 你点一下,屏幕跳转到微信授权页面,你点"允许",咻的一下,你就登录成功了,直接开始点菜。
所以,微信登录 = 让用户不用注册,直接使用微信身份进来的"快速通道"。
两者结合,具体怎么工作?
还是用刚才点餐平板的场景:
| 1 | 你打开平板,小程序显示一个"微信登录"按钮 | — | 等着接收请求 |
| 2 | 你点按钮,小程序调用 wx.login() 接口,拿到一个临时的 code | 微信告诉你:"这是这个用户的临时身份证" | — |
| 3 | 小程序把这个 code 发给自己家的后端 | — | 后端拿着 code 去问微信:"这个 code 对应谁?" |
| 4 | — | 微信告诉后端:"这是 openid=xxx 的用户" | 后端收到 openid,在自己数据库里找或创建用户,生成一张"会员卡"(token) |
| 5 | 小程序收到后端返回的 token,把它存起来 | — | 后端把 token 返回给小程序 |
| 6 | 接下来你点菜、下单,小程序都在请求里带上这个 token | — | 后端看到 token,就知道你是谁,给你处理对应的业务 |
为什么不能没有小程序开发?
你可能会想:"那能不能直接让用户用微信登录,然后就用微信本身点餐?"
这就好比问:"能不能直接用身份证去点菜,不要点餐平板?"
不行,因为:
微信是"通用工具",不是"专用工具"
-
微信能发消息、发朋友圈、支付,但它不知道你的餐厅菜单长什么样。
-
小程序是在微信里跑的一个专门应用,专门为你的餐厅设计的。
界面需要定制
-
你的菜品图片要展示得漂亮,购物车要方便修改,这些都需要小程序开发者来设计和实现。
-
微信本身不会帮你做这些。
业务逻辑需要代码
-
满减优惠怎么算?套餐怎么组合?这些逻辑都在小程序的代码里。
总结一下三者的关系
微信 = 操作系统/平台(像Windows) 小程序 = 运行在微信里的APP(像Word、Excel) 微信登录 = 小程序里用微信身份快速进入的功能
开发者的分工:
-
小程序开发者:负责把界面做漂亮,把用户操作收集起来,发给后端。
-
后端开发者:负责处理业务逻辑,管理数据库,对接微信服务器。
-
微信登录:是连接用户身份和业务系统的一座桥。
所以,在苍穹外卖项目中:
-
小程序开发:让你能看见菜品、点击加入购物车、点击下单。
-
微信登录:让你点击下单时,系统知道这是你下的单,而不是别人。
具体实现:
Cotroller:
package com.sky.controller.user;
import com.sky.constant.JwtClaimsConstant;
import com.sky.dto.UserLoginDTO;
import com.sky.entity.User;
import com.sky.properties.JwtProperties;
import com.sky.result.Result;
import com.sky.service.UserService;
import com.sky.utils.JwtUtil;
import com.sky.vo.UserLoginVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/user/user")
@Api(tags = "C端用户相关接口")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@Autowired
private JwtProperties jwtProperties;
/**
* 微信登录
* @param userLoginDTO
* @return
*/
@GetMapping("/login")
@ApiOperation("微信登录")
public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO){
log.info("微信用户登录:{}",userLoginDTO);
//微信登录
User user = userService.wxLogin(userLoginDTO);
//为用户生成jwt令牌
Map<String, Object> claims=new HashMap<>();
claims.put(JwtClaimsConstant.USER_ID,user.getId());
String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims);
UserLoginVO userLoginVO=UserLoginVO.builder()
.id(user.getId())
.openid(user.getOpenid())
.token(token)
.build();
return Result.success(userLoginVO);
}
}
Service:
@Service
@Slf4j
public class UserServiceImpl implements UserService {
@Autowired
private WeChatProperties weChatProperties;
@Autowired
private UserMapper userMapper;
//微信接口的地址
public static final String WX_LOGIN="https://api.weixin.qq.com/sns/jscode2session";
/**
* 微信登录
* @param userLoginDTO
* @return
*/
public User wxLogin(UserLoginDTO userLoginDTO) {
//具体怎么实现的操作
String openid = getOpenid(userLoginDTO.getCode());
//判断openid是否为空,如果为空,则登录失败,抛出业务异常
if (openid==null){
throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
}
//判断当前用户是否为新用户
User user = userMapper.getByOpenid(openid);
//如果是新用户,自动完成注册,相当于就是在数据库中插入没有的数据
if (user==null){
user= User.builder()
.openid(openid)
.createTime(LocalDateTime.now())
.build();
userMapper.insert(user);
}
return null;
}
private String getOpenid(String code) {
//调用微信接口的服务,获得当前用户的openid,使用前面学习的httpclient
Map<String, String> map=new HashMap<>();
//把接口文档需要的四个参数封装到map集合中
map.put("appid",weChatProperties.getAppid());
map.put("secret",weChatProperties.getSecret());
map.put("js_code", code);
map.put("grant_type","authorization_code");
String json = HttpClientUtil.doGet(WX_LOGIN, map);
JSONObject jsonObject = JSON.parseObject(json);
String openid = jsonObject.getString("openid");
return openid;
}
}
Mapper:
package com.sky.mapper;
import com.sky.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
/**
* 查询是否为新用户
* @param openid
* @return
*/
@Select("select *from sky_take_out.user where openid=#{openid}")
User getByOpenid(String openid);
/**
* 插入数据,还要返回主键值要用到
* @param user
*/
void insert(User user);
}

结语:如果对你有帮助,请点赞,关注,收藏!



