JAVA开发(基于Restful的微服务第三方简易接口设计)

这篇具有很好参考价值的文章主要介绍了JAVA开发(基于Restful的微服务第三方简易接口设计)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

 

 一、需求背景

java后端需要提供接口服务,其中接口服务分为对内网的后台管理系统的接口,对外网的用户接口和对第三方系统的接口。这里主要讲对第三方的接口。

二、接口设计

我们可以参考微信小程序的接口,一般一个系统提供给第三方系统的接口都需要接口权限认证,也就是先获取token,然后通过token再进行接口数据请求。这是为了保障数据的安全性。这是第三方接口设计的基本规范。

java restful接口开发,java,restful,后端

其中token的获取是通过appid和秘钥等信息去请求微信的后端,这个appid就相当于是一个账号,秘钥就相当于是一个密码。其原理就是相当于只有登录了系统才能请求系统的接口。

那么如果我们不制作token,使用一种简易的方式做接口设计怎么设计呢。

token其实就是一个有实效的令牌,我们简易的做法可以使用登录账号和密码直接登录,每次去请求接口都传输账号和密码就可以了,但是这里需要注意的是传参一定要加密。

三、接口设计实施

1、我们需要在数据库里建一个接口用户表,表字段包含账号和密码字段等。

-- Drop table

-- DROP TABLE public.m_token_user;

CREATE TABLE public.m_token_user (
	id varchar(64) NOT NULL,
	user_name varchar(64) NULL,
	"password" varchar(64) NULL,
	CONSTRAINT m_token_user_pk PRIMARY KEY (id)
);

2、编写校验登录账号代码流程。

实体类:

 

import java.io.Serializable;
import java.util.Date;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
  
 /**
   * 数据库表名称:m_token_user (外部系统用户接口认证表)
   */
@Data
@EqualsAndHashCode()
@TableName(value="m_token_user",schema = "public")
public class MTokenUser implements Serializable{

	/**
	 * 序列
	 */
	private static final long serialVersionUID = 1L;

    /**
     * 
     * 主键:id VARCHAR(64)
     */
    @TableId(value = "id" ,type = IdType.ASSIGN_UUID)
	@ApiModelProperty(value = "ID")
    private String id;
 
    /**
     * 
     * 用户名:user_name VARCHAR(64)
     */
    private String userName;
 
    /**
     * 
     * 密码:password VARCHAR(64)
     */
    private String password;
 
}

Service类:

import cn.ctg.common.util.PageUtils;
import cn.ctg.common.util.Query;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import cn.ctg.member.entity.MTokenUser;
import cn.ctg.member.dao.MTokenUserMapper;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Map;
  
/**
  * @Description: 外部系统用户接口认证Service
  */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class MTokenUserService extends ServiceImpl<MTokenUserMapper, MTokenUser>{

    @Autowired
    private MTokenUserMapper mTokenUserMapper;

 
    /**
     * 根据账号和密码查询
     * searchByUserName
     */
    public MTokenUser searchByUserName(String userName,String  password) {
        QueryWrapper<MTokenUser> wrapper = new QueryWrapper<MTokenUser>();
        
        wrapper.eq("user_name", userName);
        wrapper.eq("password", password);
        MTokenUser mTokenUser = mTokenUserMapper.selectOne(wrapper);
        return mTokenUser;
    }
	
}

RestFul第三方接口:

Controller类示例

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
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 com.alibaba.fastjson.JSON;

import cn.ctg.common.encryption.Des3Utils;
import cn.ctg.common.response.ResponseData;
import cn.ctg.member.dto.WXModelDTO;
import cn.ctg.member.entity.MTokenUser;
import cn.ctg.member.service.MTokenUserService;
import cn.ctg.member.service.WeixinAppService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;

/**
* @Description:
* @date:2023年5月30日
*/
@Slf4j
@RestController
@RequestMapping("rpcApi")
@Api(tags = "对外API")
@Scope(value = "prototype")
public class RpcApiController {
	/**
	 * 3DES秘钥(通过配置获取)
	 */
	@Value("${sbx.3desKey:-1}")
	private String desKey;	
	
	@Autowired
	private WeixinAppService weixinAppService;
	
	@Autowired
	private MTokenUserService mTokenUserService;
	
