SSM分布式项目之淘淘商城-第十一天(IDEA)

文章大纲
一、第十一天课程计划
二、校验数据接口开发
三、注册接口开发
四、登录接口开发
五、token的应用
六、登录注册功能实现
七、门户首页展示用户名(Jsonp)
八、参考博客

淘淘商城课程大纲

课程大纲
一共14天课程
(1)第一天:电商介绍–互联网术语-SOA-分布式-集群介绍-环境配置-框架搭建
(2)第二天:Dubbo介绍_dubbo框架整合_商品列表查询实现_分页_逆向工程
(3)第三天:Git&.Nginx,类目选择,新增商品
(4)第四天:门户网站介绍&商城首页搭建&内容系统创建&CMS实现
(5)第五天:首页轮播图显示实现,Redis环境搭建,Redis实现缓存
(6)第六天:solr索引库搭建&solr搜索功能实现&图片显示问题解决
(7)第七天:solr集群搭建_全局异常处理
(8)第八天:activeMQ介绍_搭建_解决同步索引库问题
(9)第九天:FreeMark入门_静态化页面标签介绍_静态化页面实现
(10)第十天:Nginx代理详解…单点登录系统工程搭建_接口文档讲解
(11)第十一天:SSO系统的搭建&单点登录系统实现_用户名回显_cookie跨域问题
(12)第十二天:购物车订单系统的实现。
(13)第十三天:订单提交的功能实现&项目的部署&服务器的域名规划。
(14)项目总结。

1. 课程计划

    1、SSO注册功能实现
    2、SSO登录功能实现
    3、通过token获得用户信息
    4、ajax跨域请求解决方案–jsonp

2. 校验数据接口开发

检查数据是否可用作为注册功能的辅助。

2.1 功能分析

请求的url:/user/check/{param}/{type}
参数:从url中取参数
  1、String param(要校验的数据)
  2、Integer type(校验的数据类型)
响应的数据:json数据。TaotaoResult,封装的数据校验的结果为true:表示成功,数据可用,false:失败,数据不可用。
业务逻辑:
  1、从tb_user表中查询数据。
  2、查询条件根据传递过来的参数动态生成。
  3、判断查询结果,如果查询到数据就返回false。
  4、如果没有查询到数据就返回true。
  5、使用TaotaoResult包装,并返回。

2.2 Dao

从tb_user表查询。属于单表查询,可以使用逆向工程生成的代码。

2.3 Service

先在taotao-sso-interface中定义接口UserRegisterService,
再在taotao-sso-service中写实现类。
参数:
  1、要校验的数据:String param
  2、数据类型:Integer type(1、2、3分别代表username、phone、email)
返回值:TaotaoResult
在taotao-sso-interface创建接口

public interface UserRegisterService {
	/**
	 * 根据参数和类型来校验数据
	 * @param param  要校验的数据
	 * @param type  1  2  3  分别代表 username,phone,email
	 * @return
	 */
	public TaotaoResult checkData(String param, Integer type);
}

在taotao-sso-service创建实现类

	@Autowired
	private TbUserMapper usermapper;

	@Override
	public TaotaoResult checkData(String param, Integer type) {
		//1.注入mapper
		//2.根据参数动态的生成查询的条件
		TbUserExample example = new TbUserExample();
		Criteria criteria = example.createCriteria();
		if(type==1){//username
			if(StringUtils.isEmpty(param)){
				return TaotaoResult.ok(false);
			}
			criteria.andUsernameEqualTo(param);
		}else if(type==2){
			//phone
			criteria.andPhoneEqualTo(param);
		}else if(type==3){
			//email
			criteria.andEmailEqualTo(param);
		}else{
			//是非法的参数    
			//return 非法的
			return TaotaoResult.build(400, "非法的参数");
		}
		//3.调用mapper的查询方法 获取数据   
		List<TbUser> list = usermapper.selectByExample(example);
		//4.如果查询到了数据   --数据不可以用   false
		if(list!=null && list.size()>0){
			return TaotaoResult.ok(false);
		}
		//5.如果没查到数据  ---数据是可以用  true
		return TaotaoResult.ok(true);
	}

发布服务

