java对接天猫精灵语音助手实现对公司其下的智能设备进行控制(附上源码)

java对接天猫精灵语音助手实现对公司其下的智能设备进行控制

前言当初刚来广州 公司上一任java已经离职半年 ,项目已经跑不动了,才招人的,所以我获得的是一个连跑都跑不起来的项目源码并且对项目一无所知,一年前网上并没有对接天猫精灵的相关走路只能自己整。下面我把整个controller层代码 从授权到控制的代码给贴了出来希望对大家有帮助。(基本自己改改就能直接用目前代码还在公司服务器上跑并且没任何问题)

下面有几点需要注意。
至于在天猫精灵开发者平台哪些东西就不讲了 说一些别的。
1.SSL证书(免费的 另外我有博客写了如何部署等等)在eclipse的tomcat安装ssl证书
2.域名(几块钱一个)
3.外网IP(可以花生壳什么的或者使用别的工具内网穿透直接在开发工具上调试他不香吗,难不成每次改一点点都要去发布一遍项目??)
4.天猫精灵的token会自动过期 (要操控设备的时候就会直接报已授权过期,授权已过期 ,贼烦 所以我这边授权自动过期了它来重新获取授权的时候我还是会去数据库把以前的旧token给它 ,嘿嘿嘿永不过期 )
5. combine接口有一段逻辑根据token 查询用户信息以后 根据用户名提取文件并解析公司存储在linux服务器上的客户客户设备信息并根据天猫精灵可解析的格式返回给天猫精灵 (注意 天猫精灵发送命令也是往这个接口,获取设备信息也是)
6. 有问题可以跟我留言 (目前还对接过小度小度,若琪)
7.差点忘记说了###号的地方请替换为自己的信息
把整个类拷过去然后改一下对应的域名端口号还有ID就好了在这里插入图片描述

package com.obj.controller;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.annotation.Resource;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.oltu.oauth2.as.issuer.MD5Generator;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
import org.apache.oltu.oauth2.as.request.OAuthTokenRequest;
import org.apache.oltu.oauth2.as.response.OAuthASResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.xml.sax.SAXException;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.obj.entity.Device;
import com.obj.entity.user;
import com.obj.entity.Token;
import com.obj.service.ControlService;
import com.obj.service.MqttMessageService;
import com.obj.service.ResponseService;
import com.obj.service.deviceService;
import com.obj.service.userService;

import utils.AnalysisXML;

@Controller
@RequestMapping("/Test")
public class testController {

	@Resource
	public userService userservice;
	@Resource
	public deviceService deviceservice;
	@Resource
	public MqttMessageService mqttService;
	@Resource
	public ResponseService responseService;
	@Resource
	public ControlService controlService;
	String grant_type = "authorization_code";
	String clientId = "###";// 
	String clientSecret = "###";// 
	String userInfoUrl = null;
	String response_type = "code";
	String code = null;
	String cutURL = "https://####:9443/genie/merchantHTML/login.jsp?";//
	String OAuthURL = "https://####:9443/genie/Test/responseCode.do?";//
	int cutlength = cutURL.length();
	int num=0;
	String s = new String("");

	@RequestMapping("/userlogin")
	public String userlogin(HttpServletRequest request, HttpServletResponse response)
			throws IOException, OAuthSystemException, ServletException {
		String url = request.getHeader("referer");
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		ServletContext context = request.getSession().getServletContext();
		context.setAttribute("username", username);
		user IdentifyUsername = userservice.IdentifyUsername(username);
		user IdentifyPassword = userservice.IdentifyPassword(username, password);
		if (IdentifyUsername != null) {
			if (IdentifyPassword != null) {
				String outURL = java.net.URLDecoder.decode(url, "UTF-8");
				int outlength = outURL.length();
				String responseURL = outURL.substring(cutlength, outlength);
				System.out.println("截取到的内容:"+responseURL+ "\n 长度:"+responseURL.length());
				num=responseURL.length();
				s=responseURL;
				OAuthURL = OAuthURL +responseURL;
				return "redirect:" + OAuthURL;
			} else {
				System.out.println("密码错误!");
			}
		} else {
			System.out.println("用户名不存在!");
		}
		return "error";
	}