	/**
	 * 
	 * @return Map<String, Object>
	 */
	@ApiOperation(value = "远程注册用户会员", notes = "远程注册用户会员")
	@PostMapping("/regUser")
	public ResponseData<String>  regUser(@RequestBody Map<String, Object> receiveParam) {
		
		//解密参数
		String data = "";
		if(receiveParam.containsKey("data")) {
			data = receiveParam.get("data").toString();
		}else {
			return ResponseData.error("参数格式错误");
		}
		
		String receiveData = Des3Utils.get3DESDecryptECB(data, desKey);
		log.info("3des后receiveData注册数据:" + receiveData);
		if(StringUtils.isEmpty(receiveData)) {
			return ResponseData.error("参数数据或格式错误");
		}
		
		@SuppressWarnings("unchecked")
		Map<String, Object> param = JSON.parseObject(receiveData, Map.class);
		
		String phone = "";
		String countryCode = "";
		String openId = "";
		String registerId = "";
		String registerName = "";
		String inviter = "";
		String channelId = "";
		String channelName = "";
		String company = "";
		String account = "";
		String password = "";
		
		if(param.containsKey("account")) {
			account = param.get("account").toString();
		}else {
			return ResponseData.error("授权账号为空");
		}
		
		if(param.containsKey("password")) {
			password = param.get("password").toString();
		}else {
			return ResponseData.error("授权密码为空");
		}
		
		log.info("授权账号:"+account+",授权密码:"+password);
		//授权认证
		MTokenUser mTokenUser = mTokenUserService.searchByUserName(account, password);
		if(null == mTokenUser) {
			return ResponseData.error("授权失败,账号或密码错误");
		}
 
		if(param.containsKey("phone")) {
			phone = param.get("phone").toString();
		}else {
			return ResponseData.error("手机号不能为空");
		}
		
		if(param.containsKey("countryCode")) {
			countryCode = param.get("countryCode").toString();
		}else {
			return ResponseData.error("手机国家号不能为空");
		}
		
		if(param.containsKey("openId")) {
			openId = param.get("openId").toString();
		}else {//拼接生成
			openId =countryCode+"-"+phone;
		}
		
		if(param.containsKey("registerId")) {
			registerId = param.get("registerId").toString();
		}
		if(param.containsKey("registerName")) {
			registerName = param.get("registerName").toString();
		}
		if(param.containsKey("inviter")) {
			inviter = param.get("inviter").toString();
		}
			
		if(param.containsKey("channelId")) {
			channelId = param.get("channelId").toString();
		}
		if(param.containsKey("channelName")) {
			channelName = param.get("channelName").toString();
		}
		if(param.containsKey("company")) {
			company = param.get("company").toString();
		}
		
		WXModelDTO wXModelDTO = weixinAppService.rpcRegUser(phone, countryCode, openId, registerId, registerName, inviter, channelId, channelName, company);
		
		String str = Des3Utils.get3DESEncryptECB(JSON.toJSONString(wXModelDTO), desKey);
		
		return ResponseData.success(str);
	 
	}

	
	public static void main(String[] args) {
		
		Map<String, Object> param  = new HashMap<String, Object>();
		param.put("phone", "13657**3364");
		param.put("openId", "432423");
		param.put("countryCode", "86");
		param.put("account", "测试");
		param.put("password", "测试的密码");
		String enStr = Des3Utils.get3DESEncryptECB(JSON.toJSONString(param), "秘钥");
		String deStr = Des3Utils.get3DESDecryptECB(enStr,  "秘钥");
		Map map = JSON.parseObject(deStr,Map.class);
		
		System.out.println("enStr:"+enStr);
		System.out.println("deStr:"+deStr);
		System.out.println("map:"+map);
		
		 
		
	}

}

加解密工具类Des3Utils.java



import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.util.encoders.Hex;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;




/**

* @Description:
* @Company:ctg.cn
* @date:2023年5月30日
*/
@Slf4j
public class Des3Utils {
	/**
     * 加密算法
     */
    private static final String KEY_ALGORITHM = "DESede";
    private static final String CIPHER_ALGORITHM = "DESede/CBC/PKCS5Padding";

