Springboot は、いくつかの一般的なログイン (登録) メソッドを実装しています

1. ユーザー登録(携帯電話認証コード)

現在、携帯電話番号の収集は、ユーザープロモーションやビジネスプロモーションに非常に価値があるため、携帯電話番号登録が主流の方法です。前回の記事と組み合わせて、Aliyun SMS サービスを使用して携帯電話認証コードを送信し、ここでは統合により携帯電話番号によるユーザー登録を実現します。

アイデア: 携帯電話番号を入力した後、フロントエンドは携帯電話番号を検証する必要があります. ユーザーは SMS 検証コードを受信し、検証コード検証を完了する必要があります. 具体的な手順:
1. 現在の携帯電話番号が登録されているかどうかを判断する;
2. Alibaba Cloud SMS サービス API を呼び出して確認コードを送信する;
3. 確認コードが正常に送信され、redis キャッシュに保存され、確認コードがキャッシュ消去の仕組み(有効期限の設定)を利用することで実現 有効期限切れ;
4.認証コードの認証を通過すれば登録成功。

1.1 ユーザーエンティティクラス

package com.zhmsky.service_ucenter.entity;

import com.baomidou.mybatisplus.annotation.*;

import java.util.Date;

import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * <p>
 * 会员表
 * </p>
 *
 * @author zhmsky
 * @since 2022-07-16
 */
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="UcenterMember对象", description="会员表")
public class UcenterMember implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "会员id")
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private String id;

    @ApiModelProperty(value = "微信openid")
    private String openid;

    @ApiModelProperty(value = "手机号")
    private String mobile;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "昵称")
    private String nickname;

    @ApiModelProperty(value = "性别 1 女,2 男")
    private Integer sex;

    @ApiModelProperty(value = "年龄")
    private Integer age;

    @ApiModelProperty(value = "用户头像")
    private String avatar;

    @ApiModelProperty(value = "用户签名")
    private String sign;

    @ApiModelProperty(value = "是否禁用 1(true)已禁用,  0(false)未禁用")
    private Boolean isDisabled;

    @ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
    private Boolean isDeleted;

    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.UPDATE)
    private Date updateTime;

}

1.2 ユーザービューオブジェクトの登録

package com.zhmsky.service_ucenter.entity.VO;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author zhmsky
 * @date 2022/7/16 17:18
 */
@Data
@ApiModel(value="注册对象", description="注册对象")
public class RegisterVO {
    
    

    @ApiModelProperty(value = "昵称")
    private String nickname;

    @ApiModelProperty(value = "手机号")
    private String mobile;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "验证码")
    private String code;
}

1.3 SMS認証コードの送信

Alibaba Cloud SMS サービスを呼び出して SMS 確認コードを送信する

package com.zhmsky.msmService.service.impl;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.profile.DefaultProfile;
import com.zhmsky.msmService.service.MsmService;
import org.springframework.stereotype.Service;

/**
 * @author zhmsky
 * @date 2022/7/6 21:01
 * 短信验证码实现类
 */
@Service
public class MsmServiceImpl implements MsmService {
    
    

    /**
     * 发送短信验证码
     * @param phone 手机号
     * @param code 被发送的验证码
     * @return
     */
    @Override
    public String sendCodeMsg(String phone, String code) {
    
    
        String checkCode="";
        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "LTAI5tDfHQPQ5WA5dBkrfFxu", "eRID7ZigveAH7fMRKbCDq92jjRl68R");


        IAcsClient client = new DefaultAcsClient(profile);

        SendSmsRequest request = new SendSmsRequest();
        request.setSignName("阿里云短信测试");
        request.setTemplateCode("SMS_154950909");
        request.setPhoneNumbers(phone);
        request.setTemplateParam("{\"code\":\""+code+"\"}");

        try {
    
    
            SendSmsResponse response = client.getAcsResponse(request);
            checkCode = response.getCode();
        } catch (ServerException e) {
    
    
            e.printStackTrace();
        } catch (ClientException e) {
    
    
            System.out.println("ErrCode:" + e.getErrCode());
            System.out.println("ErrMsg:" + e.getErrMsg());
            System.out.println("RequestId:" + e.getRequestId());
        }
        return checkCode;
    }
}