先在taotao-sso-service工程中的pom.xml文件中配置对taotao-sso-interface的依赖,因为服务层发布服务要通过该接口,
在这里插入图片描述

再在taotao-sso-service工程中的applicationContext-service.xml文件中发布服务:
在这里插入图片描述

引用服务

需要在taotao-sso-web中实现。
先在taotao-sso-web工程中的pom.xml文件中配置对taotao-sso-interface的依赖,表现层调用服务要通过该接口,
在taotao-sso-web工程中的springmvc.xml文件中引用服务:
在这里插入图片描述

2.4 Controller

请求的url:/user/check/{param}/{type}
参数:从url中取参数
  1、String param(要校验的数据)
  2、Integer type(校验的数据类型)
请求的方法:get。
响应的数据:json数据。TaotaoResult,封装的数据校验的结果,true:成功;false:失败。

	@Autowired
	private UserRegisterService registerservice;
	/**
	 * url:/user/check/{param}/{type}
	 * 
	 * @param param
	 * @param type  1  2 3 
	 * @return
	 */
	@RequestMapping(value="/user/check/{param}/{type}",method=RequestMethod.GET)
	@ResponseBody
	public TaotaoResult checkData(@PathVariable String param,@PathVariable Integer type){
		//1.引入服务
		//2.注入
		//3.调用
		return registerservice.checkData(param, type);
	}

2.5 测试

启动taotao-sso,taotao-sso-web
在这里插入图片描述
双击打开,用这个测试,选择get,输入http://localhost:8088/user/check/zhangsan/1
在这里插入图片描述

或者直接在浏览器中输入URL
访问地址:http://localhost:8088/user/check/zhangsan/1

2.6 插曲

我在做这个的时候,刚开始一直出现的是404页面
在这里插入图片描述
网上找了很多方法,比如拦截形式改为/,还是没有解决,停滞了将近一天,对比源码也没发现什么错误。。。当时以为没什么,可能是缓存吧,然后接着写登录注册功能,测试的时候发现全部是404。。最后我把这两个工程全部删了,重新创建,重新写(头秃啊),然后就可以了。。。真的是抓狂,这种是真的难受!!!!

3. 注册接口开发

3.1 功能分析

请求的url:/user/register
参数:表单的数据:username、password、phone、email
返回值:json数据。TaotaoResult。
接收参数:使用TbUser对象接收。
请求的方法:post
业务逻辑:
  1、使用TbUser接收提交的请求。
  2、补全TbUser其他属性。
  3、密码要进行MD5加密。
  4、把用户信息插入到数据库中。
  5、返回TaotaoResult.ok()。

3.2 Dao

可以使用逆向工程生成的代码。

3.3 Service

在taotao-sso-interface中定义接口UserRegisterService已经定义好了,我们直接添加方法即可。
再在taotao-sso-service中写实现类,我们已经定义好了,直接实现方法即可。
参数:TbUser
返回值:TaotaoResult
在taotao-sso-interface创建接口

	/**
	 * 注册用户
	 * @param user
	 * @return
	 */
	public TaotaoResult register(TbUser user);

在taotao-sso-service创建实现类

	@Override
	public TaotaoResult register(TbUser user) {
		//1.注入mapper
		//2.校验数据
			//2.1 校验用户名和密码不能为空
		if(StringUtils.isEmpty(user.getUsername())){
			return TaotaoResult.build(400, "注册失败. 请校验数据后请再提交数据");
		}
		if(StringUtils.isEmpty(user.getPassword())){
			return TaotaoResult.build(400, "注册失败. 请校验数据后请再提交数据");
		}
			//2.2 校验用户名是否被注册了
		TaotaoResult result = checkData(user.getUsername(),1);
		if(!(boolean)result.getData()){
			//数据不可用
			return TaotaoResult.build(400, "注册失败. 请校验数据后请再提交数据");
		}
			//2.3 校验电话号码是否被注册了
		if(StringUtils.isNotBlank(user.getPhone())){
			TaotaoResult result2 = checkData(user.getPhone(),2);
			if(!(boolean)result2.getData()){
				//数据不可用
				return TaotaoResult.build(400, "注册失败. 请校验数据后请再提交数据");
			}
		}
			//2.4 校验email是否被注册了
		if(StringUtils.isNotBlank(user.getEmail())){
			TaotaoResult result2 = checkData(user.getEmail(),3);
			if(!(boolean)result2.getData()){
				//数据不可用
				return TaotaoResult.build(400, "注册失败. 请校验数据后请再提交数据");
			}
		}
		
		//3.如果校验成功   补全其他的属性
		user.setCreated(new Date());
		user.setUpdated(user.getCreated());
		//4.对密码进行MD5加密
		String md5password = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
		user.setPassword(md5password);
		//5.插入数据
		usermapper.insertSelective(user);
		//6.返回taotaoresult
		return TaotaoResult.ok();
	}