    /**
     * 3DES 加密
     * @param key   秘钥(24位)
     * @param iv    偏移量
     * @param data  需要加密的字符串
     * @return 返回加密的字符串
     */
    public static String encrypt(String key, String iv, String data) {
        try {
            DESedeKeySpec spec = new DESedeKeySpec(key.getBytes(StandardCharsets.UTF_8));
            SecretKeyFactory keyfactory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
            Key deskey = keyfactory.generateSecret(spec);
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            IvParameterSpec ips = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
            cipher.init(Cipher.ENCRYPT_MODE, deskey, ips);
            byte[] bOut = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            Base64.Encoder encoder = Base64.getMimeEncoder();
            return encoder.encodeToString(bOut);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("3DES 解密错误:{}", e);
            throw new RuntimeException("3DES 解密错误");
        }
    }

    /**
     * 3DES 解密
     * @param key   秘钥(24位)
     * @param iv    偏移量
     * @param data  需要解密的密文
     * @return 返回加密的字符串
     */
    public static String decrypt(String key, String iv, String data) {
        try {
            DESedeKeySpec spec = new DESedeKeySpec(key.getBytes(StandardCharsets.UTF_8));
            SecretKeyFactory keyfactory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
            Key deskey = keyfactory.generateSecret(spec);
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            IvParameterSpec ips = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
            cipher.init(Cipher.DECRYPT_MODE, deskey, ips);
            byte[] bOut = cipher.doFinal(getBase64Decode(data));
            return new String(bOut, StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("3DES 解密错误:{}", e);
            throw new RuntimeException("3DES 解密错误");
        }
    }

    /**
     * 3DES加密
     * @param src
     * @param secretKey
     * @return
     */

    public static String get3DESEncryptECB(String src, String secretKey) {
        try {
            Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
            cipher.init(1, new SecretKeySpec(build3DesKey(secretKey), "DESede"));
            String base64Encode = getBase64Encode(cipher.doFinal(src.getBytes("UTF-8")));
            return filter(base64Encode);
        } catch (Exception var4) {
            return null;
        }
    }

    /**
     * 3DES解密
     * @param src
     * @param secretKey
     * @return
     */
    public static String get3DESDecryptECB(String src, String secretKey) {
        try {
            Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
            cipher.init(2, new SecretKeySpec(build3DesKey(secretKey), "DESede"));
            byte[] base64DValue = getBase64Decode(src);
            byte[] ciphertext = cipher.doFinal(base64DValue);
            return new String(ciphertext, "UTF-8");
        } catch (Exception var5) {
            return null;
        }
    }

    public static byte[] getBase64Decode(String str) {
        byte[] src = null;

        try {
            Base64.Decoder decoder = Base64.getMimeDecoder();
            src = decoder.decode(str);
        } catch (Exception var3) {
            var3.printStackTrace();
        }

        return src;
    }

    public static String getBase64Encode(byte[] src) {
        String requestValue = "";

        try {
            Base64.Encoder encoder = Base64.getMimeEncoder();
            requestValue = filter(encoder.encodeToString(src));
        } catch (Exception var3) {
            var3.printStackTrace();
        }

        return requestValue;
    }
    public static byte[] build3DesKey(String keyStr) throws UnsupportedEncodingException {
        byte[] key = new byte[24];
        byte[] temp = keyStr.getBytes("UTF-8");
        if (key.length > temp.length) {
            System.arraycopy(temp, 0, key, 0, temp.length);
        } else {
            System.arraycopy(temp, 0, key, 0, key.length);
        }

        return key;
    }
    private static String filter(String str) {
        String output = null;
        StringBuffer sb = new StringBuffer();

        for(int i = 0; i < str.length(); ++i) {
            int asc = str.charAt(i);
            if (asc != '\n' && asc != '\r') {
                sb.append(str.subSequence(i, i + 1));
            }
        }

        output = new String(sb);
        return output;
    }

    public static String byteToHexString(byte[] bytes) {
        StringBuffer sb = new StringBuffer();

        for(int i = 0; i < bytes.length; ++i) {
            String strHex = Integer.toHexString(bytes[i]);
            if (strHex.length() > 3) {
                sb.append(strHex.substring(6));
            } else if (strHex.length() < 2) {
                sb.append("0" + strHex);
            } else {
                sb.append(strHex);
            }
        }

        return sb.toString();
    }

    /**
     * 将data中的密文Hex后3DES解码
     * @param content
     * @param key
     * @return
     */
    public static String reHexAndDecrypt(String content, String key) {
        if(StrUtil.isBlank(content)) {
            return null;
        }
        byte[] hexBytes = Hex.decode(content.getBytes(StandardCharsets.UTF_8));
        String baseReqStr = getBase64Encode(hexBytes);
        String outInfo = Des3Utils.get3DESDecryptECB(baseReqStr, key);
        return outInfo;
    }

}

Restful的微服务接口设计原则

一、概述

在微服务架构中,各个服务之间的通信主要通过接口进行。为了实现松耦合的通信,接口设计应该遵循一些重要的原则。其中,Restful是一种流行的接口设计风格,它基于HTTP协议,通过统一的接口规范来简化分布式系统的开发。本文将重点探讨Restful的微服务接口设计原则。

二、原则详解

