Day69(6)-F:\硕士阶段\Java\课程资料\1、黑马程序员Java项目《苍穹外卖》企业级开发实战\sky-take-out-微信登录

苍穹外卖

微信登录

HttpClient

image-20251225104612768

aliyun-sdk-oss底层已经传递过来了这个http的jar包

response对象接收响应报文,statuscode是响应报文的响应行中的响应状态码,entity是响应体的

技术栈

  1. httpClient对象的创建
  2. JSON格式对象类的创建和使用,在httpClient中发送包含json的Entity请求
1.httpClient对象的创建(如28行),开启后最后要关闭(如47-48行)
2.JSON格式对象类的创建和使用(62-71行),在httpClient中发送包含json的Entity请求
package com.sky.test;

import org.apache.http.HttpEntity;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

@SpringBootTest
public class HttpClientTest {

    /**
     * 测试通过HttpClient发送GET方式的请求
     */
    @Test
    public void testGet() throws IOException {
        //创建httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        //创建请求对象
        HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");


        //发送请求,接收响应结果
        CloseableHttpResponse response = httpClient.execute(httpGet);

        //获取服务端返回的状态码
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println("服务端返回的状态码为:" + statusCode);

        HttpEntity entity = response.getEntity();
        //解析entity数据
        String body = EntityUtils.toString(entity);
        System.out.println("服务端返回的数据为:"+body);

        //关闭资源
        response.close();
        httpClient.close();
    }

    /**
     * 测试通过HttpClient发送POST方式的请求
     */
    @Test
    public void testPost() throws Exception {
        //创建httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        //创建请求对象
        HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("username","admin");
        jsonObject.put("password","123456");

        StringEntity entity = new StringEntity(jsonObject.toString());
        //指定编码方式
        entity.setContentEncoding("utf-8");
        //指定传输数据格式
        entity.setContentType("application/json");
        httpPost.setEntity(entity);

        //发送请求
        CloseableHttpResponse response = httpClient.execute(httpPost);

        //解析返回结果
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println("响应码为:"+statusCode);

        HttpEntity entity1 = response.getEntity();
        //解析entity数据
        String body1 = EntityUtils.toString(entity1);
        System.out.println("响应数据为:"+body1);

        //关闭资源
        response.close();
        httpClient.close();

    }
}

在sky-common下面的utils包里面有com.sky.utils.HttpClientUtil

微信小程序开发

image-20251225120906965

image-20251225121018277

image-20251225121149510

https://mp.weixin.qq.com/wxopen/waregister?action=step1

image-20251225123305232

下载小程序开发工具

https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html

创建开发页面

image-20251225124220453

当前版本是2.01.5

加载完之后需要:设置---项目设置---本地设置---勾选⬇️不校验合法域名

image-20251225124521683

小程序目录结构

image-20251225124749191

json是存放配置的

wxss相当于前端的css

image-20251225124958666

wxml是页面效果展示

app.json

{

"pages": [

"pages/index/index",

"pages/logs/logs"

],

"window": {

"navigationBarTextStyle": "black",

"navigationBarTitleText": "Weixin",

"navigationBarBackgroundColor": "#ffffff"

},

"style": "v2",

"componentFramework": "glass-easel",

"sitemapLocation": "sitemap.json",

"lazyCodeLoading": "requiredComponents"

}

技术栈

  1. data中数据与wxml的双向绑定
  2. wx.getUserProfile是微信官方设计的方法会返回一个包裹里面包含多种信息
  3. 微信授权码wx.login获取res.code
  4. 发送异步请求
1.data中数据与wxml的双向绑定