3.4 Controller

请求的url:/user/register
参数:表单的数据:username、password、phone、email
返回值:json数据。TaotaoResult。
接收参数:使用TbUser对象接收。
请求的方法:post

	/**
	 * 注册用户
	 * @param user
	 * @return
	 */
	@RequestMapping(value="/user/register",method=RequestMethod.POST)
	@ResponseBody
	public TaotaoResult register(TbUser user){
		//
		TaotaoResult result = registerservice.register(user);
		return result;
	}

3.5 测试

使用restclient-ui-3.5-jar-with-dependencies.jar测试接口。
Body选择String body
点击编辑按钮,填写表单提交的content-type:application/x-www-form-urlencoded
在这里插入图片描述

点击插入参数按钮,填写参数后,点击生成。
在这里插入图片描述

点击提交按钮,测试开始。
zhangsan存在了注册不了,换成zhangsansan
在这里插入图片描述

4. 登录接口开发

传统登录
在这里插入图片描述
分布式登录
在这里插入图片描述

4.1 功能分析

请求的url:/user/login
请求的方法:POST
参数:username、password,表单提交的数据。可以使用方法的形参接收。
返回值:json数据,使用TaotaoResult包含一个token。
业务逻辑:
登录的业务流程:

登录的处理流程:
  1、登录页面提交用户名密码。
  2、登录成功后生成token。token相当于原来的jsessionid,字符串,可以使用uuid。
  3、把用户信息保存到redis。key就是token,value就是TbUser对象转换成的json字符串。
  4、使用String类型保存session信息。可以使用“前缀:token”为key。
  5、设置key的过期时间。模拟session的过期时间。一般半个小时。
  6、把token写入cookie中。
  7、cookie需要跨域。例如:www.taotao.com\sso.taotao.com\order.taotao.com,可以使用工具类。
  8、cookie的有效期。关闭浏览器失效。
  9、登录成功。

4.2 Dao

查询tb_user表。单表查询。可以使用逆向工程生成的代码。

4.3 Service

参数:
  1、用户名:String username
  2、密码:String password
返回值:TaotaoResult,包装token。
业务逻辑:
  1、判断用户名和密码是否正确。
  2、登录成功后生成token。token相当于原来的jsessionid,字符串,可以使用uuid。
  3、把用户信息保存到redis。key就是token,value就是TbUser对象转换成的json串。
  4、使用String类型保存session信息。可以使用“前缀:token”为key。
  5、设置key的过期时间。模拟session的过期时间。一般半个小时。
  6、返回TaotaoResult包装token。
部分代码:需要设置加载属性文件。
在taotao-sso-interface创建接口

/**
 * @Description 用户登录接口
 * @Author LH
 * @Date  2020/9/28 11:47
 **/
public interface UserLoginService {
	/**
	 * 根据用户名和密码登录
	 * @param username
	 * @param password
	 * @return
	 * taotaoresult 登录成功 返回200 并且包含一个token数据
	 *登录失败:返回400
	 */
	public TaotaoResult login(String username, String password);
}