	@RequestMapping("/responseCode")
	public Object toShowUser(Model model, HttpServletRequest request) throws IOException {
		try {
			// 构建OAuth 授权请求
			OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request);
			oauthRequest.getClientId();
			oauthRequest.getResponseType();
			oauthRequest.getRedirectURI();
			String token=null;
			String state=null;
		      String[] strarray=s.split("&"); 
  
			 token =strarray[1].substring(6,strarray[1].length());
			 state =strarray[4].substring(6,strarray[4].length());
 
			if (oauthRequest.getClientId() != null && oauthRequest.getClientId() != "") {
				// 设置授权码
				String authorizationCode = UUID.randomUUID().toString().replace("-", "").substring(0,18);
				System.out.println("授权码UUID=" + authorizationCode);
				// 利用oauth授权请求设置responseType,目前仅支持CODE,另外还有TOKEN
				// String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE);
				// 进行OAuth响应构建
				OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse
						.authorizationResponse(request, HttpServletResponse.SC_FOUND);
			
				// 设置授权码
				builder.setParam("token",java.net.URLDecoder.decode(token, "UTF-8"));
				builder.setParam("state", state);
				builder.setCode(authorizationCode);
				// 得到到客户端重定向地址
				String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI);
				// 构建响应
				OAuthResponse response = builder.location(redirectURI).buildQueryMessage();
				System.out.println("服务端/responseCode内,返回的回调路径:" + response.getLocationUri() + "\n");
				String responceUri = response.getLocationUri();
				// 根据OAuthResponse返回ResponseEntity响应
				HttpHeaders headers = new HttpHeaders();
				try {
					headers.setLocation(new URI(response.getLocationUri()));

				} catch (URISyntaxException e) {
					e.printStackTrace();
				}

				String strURL = "https://####/genie/Test/responseAccessToken.do?grant_type=authorization_code&client_id=####&client_secret=####&redirect_uri=https://open.bot.tmall.com/oauth/callback&code="
						+ authorizationCode;
				
			 
				URL url = new URL(strURL);
				HttpURLConnection connection = (HttpURLConnection) url.openConnection();
				connection.setDoOutput(true);
				connection.setDoInput(true);
				connection.setUseCaches(false);
				connection.setInstanceFollowRedirects(true);
				connection.setRequestMethod("POST"); // 设置请求方式
				connection.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式
				connection.setRequestProperty("Content-Type", "application/json"); // 设置发送数据的格式
				connection.connect();
				System.out.println("redirect:" + responceUri);
				token=null;
				state=null;
				return "redirect:" + responceUri;
				// https://open.bot.tmall.com/oauth/callback?skillId=18105&code=0b58444322e04d9c8e&state=11&token=MjM0MDgzODYwMEFGRUhJTkZEVlE%3D
			}

		} catch (OAuthSystemException e) {
			e.printStackTrace();
		} catch (OAuthProblemException e) {
			e.printStackTrace();
		}
		return null;
	}
	@RequestMapping(value = "/responseAccessToken", method = RequestMethod.POST)
	public HttpEntity<String> token(HttpServletRequest request) throws OAuthSystemException {
		JSONObject jsonObject = new JSONObject();
		 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm");//设置日期格式
			System.out.println("--------天猫精灵服务端/responseAccessToken---------------------"+df.format(new Date())+"---------------------");
		
		
		OAuthIssuer oauthIssuerImpl = null;
		OAuthResponse response = null;
		// 构建OAuth请求
		try {
			
			OAuthTokenRequest oauthRequest = new OAuthTokenRequest(request);
				
			String clientSecret = oauthRequest.getClientSecret();
			String token = oauthRequest.getRefreshToken();
			String accessToken=null;
			String refreshToken=null;
			int AccessToken=0;
			/*
			 * accessToken存进数据库,过期时间3天 绑定user用户
			 */
			ServletContext context = request.getSession().getServletContext();
			String username = (String) context.getAttribute("username");
			
			if (clientSecret != null && clientSecret != "") 
			{
				
				
				if(token==null && username!=null) {//绑定 赋予授权
					
					
					// 生成Access Token
					oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
					 accessToken = oauthIssuerImpl.accessToken();
					 refreshToken = oauthIssuerImpl.refreshToken();
					/*	
					 判断 数据库是否有相同token 有则继续取新的
					*/
					boolean l=true;  
					do {  
					    String name=userservice.SelectByToken(accessToken);  
					    String name2=userservice.SelectRefreshToken(refreshToken);
					    if(name==null&&name2==null) {
					    	l=false;
					    }else {
					    	accessToken=oauthIssuerImpl.accessToken();
					    	refreshToken = oauthIssuerImpl.refreshToken();
					    }
					} while (l);
					
					jsonObject.put("access_token", accessToken);
					jsonObject.put("refresh_token", refreshToken);
					jsonObject.put("expires_in", 259200);
					AccessToken = userservice.update(accessToken, username,refreshToken);
					
					
					if(AccessToken!=0) {
						System.out.println("授权成功");
					}else {
						System.out.println("授权失败");
					}
					
					
					
					response = OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK).setAccessToken(accessToken)
							.setRefreshToken(refreshToken).setExpiresIn("259200").setParam("expires_in", "259200").setParam("example_parameter", "example_value").buildJSONMessage();
					// 根据OAuthResponse生成ResponseEntity
					return new ResponseEntity<String>(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
					
					
				}else if(token!=null) { //天猫主动刷新授权
					
					
					System.out.println("授权过期 天猫精灵自动获取授权!");
				
					Token  userToken=userservice.SelectToken(token); //凭着带过来的token
					
					if(userToken.getTokenid()!=null&&userToken.getRefresh_tokenTM()!=null&&userToken.getUser()!=null) {//如果本身就拥有token 则取出数据库中的token 再返回给天猫精灵
						
						refreshToken=userToken.getRefresh_tokenTM();
						accessToken=userToken.getTokenid();
						System.out.println("天猫精灵主动发起授权刷新,数据库存在token 将其返回");
					
						// 生成OAuth响应
						response = OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK).setAccessToken(accessToken)
								.setRefreshToken(refreshToken).setExpiresIn("259200").setParam("expires_in", "259200").setParam("example_parameter", "example_value").buildJSONMessage();
						// 根据OAuthResponse生成ResponseEntity
						return new ResponseEntity<String>(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));

				
					}else {//生成新的token 返回给天猫精灵
						
						// 生成Access Token
						oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
						 accessToken = oauthIssuerImpl.accessToken();
						 refreshToken = oauthIssuerImpl.refreshToken();
						/*	
						 判断 数据库是否有相同token 有则继续取新的
						*/
						boolean l=true;  
						do {  
						    String name=userservice.SelectByToken(accessToken);  
						    String name2=userservice.SelectRefreshToken(refreshToken);
						    if(name==null&&name2==null) {
						    	l=false;
						    }else {
						    	accessToken=oauthIssuerImpl.accessToken();
						    	refreshToken = oauthIssuerImpl.refreshToken();
						    }
						} while (l);
						
						jsonObject.put("access_token", accessToken);
						jsonObject.put("refresh_token", refreshToken);
						jsonObject.put("expires_in", 259200);
						AccessToken = userservice.update(accessToken, userToken.getUser(),refreshToken);
						
						if(AccessToken!=0) {
							System.out.println("授权成功");
						}else {
							System.out.println("授权失败");
						}
						
						
						// 生成OAuth响应
						response = OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK).setAccessToken(accessToken)
								.setRefreshToken(refreshToken).setExpiresIn("259200").setParam("expires_in", "259200").setParam("example_parameter", "example_value").buildJSONMessage();
						// 根据OAuthResponse生成ResponseEntity
						return new ResponseEntity<String>(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));

						
						
						
					}
					
					
					
					
				}else {
					System.out.println("user:"+username);
					System.out.println("token:"+token);
					System.out.println("天猫精灵授权失败");
					
					
					response = OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK).setParam("error", "101")
							.setParam("error_description", "内部错误").buildJSONMessage();

					jsonObject.put("error", 101);
					jsonObject.put("error_dercription", "内部错误");
					System.out.println(jsonObject.toString());
					return new ResponseEntity<String>(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
					
					
				}
				
				
	
					
					
				
				
				
				}
			// 根据OAuthResponse生成ResponseEntity
			return new ResponseEntity<String>(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));

			} catch (OAuthSystemException e) {
			response = OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK).setParam("error", "101")
					.setParam("error_description", "内部错误").buildJSONMessage();

			jsonObject.put("error", 101);
			jsonObject.put("error_dercription", "内部错误");
			System.out.println(jsonObject.toString());
			return new ResponseEntity<String>(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
		} catch (OAuthProblemException e) {
			response = OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK).setParam("error", "102")
					.setParam("error_description", "参数错误").buildJSONMessage();
			jsonObject.put("error", 102);
			jsonObject.put("error_dercription", "参数错误");
			System.out.println(jsonObject.toString());
			return new ResponseEntity<String>(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
		}

	}

	/**
	 * 设备控制与设备状态查询
	 * http://doc-bot.tmall.com/docs/doc.htm?spm=0.7629140.0.0.21551780A5E52S&treeId=393&articleId=108268&docType=1
	 */
	@RequestMapping(value = "/combine", method = RequestMethod.POST)
	@ResponseBody
	public JSONObject combineDevice(HttpServletRequest request, HttpServletResponse response, BufferedReader br)
			throws SAXException, IOException, MqttException, InterruptedException, UnsupportedEncodingException {
		
		ServletContext context = request.getSession().getServletContext();
		String username = "";
		// 天猫精灵request的Body
		String inputLine;
		String str = "";
		try {
			while ((inputLine = br.readLine()) != null) {
				str += inputLine;
			}
			br.close();
		} catch (IOException e) {
			System.out.println("IOException: " + e);
		}
		System.out.println("天猫精灵的请求体:" + str + "\n");
		JSONObject recieveHeader = new JSONObject();
		recieveHeader = JSON.parseObject(str);
		String str1 = recieveHeader.getString("header");
		String str2 = recieveHeader.getString("payload");
		JSONObject recieveMessageId = new JSONObject();
		JSONObject recievedeviceId = new JSONObject();
		recieveMessageId = JSON.parseObject(str1);
		recievedeviceId = JSON.parseObject(str2);
		JSONObject MerchineList = new JSONObject();
		// 如果请求体的accessToken与数据库的不一致,则报token错误
		if (userservice.SelectByToken(recievedeviceId.getString("accessToken")) == null) {
			return responseService.ErrorResponce(recieveMessageId, recievedeviceId);
		} else {
			System.out.println("使用token查询出来的用户名是;"+userservice.SelectByToken(recievedeviceId.getString("accessToken")));
			// 如果请求体的accessToken与数据库一致,则返回该token对应的用户名
			username = userservice.SelectByToken(recievedeviceId.getString("accessToken"));
		}
		
		String fileName = "/var/www/html/store/" + username + "_Devices.xml";
		//String fileName = "file:///F:/DSKJ_Devices.xml";
		AnalysisXML test = new AnalysisXML();
		File file = new File("/var/www/html/store/" + username + "_Devices.xml");
		//File file = new File("F:/DSKJ_Devices.xml");
		
		String name = recieveMessageId.getString("name");
		
		
		if (file.exists()) {
			test.setCenterID(fileName);
		}
		String topic = "MSG/" + test.getCenterID();
		switch (name) {
		case "DiscoveryDevices":
			// 对应:登陆天猫精灵后自动查询出的设备列表
			if (file.exists()) {			
				// 如果文件是真实存在的,再进行XML解析
				List<Device> Devices = deviceservice.AnalysisXML(fileName);
				MerchineList = responseService.DeviceResponce(recieveMessageId, Devices);
			} else {
				// 如果文件是不存在的,提供写死的test数据
				MerchineList = responseService.WritedeadResponce();
			}
			return MerchineList;
			
		case "TurnOn":// 对应:天猫精灵,打开某某设备
			System.out.println("进入了打开");
			MerchineList = controlService.TurnOnResponce(username, topic, recieveMessageId, recievedeviceId);
			return MerchineList;
		case "TurnOff":// 对应:天猫精灵,关闭某某设备
			MerchineList = controlService.TurnOffResponce(username, topic, recieveMessageId, recievedeviceId);
			return MerchineList;
		case "Pause":// 对应:天猫精灵,窗帘暂停
			MerchineList = controlService.PauseResponce(username, topic, recieveMessageId, recievedeviceId);
			return MerchineList;
		case "SetBrightness":// 对应:天猫精灵,调节灯光亮度
			String value = recievedeviceId.getString("value");
			String setValue = value;
			if (!(value.equals("max") || value.equals("min"))) {
				if (Integer.parseInt(value) > 100 || Integer.parseInt(value) < 0) {
					MerchineList = responseService.ParamsErrorResponce(recieveMessageId, recievedeviceId);
					return MerchineList;
				}
			}
			MerchineList = controlService.SetBrightnessResponce(setValue, username, topic, recieveMessageId,
					recievedeviceId);
			return MerchineList;
		case "SetTemperature":// 对应:天猫精灵,调节空调温度
			String value2 = recievedeviceId.getString("value");
			String setValue2 = value2;
			if (!(value2.equals("max") || value2.equals("min"))) {
				if (Integer.parseInt(value2) > 32 || Integer.parseInt(value2) < 16) {
					MerchineList = responseService.ParamsErrorResponce(recieveMessageId, recievedeviceId);
					return MerchineList;
				}
			}
			MerchineList = controlService.SetTemperatureResponce(setValue2, username, topic, recieveMessageId,
					recievedeviceId);
			return MerchineList;
		case "Query":// 对应:天猫精灵,查询空调状态
			
			MerchineList = controlService.QueryResponce(topic, recieveMessageId, recievedeviceId);
			return MerchineList;
			
		case "SetMode":// 对应:天猫精灵,将空调调到某某模式
			MerchineList = controlService.SetModeResponce(username, topic, recieveMessageId, recievedeviceId);
			return MerchineList;
			
		case "SetWindSpeed":// 对应:天猫精灵,空调风速调到某风
			
			MerchineList = controlService.SetWindSpeedResponce(username, topic, recieveMessageId, recievedeviceId);
			return MerchineList;
			
		case "AdjustUpTemperature":// 对应:天猫精灵,空调温度调高一点
			
			MerchineList = controlService.AdjustUpTemperatureResponce(username, topic, recieveMessageId,
					recievedeviceId);
			
			return MerchineList;
			
		case "AdjustDownTemperature":// 对应:天猫精灵,空调温度调低一点
			MerchineList = controlService.AdjustDownTemperatureResponce(username, topic, recieveMessageId,
					recievedeviceId);
			return MerchineList;
		case "AdjustUpWindSpeed":// 对应:天猫精灵,空调风速调高一点
			MerchineList = controlService.AdjustUpWindSpeedResponce(username, topic, recieveMessageId, recievedeviceId);
			return MerchineList;
		case "AdjustDownWindSpeed":// 对应:天猫精灵,空调风速调低一点
			MerchineList = controlService.AdjustDownWindSpeedResponce(username, topic, recieveMessageId,
					recievedeviceId);
			return MerchineList;
		default:
			
			return MerchineList;
		}
	}
}

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
由于大华SDK是商业软件,需要购买后方可使用,因此无法提供完整的源码。以下是实现回放功能的详细步骤: 1. 导入大华SDK的jar包和相关依赖库。 2. 创建SDK实例,并初始化。 3. 登录录像机。 4. 获取录像机上的所有通道列表。 5. 选择需要回放的通道,并设置回放的起始时间和结束时间。 6. 开始回放,并获取回放的视频流数据。 7. 将视频流数据解码并显示在界面上。 以下是部分示例代码: ```java // 初始化SDK HCNetSDK hCNetSDK = HCNetSDK.INSTANCE; hCNetSDK.NET_DVR_Init(); // 登录录像机 HCNetSDK.NET_DVR_DEVICEINFO_V30 deviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V30(); int userId = hCNetSDK.NET_DVR_Login_V30(ip, port, username, password, deviceInfo); // 获取通道列表 HCNetSDK.NET_DVR_IPPARACFG_V40 ipParaCfg = new HCNetSDK.NET_DVR_IPPARACFG_V40(); hCNetSDK.NET_DVR_GetDVRConfig(userId, HCNetSDK.NET_DVR_GET_IPPARACFG_V40, deviceInfo.byStartDChan, ipParaCfg, ipParaCfg.size(), new IntByReference()); // 选择通道并设置回放时间 int channel = 1; HCNetSDK.NET_DVR_TIME startTime = new HCNetSDK.NET_DVR_TIME(); HCNetSDK.NET_DVR_TIME endTime = new HCNetSDK.NET_DVR_TIME(); // 设置startTime和endTime的值 // 开始回放 int playId = hCNetSDK.NET_DVR_PlayBackByTime_V40(userId, new NativeLong(channel), startTime, endTime, null); // 获取视频流数据 HCNetSDK.NET_DVR_PACKET_INFO_EX packetInfo = new HCNetSDK.NET_DVR_PACKET_INFO_EX(); Pointer videoData = hCNetSDK.NET_DVR_GetPlayBackData_V40(playId, packetInfo); // 将视频流数据解码并显示 // 可以使用FFmpeg等第三方库进行解码和显示 ``` 需要注意的是,以上代码仅供参考,具体实现方式还需要根据实际情况进行调整。同时,由于涉及到视频流的解码和显示,可能需要使用第三方库进行处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值