data: {

msg: 'hello world',

motto: 'Hello World',

userInfo: {

avatarUrl: defaultAvatarUrl,

nickName: '',

url: '',

},

2.wx.getUserProfile是微信官方设计的方法会返回一个包裹里面包含多种信息

以下是wx.getUserProfile返回的完整对象结构:

属性名 类型 作用
userInfo 目的 用户公开资料(头像、昵称、性别、地区等)。
rawData 细绳 它是userInfo对象的字符串形式。
signature 细绳 签名。用于校验数据没有被中间人篡改(通常发给远程验证)。
encryptedData 细绳 加密数据。包含更敏感的信息(如OpenID、UnionID),需要发给解密。
iv 细绳 加密偏移量。唤醒在解密encryptedData时必须占用的按键。

//获取微信用户的头像和昵称

getUserInfo(){

wx.getUserProfile({

desc: '获取用户信息',

success:(res) =>{

​ console.log(res.userInfo)

​ //为数据赋值

​ this.setData({

​ nickName: res.userInfo.nickName,

​ url: res.userInfo.avatarUrl

​ })

}

})

},

3.微信授权码wx.login获取res.code

//微信登录,获取微信登录的授权码

wxlogin(){

wx.login({

success :(res)=>{

​ console.log(res.code)

​ this.setData({

​ code: res.code

​ })

}

})

},

微信登录

导入小程序代码

技术栈:

  1. 修改前端请求的url:在common下的vendor.js
  2. 后端的登录接口,记得修改yml配置
  3. 前端传递来后端的数据后,后端处理并生产jwt令牌返回
  4. 设置拦截器校验jwt
1.修改前端请求的url:在common下的vendor.js

image-20251225142759571

微信登录流程

image-20251225142912508

登录凭证校验接口

image-20251225144827971

https://api.weixin.qq.com/sns/jscode2session
2.后端的登录接口,修改yml配置

image-20251225145422393

image-20251225145601462

image-20251225145757862

image-20251225145921881

image-20251225145931480

yml配置修改为(10-12,19-20行)

sky:
  jwt:
    # 设置jwt签名加密时使用的秘钥
    admin-secret-key: itcast
    # 设置jwt过期时间
    admin-ttl: 7200000
    # 设置前端传递过来的令牌名称
    admin-token-name: token
    #阿里云OSS
    user-secret-key: itheima
    user-ttl: 7200000
    user-token-name:  authentication
  alioss:
    endpoint: ${sky.alioss.endpoint}
    access-key-id: ${sky.alioss.access-key-id}
    access-key-secret: ${sky.alioss.access-key-secret}
    bucket-name: ${sky.alioss.bucket-name}
  wechat:
    appid: ${sky.wechat.appid}
    secret: ${sky.wechat.secret}
3.前端传递来后端的数据后,后端处理并生产jwt令牌返回(重点)
微信登录的Controller
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.PostMapping;
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;

@RestController
@RequestMapping("/user/user")
@Api(tags = "C端用户模块")
@Slf4j
public class UserController {
    @Autowired
    private UserService userService;
    @Autowired
    private JwtProperties jwtProperties;

    @PostMapping("/login")
    @ApiOperation("微信登录")
    public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO){
        log.info("微信用户登录:{}",userLoginDTO.getCode());

        //微信登录
        User user = userService.wxlogin(userLoginDTO);

        //为微信用户生成jwt令牌
        HashMap<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

微信官方指出要传递给的信息,验证通过之后才能回复对应的openid

image-20251225160457234

返回是一个json包

image-20251225161242787

package com.sky.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.constant.MessageConstant;
import com.sky.dto.UserLoginDTO;
import com.sky.entity.User;
import com.sky.exception.LoginFailedException;
import com.sky.mapper.UserMapper;
import com.sky.properties.WeChatProperties;
import com.sky.service.UserService;
import com.sky.utils.HttpClientUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.HashMap;

@Service
@Slf4j
public class UserServiceimpl implements UserService {

    //微信服务接口地址
    public static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";

    @Autowired
    private WeChatProperties weChatProperties;

    @Autowired
    private UserMapper userMapper;
    /**
     * 微信登录
     * @param userLoginDTO
     * @return
     */
    @Override
    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 user;
    }

    /**
     * 调用微信接口服务,获取对应的openid
     * @param code
     * @return
     */
    private String getOpenid(String code){
        //调用微信接口服务,获得当前微信用户的openid
        HashMap<String, String> map = new HashMap<>();
        //微信官方的文档里面指出了要传递什么信息
        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);
        //解析返回的json
        JSONObject jsonObject = JSON.parseObject(json);
        String openid = jsonObject.getString("openid");
        return openid;
    }
}
微信登录的Mapper和对应xml
package com.sky.mapper;