在taotao-sso-service创建实现类

	@Autowired
	private TbUserMapper usermapper;
	@Autowired
	private JedisClient client;
	
	@Value("${USER_INFO}")
	private String USER_INFO;
	@Value("${EXPIRE_TIME}")
	private Integer EXPIRE_TIME;

	@Override
	public TaotaoResult login(String username, String password) {
		//1.注入mapper
		//2.校验用户名和密码是否为空
		if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)){
			return TaotaoResult.build(400, "用户名或密码错误");
		}
		//3.先校验用户名
		TbUserExample example = new TbUserExample();
		Criteria criteria = example.createCriteria();
		criteria.andUsernameEqualTo(username);
		List<TbUser> list = usermapper.selectByExample(example);//select* from tbuser where username=123
		if(list==null && list.size()==0){
			return TaotaoResult.build(400, "用户名或密码错误");
		}
		//4.再校验密码
		TbUser user = list.get(0);
		//先加密密码再比较
		String md5DigestAsHex = DigestUtils.md5DigestAsHex(password.getBytes());
		if(!md5DigestAsHex.equals(user.getPassword())){//表示用户的密码不正确
			return TaotaoResult.build(400, "用户名或密码错误");
		}
		//5.如果校验成功
		//6.生成token : uuid生成    ,还需要设置token的有效性期来模拟session  用户的数据存放在redis  (key:token,value:用户的数据JSON)
		String token = UUID.randomUUID().toString();
		//存放用户数据到redis中,使用jedis的客户端,为了管理方便加一个前缀"kkk:token"
		//设置密码为空
		user.setPassword(null);
		client.set(USER_INFO+":"+token, JsonUtils.objectToJson(user));
		//设置过期时间 来模拟session
		client.expire(USER_INFO+":"+token, EXPIRE_TIME);
		//7.把token设置cookie当中    在表现层设置
		return TaotaoResult.ok(token);
	}

发布服务

在taotao-sso-service工程中applicationContext-service.xml文件中发布服务:
在这里插入图片描述

引用服务

在taotao-sso-web工程中的springmvc.xml文件中引用服务:
在这里插入图片描述

4.4 Controller

请求的url:/user/login
请求的方法:POST
参数:username、password,表单提交的数据。
因为只有两个参数,所以不用使用pojo来接收,可以使用方法的形参接收就可以。
从返回结果中取出token,写入cookie。需要使用HttpServletRequest、HttpServletResponse
返回值:json数据,使用TaotaoResult包含一个token。
业务逻辑:
  1、接收表单提交的两个参数。
  2、调用Service层的方法进行登录。
  3、从返回结果中取token,写入cookie。注意:cookie要跨域。
   cookie的二级域名跨域需要进行设置:
   1)setDomain(),设置一级域名:
    .itcast.cn
    .taotao.com
    .taotao.com.cn
   2)setPath(),设置为“/”
   可以使用工具类。工具类放到taotao-common工程中。

需要加入servlet-api的依赖包:在common项目中的pom.xml中,加入依赖:

<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>servlet-api</artifactId>
	<scope>provided</scope>
</dependency>

注意:scope的值是provided,表示该jar包在运行时使用、编译时使用、测试时用,但是打包的时候不用,因为该jar包web容器tomcat会提供,如果打包的时候使用该jar包,会出现冲突。

响应数据。json数据。TaotaoResult,其中包含token。

	@Autowired
	private UserLoginService loginservice;
	
	@Value("${TT_TOKEN_KEY}")
	private String TT_TOKEN_KEY;
	/**
	 * url:/user/login
	 * 参数:username password
	 * 返回值:json
	 * 请求限定的方法:post
	 */
	@RequestMapping(value="/user/login",method=RequestMethod.POST)
	@ResponseBody
	public TaotaoResult login(HttpServletRequest request,HttpServletResponse response,String username,String password){
		//1.引入服务
		//2.注入服务
		//3.调用服务
		TaotaoResult result = loginservice.login(username, password);
		//4.需要设置token到cookie中 可以使用 工具类  cookie需要跨域
		if(result.getStatus()==200){
			CookieUtils.setCookie(request, response,TT_TOKEN_KEY, result.getData().toString());
		}
		return result;
	}

4.5 测试

使用restclient-ui-3.5-jar-with-dependencies.jar测试接口,查看Body;用zhangsansan登录。
在这里插入图片描述
然后查看redis服务器中的key
在这里插入图片描述

5. token的应用

5.1 功能分析

请求的url:/user/token/{token}
参数:String token(需要从url中取)
返回值:json数据。使用TaotaoResult包装Tbuser对象。
业务逻辑:
  1、从url中取参数。
  2、根据token查询redis。
  3、如果查询不到数据,则返回用户已经过期。
  4、如果查询到数据,则说明用户已经登录。
  5、需要重置key的过期时间。
  6、把json数据转换成TbUser对象,然后使用TaotaoResult包装并返回。