  1. 明确资源定位

Restful的核心思想是基于资源的。每一个微服务都应该有一个明确的资源定位,这有助于明确服务的边界和职责。资源的标识符通常使用URI(统一资源标识符),通过特定的URI来访问特定的资源。例如,用户管理微服务的资源定位可能是“/users”,通过访问“/users”可以获取用户列表或特定用户的信息。

  1. 使用HTTP方法

Restful接口推荐使用HTTP协议的四种方法(GET、POST、PUT、DELETE)来操作资源。每种方法都有明确的语义,用于执行不同的操作。例如,GET方法用于获取资源,POST方法用于创建资源,PUT方法用于更新资源,DELETE方法用于删除资源。通过使用标准的HTTP方法,可以实现跨平台的互操作性,并简化客户端和服务端的交互。

  1. 保持无状态

无状态是Restful接口的一个重要特性。这意味着服务端不应该为客户端保存任何状态信息。每次请求都需要携带必要的信息,服务端根据请求的信息来处理请求并返回结果。这样可以提高系统的可伸缩性和可靠性,因为服务端不需要维护客户端的状态信息。

  1. 参数传递

Restful接口通过URI和HTTP请求体来传递参数。在URI中,可以通过查询参数的形式传递某些信息,如排序、分页等。对于复杂的请求体,可以使用JSON或XML格式来传递数据。在返回结果时,也可以使用相同的格式来返回数据。这种数据格式的传递方式使得Restful接口更加灵活和可扩展。

  1. 错误处理

当请求发生错误时,Restful接口使用HTTP状态码来表示错误类型。常见的状态码包括404(未找到)、401(未授权)、500(服务器内部错误)等。通过使用标准的HTTP状态码,客户端可以理解请求失败的原因,并根据具体情况采取相应的措施。除了状态码外,还可以在响应体中返回具体的错误信息,以便客户端进行更详细的错误处理。

  1. 版本控制

随着微服务的演进和变化,可能会导致接口的变更。为了避免因接口变更而导致的兼容性问题,应该对接口进行版本控制。可以通过在URI中添加版本号来实现接口的版本控制。例如,“/v1/users”表示访问版本1的用户管理接口。通过版本控制,可以逐步迁移现有客户端,同时允许新旧版本共存,降低升级成本。

  1. 安全性考虑

Restful接口应考虑安全性问题,以确保数据的安全性和完整性。常见的安全措施包括使用HTTPS协议进行加密通信、实施身份验证和授权机制等。身份验证可以通过JWT(JSON Web Token)、OAuth等协议来实现,确保只有经过身份验证和授权的用户才能访问相应的资源。此外,还可以使用参数校验、输入过滤等手段来防止潜在的安全漏洞。

  1. 文档化与可维护性

为了方便开发和维护,Restful接口应该具备良好的文档化。文档应详细描述接口的URI、请求和响应的格式、参数说明、错误码等信息。可以使用Swagger等工具自动生成API文档,以便开发人员快速了解和使用接口。此外,对于复杂的业务逻辑或参数设置,应该在注释中进行说明,提高代码的可读性和可维护性。

  1. 扩展性与灵活性

随着业务的发展和变化,微服务可能需要不断演进和调整。因此,Restful接口设计应具备良好的扩展性与灵活性。可以通过设计可扩展的API、使用插件机制等方式来实现这一目标。此外,为了满足不同客户端的需求,可以提供多种数据格式的支持(如JSON、XML等),提高接口的适应性。

三、总结

以上是关于Restful的微服务接口设计原则的探讨。遵循这些原则可以帮助我们设计出清晰、可维护、可扩展的微服务接口,提高系统的可伸缩性和可靠性。在实际应用中,根据具体的业务需求和技术环境,可以选择适合的原则进行实施,以满足项目的实际需求。同时,随着技术的不断发展和演进,我们也需要不断关注新的趋势和实践,不断完善和优化微服务的接口设计。文章来源地址https://www.toymoban.com/news/detail-705326.html

到了这里,关于JAVA开发(基于Restful的微服务第三方简易接口设计)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 安全学习_开发相关_Java第三方组件Log4j&FastJSON及相关安全问题简介