import com.sky.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface UserMapper {

    /**
     * 根据openid查询用户
     * @param openid
     * @return
     */
    @Select("select * from user where openid = #{openid}")
    User getByOpenid(String openid);

    /**
     * 插入数据
     * @param user
     */
    void insert(User user);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.UserMapper">


    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into user (openid, name, phone, sex, id_number, avatar, create_time)
            values
        (#{openid},#{name},#{phone},#{sex},#{idNumber},#{avatar},#{createTime})
    </insert>


</mapper>
4.设置拦截器校验jwt
编译拦截器修改点(47、52、53行)
package com.sky.interceptor;

import com.sky.constant.JwtClaimsConstant;
import com.sky.context.BaseContext;
import com.sky.properties.JwtProperties;
import com.sky.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties;


    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("当前线程的id:"+Thread.currentThread().getId());

        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getUserTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
            Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
            log.info("当前用户id:", userId);
            BaseContext.setCurrentId(userId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            log.error("JWT解析发生极其严重的错误!!原因:{}", ex.getMessage());
            ex.printStackTrace(); // 这行非常重要,它会告诉你到底是密钥错了还是过期了
            response.setStatus(401);
            return false;
        }
    }
}
注册拦截器

在WebMvcConfiguration配置类中注册(35、48-51行)

package com.sky.config;

import com.sky.interceptor.JwtTokenAdminInterceptor;
import com.sky.interceptor.JwtTokenUserInterceptor;
import com.sky.json.JacksonObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.List;

/**
 * 配置类,注册web层相关组件
 */
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    @Autowired
    private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
    
    @Autowired
    private JwtTokenUserInterceptor jwtTokenUserInterceptor;

    /**
     * 注册自定义拦截器
     *
     * @param registry
     */
    protected void addInterceptors(InterceptorRegistry registry) {
        log.info("开始注册自定义拦截器...");
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/admin/**")
                .excludePathPatterns("/admin/employee/login");
        
        registry.addInterceptor(jwtTokenUserInterceptor)
                .addPathPatterns("/user/**")
                .excludePathPatterns("/user/user/login")
                .excludePathPatterns("/user/shop/status");
    }

    /**
     * 通过knife4j生成接口文档
     * @return
     */
    @Bean
    public Docket docket1() {
        log.info("准备生成接口文档");
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .groupName("管理端接口")
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

    @Bean
    public Docket docket2() {
        log.info("准备生成接口文档");
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .groupName("用户端接口")
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller.user"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

    /**
     * 设置静态资源映射
     * @param registry
     */
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始设置静态资源映射");
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    /**
     * 扩展Spring MVC的消息转换器
     * @param converters
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("扩展消息转换器");
        //创建一个消息转换器
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        //需要为消息转换器设置一个对象转换器,对象转换器可以将java对象序列化为json数据
        converter.setObjectMapper(new JacksonObjectMapper());
        //将自己的消息转换器加入容器中,并通过索引提升自己消息转换器的优先级
        converters.add(0,converter);
    }
}

导入商品流览业务功能

image-20251225165245227

image-20251225165251205

image-20251225165405169

image-20251225165647219

image-20251225165745700

image-20251225170010481

posted @ 2025-12-25 17:43  David大胃  阅读(2)  评论(0)    收藏  举报