5.2 Dao

使用JedisClient对象进行查询。

5.3 Service

参数:String token
返回值:TaotaoResult
在taotao-sso-interface创建接口

	/**
	 * 根据token获取用户的信息
	 * @param token
	 * @return  TaotaoResult 应该包含用户的信息
	 */
	public TaotaoResult getUserByToken(String token);

在taotao-sso-service创建实现类

	@Override
	public TaotaoResult getUserByToken(String token) {
		//1.注入jedisclient
		//2.调用根据token查询 用户信息(JSON)的方法   get方法
		String strjson = client.get(USER_INFO+":"+token);
		//3.判断是否查询到
		if(StringUtils.isNotBlank(strjson)){
			//5.如果查询到  需要返回200  包含用户的信息  用户信息转成对象
			TbUser user = JsonUtils.jsonToPojo(strjson, TbUser.class);
			//重新设置过期时间
			client.expire(USER_INFO+":"+token, EXPIRE_TIME);
			return TaotaoResult.ok(user);
		}
		//4.如果查询不到 返回400
		return TaotaoResult.build(400, "用户已过期");
	}

注意:如果返回的是:return TaotaoResult.ok(json);
那么返回的json串中会有转义字符,如下:

{
"status": 200
"msg": "OK"
"data": "{\"id\":1,\"username\":\"zhangsan\",\"password\":null,\"phone\":\"15800807944\",\"email\":\"420840806@qq.com\",\"created\":1414119176000,\"updated\":1414119179000}"
}

这跟我们接口开发文档中的格式不一样,接口开发文档中的格式如下:

{
"status": 200
"msg": "OK"
"data": {"id":1,"username":"zhangzhijun","password":null,"phone":"15800807944","email":"420840806@qq.com","created":1414119176000,"updated":1414119179000}
}

5.4 Controller