SMS 確認コード送信インターフェース

package com.zhmsky.msmService.controller;

import com.zhmsky.msmService.service.MsmService;
import com.zhmsky.msmService.utils.RandomUtil;
import com.zhmsky.result.Result;
import com.zhmsky.result.ResultUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author zhmsky
 * @date 2022/7/6 20:58
 */
@RestController
@Api("短信注册控制器")
@CrossOrigin
@RequestMapping("/msmService")
public class MsmController {
    
    

    @Autowired
    private MsmService msmService;
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @GetMapping("/send/{phone}")
    @ApiOperation("发送短信验证码")
    public Result<String> sendMsg(@PathVariable String phone){
    
    
        //通过redis设置缓存时间实现验证码过期
        String code = redisTemplate.opsForValue().get(phone);
        if(!StringUtils.isEmpty(code)){
    
    
            return new ResultUtil<String>().setSuccessMsg("请勿重复发送!");
        }
        /**  如果缓存里面没有那么就重新发送   **/
        //生成随机验证码
        String fourBitRandom = RandomUtil.getFourBitRandom();
        //调用阿里云api实现短信发送
        String checkCode = msmService.sendCodeMsg(phone, fourBitRandom);
        if("OK".equals(checkCode)){
    
    
            //将验证码保存到redis中并设置有效时间为5分钟
            redisTemplate.opsForValue().set(phone,fourBitRandom,5, TimeUnit.MINUTES);
            return new ResultUtil<String>().setData(fourBitRandom,"验证码发送成功!");
        }else{
    
    
            return new ResultUtil<String>().setErrorMsg("验证码发送给失败!");
        }
    }

}

1.4 登録機能の実現

1.インターフェース保護、パラメータ非空判定
2.携帯電話番号登録の有無確認
3.認証コード確認
4.保存

 /**
     * 用户注册
     * @param registerVO
     * @return
     */
    @Override
    public boolean register(RegisterVO registerVO) {
    
    
        //获取注册数据,接口保护,参数校验
        String code = registerVO.getCode();
        String mobile = registerVO.getMobile();
        String nickname = registerVO.getNickname();
        String password = registerVO.getPassword();

        if(StringUtils.isEmpty(mobile)||StringUtils.isEmpty(password)||StringUtils.isEmpty(code)||StringUtils.isEmpty(nickname)){
    
    
            throw new MyException(20005,"注册失败!");
        }

        //判断手机号是否已注册
        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile",mobile);
        Long count = baseMapper.selectCount(wrapper);
        if(count>0){
    
    
            throw new MyException(20005,"注册失败!");
        }

        //验证码校验
        //先从redis中获取验证码
        String cacheCode = redisTemplate.opsForValue().get(mobile);
        if(!code.equals(cacheCode)){
    
    
           throw new MyException(20005,"注册失败!");
        }

        //入库
        UcenterMember ucenterMember = new UcenterMember();
        ucenterMember.setMobile(mobile);
        ucenterMember.setNickname(nickname);
        ucenterMember.setPassword(MD5.encrypt(password));
        ucenterMember.setIsDisabled(false);
        ucenterMember.setAvatar("http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132");
        int insert = baseMapper.insert(ucenterMember);
        if(insert>0){
    
    
            return true;
        }else{
    
    
            return false;
        }

    }

2. ユーザーログイン

2.1 アカウントパスワードログイン

ログイン プロセス:
1. ログイン インターフェイスを呼び出し、トークン文字列を返します。 2. 返されたトークン文字列を Cookie に入れます

4. ヘッダーでインターフェースを
呼び出し、トークンに応じてユーザー情報を取得し、再度 Cookie に入れる (最初のページの表示用)
5. Cookie からユーザー情報を取り出して表示する
アイデア:
1 . インターフェース保護、パラメーター非 null チェック;
2. アカウントが登録されていることを確認します;
3. アカウントが無効であることを確認します;
4. パスワードを確認します;
5. ログインしてトークンを返します。