    Java Naming and Directory Interface (Java 命名和目录接口 ),JNDI 提供统一的客户端 API,通过不同的服务供应接口(SPI)的实现,由管理者将 JNDI API 映射为特定的命名服务和目录服务,使得 JAVA 应用程可以通过 JNDI 实现和这些命名服务和目录服务之间的交互。 Log4J: Apache的一个开源项目

    2024年02月05日
    浏览(44)
  • spring boot整合第三方微信开发工具 weixin-java-miniapp 实现小程序微信登录

    有时候项目需要用到微信登录或获取用户的手机号码,weixin-java-miniapp是一个好用的第三方工具,不用我们自己写httpcline调用。 导入jar包 添加一个resource.properties文件,写上小程序的appid和secret 添加两个配置文件 WxMaProperties.java WxMaConfiguration.java 如何使用 小程序给微信发送消息

    2024年02月16日
    浏览(33)
  • java调第三方接口

    目录 背景 Spring的RestTemplate 1.引入依赖 2.RestTemplate配置类 3.RestTemplate实现类 java调第三方接口我百度的有三种方法. 第一种:通过JDK网络类Java.net.HttpURLConnection 第二种:通过Apache common封装好的HttpClient 第三种:通过Spring的RestTemplate 这个三种方法在我的scdn收藏里有,都是可以直接用的

    2024年02月05日
    浏览(48)
  • Java 音频播报(内置 + 第三方)

            jdk内置的可以使用,不过呢就是声音太原始了,这是同过输入文字然后识别读出来的。 1、导入JL的依赖 2、代码实现         这个是通过文件播报的,可以在网上下载自己需要的音频 最近在做项目,做外卖的,其中有一个功能就是接单,商家接单就需要用到这个功

    2024年02月02日
    浏览(34)
  • Java常用第三方工具类

    一、Apache StringUtils:专为Java字符串而生的工具类 首先引入依赖: 1.字符串判空 isEmpty: 判断null和\\\"\\\" isNotEmpty:判断null和\\\"\\\" isBlank:判断null和\\\"\\\"和\\\" \\\" isNotBlank:判断null和\\\"\\\"和\\\" \\\" 示例代码如下: 执行结果: 2.分割字符串 使用StringUtils的split()方法分割字符串成数组。 示例代码如下:

    2024年02月08日
    浏览(41)
  • APP外包开发的第三方代码库

    在APP的开发过程中有很多好用的第三方库,这些第三方库代码质量高,已经在很多的项目实际使用过,因此在开发APP时是非常好的选择。第三方库可以减轻开发人员工作量,也是开发人员必须要关注的辅助代码。今天和大家分享一些常用的第三方库,希望对大家有所帮助。北

    2024年02月08日
    浏览(27)
  • vscode 配置第三方库 opengl 开发

    2024年02月08日
    浏览(44)
  • 打包jar服务,如何提取第三方依赖包

    很多时候有这个需求,编译源码的时候无法联网,需要把源代码和依赖包一起离线用。 那么怎么把可以联网的工程依赖包,下载后提供给无网环境用呢。war的很多时候是默认提供好的,那么maven打包jar包服务的,assembly.xml 配置需要增加以下内容: 再执行maven打包就可以了。所

    2024年01月25日
    浏览(34)
  • uniapp原生插件开发调用第三方SDK

    uniapp安卓官方SDK Android 离线SDK - 正式版 | uni小程序SDK 官方uni原生插件开发教程(android)网址: 简介 | uni小程序SDK 第一步,开发环境的准备 下载uniapp安卓官方SDK待后面使用,解压完成如下图所示。 JAVA环境 jdk,打开cmd输入java -version查看自己的jdk版本,官方文档中提示jdk版本应

    2024年02月15日
    浏览(34)
  • SpringBoot【开发实用篇】---- 整合第三方技术(缓存)

    企业级应用主要作用是信息处理,当需要读取数据时,由于受限于数据库的访问效率,导致整体系统性能偏低。 应用程序直接与数据库打交道,访问效率低 为了改善上述现象,开发者通常会在应用程序与数据库之间建立一种临时的数据存储机制,该区域中的数据在内存中保

    2024年02月05日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包