请求的url:/user/token/{token}
参数:String token (需要从url中取)
返回值:json数据。使用TaotaoResult包装Tbuser对象。

	@RequestMapping(value="/user/token/{token}",method=RequestMethod.GET,produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
	@ResponseBody
	public String getUserByToken(@PathVariable String token,String callback){
		
		//判断是否是Jsonp请求
		if(StringUtils.isNotBlank(callback)){
			//如果是jsonp 需要拼接 类似于fun({id:1});
			TaotaoResult result = loginservice.getUserByToken(token);
			String jsonpstr = callback+"("+JsonUtils.objectToJson(result)+")";
			return jsonpstr;
		}
		//如果不是jsonp
		//1.调用服务
		TaotaoResult result = loginservice.getUserByToken(token);
		return JsonUtils.objectToJson(result);
	}

5.5 测试

使用redis的token
在这里插入图片描述

切换get请求
在这里插入图片描述

或直接在浏览器中输入URL即可。

6、登录注册功能实现

6.1 展示登录及注册页面及首页超链接修改

第一步:加入静态文件及JSP页面

第二步:在taotao-sso-web的springmvc.xml文件中配置资源映射标签,即不拦截静态资源。

第三步:点击门户首页登录按钮,浏览器URL为:
  http://localhost:8084/page/login,报错:

第四步:点击门户首页的免费注册按钮,浏览器的URL为:
  http://localhost:8084/page/register,报错:

由此我们发现,其实登录注册的URL非常类似:
  http://localhost:8084/page/register
  http://localhost:8084/page/login

所以通过/page/{page}的方式请求,通过URL模板映射,使用@PathVariable注解即可接收请求,转发jsp页面。。
编写PageController

/**
 * @Description 跳转页面使用的controller
 * @Author LH
 * @Date  2020/9/28 12:14
 **/
@Controller
public class PageController {
	
	@RequestMapping("/page/{page}")
	public String showPage(@PathVariable String page){
		return page;
	}
}

第五步:修改端口号为访问登录注册的端口号为8088

一定要注意修改的是taotao-portal-web中的base-v1.js文件,不要像我修改的是taotao-sso-web的js文件,最后咋搞都搞不出来。。。。。
在这里插入图片描述

6.2 测试

注册功能实现

/taotao-sso-web/src/main/webapp/WEB-INF/jsp/register.jsp分析:

提交之前检查:

注册,提交表单:
在这里插入图片描述

分析得出,此时的登录功能应该是可以使用了。
登录功能实现

/taotao-sso-web/src/main/webapp/WEB-INF/jsp/login.jsp分析:

文档加载时,调用方法,一旦点击,则提交表单:

表单提交:

分析得出,登录功能也是可以使用的。
在这里插入图片描述

7. 门户首页展示用户名

在这里插入图片描述

7.1 首页展示用户名分析

1、在taotao-sso-web工程中,当用户登录成功后,在cookie中有token信息。
2、在taotao-portal-web工程中,从cookie中取出token,携带token发送格式为jsonp的请求,根据token从taotao-sso-web工程中查询用户信息。
3、把从taotao-sso-web工程中查询到的用户名展示到首页taotao-portal-web工程中。

方案一:在Controller中取cookie中的token数据,调用sso服务查询用户信息。
  缺点:由于淘淘商城首页footer.jsp,在每个系统中都有,可以在每一个系统的footer.jsp中写一个ajax发起请求调用当前系统的Controller,设置模型数据,然后展示数据。麻烦!
方案二:当页面加载完成后使用js取cookie中token的数据,使用ajax请求查询用户信息的JSON数据。只需要在页面实现一次即可。
乍一看方案一与方案二是一样的,其实不是,方案一需要在每一个系统都编写Controller,方案二只需要在taotao-sso-web编写一个Controller即可。而且方案一是立即可行的,但是方案二的服务接口在sso系统中。sso.taotao.com(localhost:8088),在首页显示用户名称,首页的域名是www.taotao.com(localhost:8082),使用ajax请求跨域了。
什么是跨域:
  1、域名不同。
  2、域名相同端口不同。

例如:
www.taotao.com --> 请求 www.taobao.com 也是跨域
www.taotao.com --> 请求 sso.taotao.com 也是跨域
www.taotao.com:8080 --> www.taotao.com:8088 也是跨域
localhost:8080 --> localhost:8088 也是跨域
www.taotao.com --> www.taotao.com 不是跨域

只有域名和端口完全一样才不是跨域。
js不可以跨域请求JSON数据。
解决js的跨域问题可以使用jsonp。
jsonp不是新技术是跨域的解决方案。使用js的特性绕过跨域请求,特性:js可以跨域加载js文件。

7.2 jsonp原理

举个非常常见的例子,我们在html头部一般都会引入很多js,甚至我们直接引用在线的js,比如我们引用官方网站的jQuery路径加载进来也是可以的。jQuery的官方域名与我们的工程所在的域名肯定是不一样的,但是不影响使用,这就是我们所说的js可以跨域请求js文件!
即:ajax无法跨域请求别的url,我们可以使用ajax跨域加载js文件。

http://localhost:8088/user/token/4ffd07a2-1f92-4601-94ff-2ab763931018?callback=fun
8088做处理:查询到JSON数据,拼接成fun({“id”:1});
8082浏览器加载fun({“id”:1});
调用fun方法,里面参数就是JSON数据。做相关的处理。

7.3 jsonp实现在工程中

修改taotao-sso-web中的taotao.js
在这里插入图片描述
更改UserLoginController的getUserByToken方法

@RequestMapping(value="/user/token/{token}",method=RequestMethod.GET,produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
	@ResponseBody
	public String getUserByToken(@PathVariable String token,String callback){
		
		//判断是否是Jsonp请求
		if(StringUtils.isNotBlank(callback)){
			//如果是jsonp 需要拼接 类似于fun({id:1});
			TaotaoResult result = loginservice.getUserByToken(token);
			String jsonpstr = callback+"("+JsonUtils.objectToJson(result)+")";
			return jsonpstr;
		}
		//如果不是jsonp
		//1.调用服务
		TaotaoResult result = loginservice.getUserByToken(token);
		return JsonUtils.objectToJson(result);
	}

7.4 测试效果

在这里插入图片描述

8. 参考博客

day78_淘淘商城项目_11_实现SSO系统的登录注册功能 + 门户首页展示用户名 + ajax请求跨域问题详解_匠心笔记

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值