シングル サインオンを実現するために、トークン トークン メソッドを使用して jwt
jwt ツール クラスを導入します。

package com.zhmsky.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * @author zhmsky
 * @date 2022/7/6 17:53
 */
public class JwtUtils {
    
    
    //设置token过期时间
    public static final long EXPIRE = 1000 * 60 * 60 * 24;    //一天
    //签名加密密钥
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    /**
     * 生成token
     * @param id
     * @param nickname
     * @return
     */
    public static String getJwtToken(String id, String nickname){
    
    

        String JwtToken = Jwts.builder()
                //设置头信息
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                //设置token过期时间
                .setSubject("user")   //随便写
                .setIssuedAt(new Date())
                //当前时间加上设置的过期时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                //设置token主体,可存储用户信息
                .claim("id", id)
                .claim("nickname", nickname)
                //签名哈希
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {
    
    
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
    
    
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
    
    
        try {
    
    
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token获取会员id
     * @param request
     * @return
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
    
    
        String jwtToken = request.getHeader("token");
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

ログイン事業:

 /**
     * 用户登录
     * @param member
     * @return token
     */
    @Override
    public String login(UcenterMember member) {
    
    
       //获取账号和密码
        String mobile = member.getMobile();
        String password = member.getPassword();
       if(StringUtils.isEmpty(mobile)||StringUtils.isEmpty(password)){
    
    
           throw new MyException(20010,"账号和密码不能为空!");
       }
       //判断账号和密码是否存在
        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile",mobile);
        UcenterMember ucenterMember = baseMapper.selectOne(wrapper);
        if(ObjectUtils.isEmpty(ucenterMember)){
    
    
            throw new MyException(20011,"账号不存在,请重新输入!");
        }
        //判断该用户是否被禁用
        Boolean isDisabled = ucenterMember.getIsDisabled();
        if(isDisabled){
    
    
            throw new MyException(20013,"该账号已禁用!");
        }
        //判断密码是否正确
        //密码存储肯定是加密的,实际开发中数据库不会存储明文密码
        //先将输入的密码加密,再和数据库密码比较
        //MD5加密
        String realPassword = ucenterMember.getPassword();
        if(!MD5.encrypt(password).equals(realPassword)){
    
    
            throw new MyException(20012,"密码错误,请重新输入!");
        }
        //登录成功,返回token(通过查出来的用户数据去生成token)
        return JwtUtils.getJwtToken(ucenterMember.getId(), ucenterMember.getNickname());
    }

ログイン インターフェイス:

    @PostMapping ("/login")
    @ApiOperation("用户登录")
    public Result<String> login(@RequestBody(required = false) UcenterMember ucenterMember){
    
    
        //登录生成token并返回
        String token = memberService.login(ucenterMember);
        return new ResultUtil<String>().setData(token);
    }

ログインが成功した後、フロントエンドの各リクエストはトークンを運び、トークンはリクエスト オブジェクトから取得され、トークンが解析されてユーザー情報が取得されます。

    @GetMapping("/getUserInfo")
    @ApiOperation("根据token获取用户信息")
    public Result<UcenterMember> getUserInfo(HttpServletRequest httpServletRequest){
    
    
        //从request对象中获取token,再根据token获取用户信息
        String userId = JwtUtils.getMemberIdByJwtToken(httpServletRequest);
        //根据用户id获取用户信息
        UcenterMember ucenterMember = memberService.getById(userId);
        return new ResultUtil<UcenterMember>().setData(ucenterMember);
    }

2.2 WeChat スキャン コード ログイン

2.2.1 準備:

ウェブアプリWeChatログインは、OAuth2.0プロトコル規格に基づくWeChat OAuth2.0認証ログインシステムです。
WeChat OAuth2を実行する前にWeChat OAuth2.0認証ログインアクセスを実行する前に、WeChatオープンプラットフォームに開発者アカウントを登録し、承認されたWebサイトアプリケーションを持っており、対応するAppIDとAppSecretを取得し、WeChatログインを申請して審査に合格しますその後、アクセスプロセスを開始できます。

2.2.2 認可プロセス

  1. サード パーティが WeChat 認証ログイン リクエストを開始します. WeChat ユーザーがサード パーティ アプリケーションの認証を許可した後、WeChat はアプリケーションを起動するか、サード パーティの Web サイトにリダイレクトし、認証一時チケット コード パラメータを取得します。
  2. コード パラメーターなどを介して AppID と AppSecret を追加し、API を介して access_token と交換します。
  3. access_token を使用してインターフェイス呼び出しを行い、基本的なユーザー データ リソースを取得したり、ユーザーが基本的な操作を実装できるようにします。
    ここに画像の説明を挿入
    ステップ 1: WeChat QR コードの生成をリクエストする
    ここに画像の説明を挿入
    公式文書に従って、WeChat オープン プラットフォームの固定アドレスを直接リクエストしますhttps://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state =STATE#wechat_redirect、4 つの必要なパラメーターがあります。

appid: WeChat オープン プラットフォーム アプリケーションが承認された後に発行される一意の識別子
redirect_uri: WeChat スキャン コード承認後のコールバック アドレス
response_type: コード
スコープ: 承認スコープ、Web アプリケーションの場合は snsapi_login を入力するだけです

上記パラメータを用意し、プロパティファイルに初期値設定を完了

wx.open.app_id=wxed9954c01bb89b47
wx.open.app_secret=a7482517235173ddb4083788de60b90e
wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback

パラメータ初期化ツール クラスを記述します。

package com.zhmsky.service_ucenter.utils;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author zhmsky
 * @date 2022/7/17 17:06
 */
@Component
public class ConstWxUtil implements InitializingBean {
    
    

    @Value("${wx.open.app_id}")
    private String appId;

    @Value("${wx.open.app_secret}")
    private String appSecret;

    @Value("${wx.open.redirect_url}")
    private String redirectUrl;

    public static String WX_OPEN_APP_ID;
    public static String WX_OPEN_APP_SECRET;
    public static String WX_OPEN_REDIRECT_URL;

    @Override
    public void afterPropertiesSet() throws Exception {
    
    
        WX_OPEN_APP_ID = appId;
        WX_OPEN_APP_SECRET = appSecret;
        WX_OPEN_REDIRECT_URL = redirectUrl;
    }
}

二次元コード生成インターフェース:

    @GetMapping("/getWxCode")
    @ApiOperation("生成微信二维码")
    public String getWxCode() {
    
    
        // 微信开放平台授权baseUrl
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=%s" +
                "#wechat_redirect";

        // 回调地址
        String redirectUrl = ConstWxUtil.WX_OPEN_REDIRECT_URL;
        try {
    
    
            redirectUrl = URLEncoder.encode(redirectUrl, "UTF-8"); //url编码
        } catch (UnsupportedEncodingException e) {
    
    
            throw new MyException(20001, e.getMessage());
        }
        String state = "imhelen";
        //生成qrcodeUrl
        String qrcodeUrl = String.format(
                baseUrl,
                ConstWxUtil.WX_OPEN_APP_ID,
                redirectUrl,
                state);

        return "redirect:" + qrcodeUrl;
    }

ステップ 2: WeChat で QR コードをスキャンした後、コールバックを実行する

    @GetMapping("/callback")
    @ApiOperation("微信扫码确认后执行回调")
    public String callback(String code,String state){
    
    
        //TODO 
        return "redirect:http://localhost:3000";
    }

次に、
ここに画像の説明を挿入
ランダム コードを取得し、WeChat リソース サーバーの固定アドレスを要求して accessToken (アクセス資格情報) と openId (一意のユーザー ID) を取得します。次に、accessToken と openId を介して WeChat リソース サーバーの固定アドレスを要求します。コードスキャナーの基本情報を取得します。利用者の基本情報を取得後、本人確認を行い、保管等を完了させます。

   @GetMapping("/callback")
    @ApiOperation("微信扫码确认后执行回调")
    public String callback(String code,String state){
    
    
        //1、接收code值
        //用code去请求微信的固定地址,得到accessToken和openId
        //向认证服务器发送请求换取access_token
        String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                "?appid=%s" +
                "&secret=%s" +
                "&code=%s" +
                "&grant_type=authorization_code";

        //带参数的真实认证服务器请求地址
        String accessTokenUrl = String.format(baseAccessTokenUrl,
                ConstWxUtil.WX_OPEN_APP_ID,
                ConstWxUtil.WX_OPEN_APP_SECRET,
                code
        );
        //2、请求认证服务器获取接口调用凭证access_token和用户唯一标识openId
        try {
    
    
            String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
            //将上述json字符串转换成map对象
            /*
            {
                "access_token":"ACCESS_TOKEN",
                "expires_in":7200,
                "refresh_token":"REFRESH_TOKEN",
                "openid":"OPENID",
                "scope":"SCOPE",
                "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
             }
             */
            Gson gson = new Gson();
            HashMap mapAccessTokenInfo = gson.fromJson(accessTokenInfo, HashMap.class);
            //取出的access_token
            String access_token = (String)mapAccessTokenInfo.get("access_token");
            //取出的openid
            String openid = (String)mapAccessTokenInfo.get("openid");

            //3、再通过获取出来的access_token和openid去请求微信开放平台服务器获取扫码人信息
            //访问微信的资源服务器,获取用户信息
            String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                    "?access_token=%s" +
                    "&openid=%s";
            String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
            //发送请求获取扫码人基本信息
            String userInfo = HttpClientUtils.get(userInfoUrl);
            /*
              {
    "openid":"o3_SC5_eI--mIC9ikI2pvTuZhYnk",
    "nickname":"Kong",
    "sex":0,
    "language":"",
    "city":"",
    "province":"",
    "country":"",
    "headimgurl":"https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/hAqkcbxzEJzic0WYl9pHDglAvYBI4iagLsSLXb2ialcxa3Au6UmwibSiadGMtbQia0oAzmzq26k2f1ES4q1HbS6aIYuA\/132",
    "privilege":[],
    "unionid":"oWgGz1OqVll-tTU4R_DM_zRp7Rjc"
                }
             */
            //将上面的json字符串转换成map对象
            HashMap mapUserInfo = gson.fromJson(userInfo, HashMap.class);
            //扫码人基础信息
            String nickname = (String)mapUserInfo.get("nickname");
            String headImgurl=(String)mapUserInfo.get("headimgurl");
            String openId=(String)mapUserInfo.get("openid");

            //扫码后自动注册(入库)
            //先判断是否已注册
            boolean isExist = memberService.getUserOpenId(openId);
            if(!isExist){
    
    
                //入库
                UcenterMember member = new UcenterMember();
                member.setNickname(nickname);
                member.setOpenid(openid);
                member.setAvatar(headImgurl);
                memberService.save(member);
            }
            UcenterMember ucenterMember = memberService.getUserByOpenId(openId);
            String token = JwtUtils.getJwtToken(ucenterMember.getId(), ucenterMember.getNickname());
            //因为端口号不同存在跨域问题,cookie不能跨域,所以这里使用url重写
            return "redirect:http://localhost:3000?token="+token;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            throw new MyException(20010,"登录失败!");
        }
    }

まとめ:
WeChat認証ログインの実装は宝箱を開けるようなもの. 合計3つのキーが必要. 最初のキーは appid (これはWeChatオープンプラットフォームに登録および認証され、プラットフォームによって発行される必要があります); 固定アドレスを要求します. appid を介して WeChat QR コードを生成できます; 認証のために QR コードをスキャンした後、ユーザーは 2 番目のキー コード (ランダムな一意の値) を取得します; 次に、コードを介して固定サーバー アドレスを要求し、3 番目のキーの openid (ユーザーの一意の識別子) を取得します。最後に、openid と accessToken を介して固定アドレスを要求することにより、WeChat ユーザーの基本情報を取得します。

おすすめ

転載: blog.csdn.net/weixin_42194695/article/details/125829378