(八) play之yabe项目 【身份验证】


添加身份验证

play提供了一个模块-Secure(安全模块),用来做身份验证

 

允许Secure模块

修改yabe\conf\dependencies.yml,加入对secure的依赖

Html代码   收藏代码
  1. # Application dependencies  
  2.   
  3. require:  
  4.     - play -> crud  
  5.     - play -> secure  

 

cmd命令行执行dependencies命令

E:\technology-hqh\proj\play-framework\yabe>play dependencies

 

cmd命令行执行eclipsify命令

E:\technology-hqh\proj\play-framework\yabe>play eclipsify

 

刷新工程,IDE中便可导入依赖包

 

修改yabe\conf\routes文件,为secure配置路由

Html代码   收藏代码
  1. # Routes  
  2. # This file defines all application routes (Higher priority routes first)  
  3. # ~~~~  
  4.   
  5. # Home page  
  6. GET     /                                       Application.index  
  7.   
  8. #import Secure routes  
  9. *       /                                       module:secure  
  10.   
  11. # restful style route  
  12. GET     /post/{id}                              Application.show  
  13.   
  14. POST    /post/{postId}/comments                 Application.postComment  
  15.   
  16. GET     /captcha                                Application.captcha  
  17.   
  18.   
  19. #import CRUD module  
  20. *       /admin                                  module:crud  
  21.   
  22.   
  23. # Ignore favicon requests  
  24. GET     /favicon.ico                            404  
  25.   
  26. # Map static resources from the /app/public folder to the /public path  
  27. GET     /public/                                staticDir:public  
  28.   
  29. # Catch all  
  30. *       /{controller}/{action}                  {controller}.{action}  

 

 Secure需要的配置基本完成,访问主页仍然可以直接进入

这是因为还没有对任何Controller指定是否需要验证,所以还Secure还没开始工作!

 

 为控制器添加身份验证

使用注解@With(Secure.class)标识Controller,Secure就会对访问该控制器进行身份验证

如,在Application类上加@With(Secure.class)

Java代码   收藏代码
  1. @With(Secure.class)  
  2. public class Application extends Controller {  
  3.   
  4.     ......  
  5.   
  6. }  

 刷新页面,Secure开始工作了



 

随便输入用户名和密码都可以登录,即系统验证功能还没有真正开始

使用http://localhost:9000/logout 可以注销登录

 

 

定制系统身份认证

应用程序必须提供一个controllers.Secure.Security实例来定制身份认证处理。

通过继承这个类来创建我们自己版本的Secure类,可以指定如何对用户身份进行认证。
yabe\app\controllers下创建Security,重写authenticate()

Java代码   收藏代码
  1. package controllers;  
  2.   
  3. import models.User;  
  4.   
  5. public class Security extends Secure.Security {  
  6.       
  7.      static boolean authenticate(String username, String password) {  
  8.          return User.login(username, password);  
  9.      }  
  10. }  

 修改User类,增加username属性,以及一个以username和password为条件的查询的方法

 

Java代码   收藏代码
  1. package models;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import javax.persistence.Entity;  
  7. import javax.persistence.OneToMany;  
  8.   
  9. import play.data.validation.Email;  
  10. import play.data.validation.Required;  
  11. import play.db.jpa.Model;  
  12.   
  13. @Entity  
  14. public class User extends Model {  
  15.       
  16.     @Required  
  17.     public String username;  
  18.       
  19.     @Email  
  20.     @Required  
  21.     public String email;  
  22.       
  23.     @Required(message="input your pwd now!")  
  24.     public String password;  
  25.       
  26.     public String fullname;  
  27.       
  28.     public boolean isAdmin;  
  29.       
  30.     //@OneToMany 声明User与Post之间是1对多的关系  
  31.     //mappedBy="author" 表示将通过对方(User)的author字段来进行关联关系的维护  
  32.     @OneToMany(mappedBy="author")  
  33.     public List<Post> posts;  
  34.       
  35.       
  36.     public User(String email,String password, String fullname) {  
  37.         this.email = email;  
  38.         this.password = password;  
  39.         this.fullname = fullname;  
  40.         this.posts = new ArrayList<Post>(0);  
  41.     }  
  42.       
  43.     /** 
  44.      * 联合email和password两个条件查询User 
  45.      * @param email 
  46.      * @param password 
  47.      * @return 
  48.      */  
  49.     public static User connect(String email, String password) {  
  50.         return find("byEmailAndPassword", email, password).first();  
  51.     }  
  52.       
  53.     /** 
  54.      * 登陆时根据username和password查询User 
  55.      * 如果存在,则允许登陆 
  56.      * @param username 
  57.      * @param password 
  58.      * @return 
  59.      */  
  60.     public static boolean login(String username, String password) {  
  61.         return find("byUsernameAndPassword", username, password).first() != null;  
  62.     }  
  63.       
  64.     /** 
  65.      * 添加Post的动作放到User中,这样可以把Post设置到User的List<Post>集合中 
  66.      * 这样实现了双方都持有对方的引用了 
  67.      * @param title 
  68.      * @param content 
  69.      * @return 
  70.      */  
  71.     public User addPost(String title, String content) {  
  72.         Post post = new Post(title,content,this).save();  
  73.         this.posts.add(post);  
  74.         this.save();  
  75.         return this;  
  76.     }  
  77.   
  78.     @Override  
  79.     public String toString() {  
  80.         return "User [" + fullname + "]";  
  81.     }  
  82.       
  83.       
  84.       
  85. }  

 

修改yabe\conf\initial-data.yml ,为User对象加入username初始化值

注意,该文件对TAB键不友好,只认空格符作为间隔

Html代码   收藏代码
  1. # Test data  
  2.   
  3. User(bob):  
  4.     username:       bob  
  5.     email:          bob@gmail.com  
  6.     password:       secret  
  7.     fullname:       Bob  
  8.     isAdmin:        true  
  9.       
  10. User(jeff):  
  11.     username:       jeff  
  12.     email:          jeff@gmail.com  
  13.     password:       secret  
  14.     fullname:       Jeff      
  15.       
  16. User(paul):  
  17.     username:       paul  
  18.     email:          paul@gmail.com  
  19.     password:       secret  
  20.     fullname:       Paul      
  21.   
  22. ...  

 http://localhost:9000/logout  注销,重新登陆

此时,只有输入正确的用户名和密码才能进入系统了

有效账户yml中的初始用户:[bob,secret] :[jeff,secret]:[paul,secret]

但是,对CRUD页面 http://localhost:9000/admin/ 控制不起作用!



 

 

集成CRUD管理域到博客中

超级用户可以管理所有的博客

普通用户可以管理自己的博客

 

首先,看一下用户登陆验证的内部执行逻辑

public class Secure extends Controller中的方法

Java代码   收藏代码
  1. /** 
  2.  * 登陆页面,点击登陆,将执行此方法 
  3.  * username 
  4.  * password 
  5.  * checkbox框 --- remember 
  6.  */  
  7. public static void authenticate(@Required String username, String password, boolean remember) throws Throwable {  
  8.         // Check tokens  
  9.         Boolean allowed = false;  
  10.         try {  
  11.             // This is the deprecated method name  
  12.             // 该方法废弃,所以这里总会抛异常,进而执行catch块的代码  
  13.             allowed = (Boolean)Security.invoke("authentify", username, password);  
  14.         } catch (UnsupportedOperationException e ) {  
  15.             // This is the official method name  
  16.             // 子类(class Security extends Secure.Security)复写了authenticate(),所以这里将调用我们自己的authenticate(),根据用户名和密码查询数据库  
  17.             allowed = (Boolean)Security.invoke("authenticate", username, password);  
  18.         }  
  19.         if(validation.hasErrors() || !allowed) {  
  20.             flash.keep("url");  
  21.             flash.error("secure.error");  
  22.             params.flash();  
  23.             login();  
  24.         }  
  25.         // Mark user as connected  
  26.         // 如果登陆成功,session中存入登陆用户名  
  27.         session.put("username", username);  
  28.         // Remember if needed  
  29.         // 如果需要保存登陆状态,则应勾选登陆页面的checkbox框  
  30.         if(remember) {  
  31.             Date expiration = new Date();  
  32.             String duration = "30d";  // maybe make this override-able   
  33.             expiration.setTime(expiration.getTime() + Time.parseDuration(duration));  
  34.             response.setCookie("rememberme", Crypto.sign(username + "-" + expiration.getTime()) + "-" + username + "-" + expiration.getTime(), duration);  
  35.   
  36.         }  
  37.         // Redirect to the original URL (or /)  
  38.         redirectToOriginalURL();  
  39.     }  

 

 

创建一个新的Controller,该控制器用来对CRUD管理界面进行控制

 

Java代码   收藏代码
  1. package controllers;  
  2.   
  3. import models.User;  
  4. import play.mvc.Before;  
  5. import play.mvc.Controller;  
  6.   
  7. public class Admin extends Controller {  
  8.       
  9.     /** 
  10.      * 首先,用户登陆会被Security拦截,登陆成功会把username放入session中 
  11.      * session.put("username",username); 
  12.      * 通过session.contains("username");判断用户是否已经登陆 
  13.      *  
  14.      * @Before 访问Admin控制器时,将先执行由该注解标注的方法,进行拦截(过滤/检查) 
  15.      */  
  16.     @Before  
  17.     static void setConnectedUser() {  
  18.         //Security.isConnected() 检查session中是否有username为key的map存在  
  19.         //因为用户登陆后会用username作为key存储登陆信息  
  20.         if(Security.isConnected()) {  
  21.             //Security.connected() 取得session中以username为key的value,即用户名  
  22.             User user = User.find("byUsername", Security.connected()).first();  
  23.             renderArgs.put("user", user.fullname);  
  24.         }  
  25.     }  
  26.       
  27.     //返回管理CRUD功能模块的主页面  
  28.     public static void index(){  
  29.         render();  
  30.     }  
  31.       
  32.       
  33.       
  34. }  

 为Admin控制器的index()添加模板

创建yabe\app\views\Admin\index.html

Html代码   收藏代码
  1. Welcome ${user}!  

 

在主页面为Admin为CRUD功能模块加入超链接

更改Log in to write something的href属性,使其指向Admin的index()

Html代码   收藏代码
  1. <ul id="tools">  
  2.     <li><a href="@{Admin.index()}">Log in to write something</a></li>  
  3. </ul>  

 

刷新页面



 

 跳转到管理页面

当然,这里只是简单取了一下Admin.java中 @Before标注的setConnectedUser()方法所设置的用户名

 到这里,完成了2个操作

一是Admin控制器中使用了拦截器,通过@Before进行设置

二是从Security.connected()中获得当前登陆的用户名,再使用renderArgs.put(key,value)将信息传递到页面中,以便进行显示当前登陆用户。

 

为CRUD模块管理页面配置一个模板

这里为admin/index.html配置一个父模板,对页面进行统一的设置(标题,附加信息,版权声明等)

 

Html代码   收藏代码
  1. <!DOCTYPE html>  
  2.   
  3. <html>  
  4.     <head>  
  5.         <title>Administration</title>  
  6.         <meta charset="utf-8">  
  7.         <link rel="stylesheet" media="screen" href="@{'/public/stylesheets/main.css'}">  
  8.         <link rel="shortcut icon" type="image/png" href="@{'/public/images/favicon.png'}">  
  9.         <script src="@{'/public/javascripts/jquery-1.6.4.min.js'}" type="text/javascript"></script>  
  10.         <script src="@{'/public/javascripts/jquery.tools-1.2.5.toolbox.expose.min.js'}" type="text/javascript"></script>  
  11.     </head>  
  12.       
  13.     <body id="admin">  
  14.         <!-- 页面顶部显示的信息 -->  
  15.         <div id="header">  
  16.             <div id="log">yabe. <span>administration</span></div>  
  17.             <ul id="tools">  
  18.                 <!-- 调用Secure的logout()进行注销 -->  
  19.                 <li><a href="@{Secure.logout()}">Log out</a></li>  
  20.             </ul>  
  21.         </div>  
  22.           
  23.         <!-- 子模板内容显示区 -->  
  24.         <div id="main">  
  25.             #{doLayout /}  
  26.         </div>  
  27.           
  28.         <!-- 页脚 -->  
  29.         <p id="footer">  
  30.             Yabe is a (not that) powerful bolg engine built with the   
  31.             <a href="http://playframework.org">Play framework</a> as a tutorial application.  
  32.         </p>  
  33.     </body>  
  34. </html>  

 

刷新页面



 

可见模板已经开始生效了,点击logout,则登出

这里调用的是Secure内部的logout(),而且可以复写Secure中的其它方法

比如,登出之后需要进行某些操作,则复写onDisconnected() 

比如,登录成功之后要进行某些操作,则复写onAuthenticated() 

Java代码   收藏代码
  1. package controllers;  
  2.   
  3. import play.Logger;  
  4. import models.User;  
  5.   
  6. public class Security extends Secure.Security {  
  7.        
  8.      static boolean authenticate(String username, String password) {  
  9.          return User.login(username, password);  
  10.      }  
  11.        
  12.      /** 
  13.       * 登陆成功后会调用onAuthenticated() 
  14.       */  
  15.      static void onAuthenticated() {  
  16.          Logger.info(Secure.Security.connected()+"\tlogin");  
  17.          //Admin.index();  
  18.                  //登陆成功后,自动跳转到管理页面  
  19.      }  
  20.        
  21.      /** 
  22.       * 注销后会调用onDisconnected() 
  23.       */  
  24.      static void onDisconnected() {  
  25.          Logger.info(Secure.Security.connected()+"\tloginlogout");  
  26.          Application.index();  
  27.      }  
  28.   
  29. }  

到此,CRUD管理页面尚未提供任何可操作的功能

现在,继续编辑模板,加入CRUD的超链接到管理页面

Html代码   收藏代码
  1. <!DOCTYPE html>  
  2.   
  3. <html>  
  4.     <head>  
  5.         <title>Administration</title>  
  6.         <meta charset="utf-8">  
  7.         <link rel="stylesheet" media="screen" href="@{'/public/stylesheets/main.css'}">  
  8.         <link rel="shortcut icon" type="image/png" href="@{'/public/images/favicon.png'}">  
  9.         <script src="@{'/public/javascripts/jquery-1.6.4.min.js'}" type="text/javascript"></script>  
  10.         <script src="@{'/public/javascripts/jquery.tools-1.2.5.toolbox.expose.min.js'}" type="text/javascript"></script>  
  11.     </head>  
  12.       
  13.     <body id="admin">  
  14.         <!-- 页面顶部显示的信息 -->  
  15.         <div id="header">  
  16.             <div id="log">yabe. <span>administration</span></div>  
  17.             <ul id="tools">  
  18.                 <!-- 调用Secure的logout()进行注销 -->  
  19.                 <li><a href="@{Secure.logout()}">Log out</a></li>  
  20.             </ul>  
  21.         </div>  
  22.           
  23.         <!-- 子模板内容显示区 -->  
  24.         <div id="main">  
  25.             <ul id="adminMenu">  
  26.                 <!-- 如果登陆用户是超级管理员,则显示 -->  
  27.                 <li class="${request.controller == 'Admin' ?  'selected' : ''}">  
  28.                     <a href="@{Admin.index()}">My Posts</a>  
  29.                 </li>  
  30.                 <!-- 使用Secure.check() 控制是否为超级用户,如果是,则显示下面的<li/> -->  
  31.                 #{secure.check 'admin'}  
  32.                 <li class="${request.controller == 'Posts' ? 'selected' : ''}">  
  33.                     <a href="@{Posts.list()}">Posts</a>  
  34.                 </li>  
  35.                 <li class="${request.controller == 'Comments' ? 'selected' : ''}">  
  36.                     <a href="@{Comments.list()}">Comments</a>  
  37.                 </li>  
  38.                 <li class="${request.controller == 'Users' ? 'selected' : ''}">  
  39.                     <a href="@{Users.list()}">Users</a>  
  40.                 </li>  
  41.                 <!-- 注意这里的结束标签是写在前面的! -->  
  42.                 #{/secure.check}  
  43.             </ul>  
  44.             #{doLayout /}  
  45.         </div>  
  46.       
  47.         <!-- 页脚 -->  
  48.         <p id="footer">  
  49.             Yabe is a (not that) powerful bolg engine built with the   
  50.             <a href="http://playframework.org">Play framework</a> as a tutorial application.  
  51.         </p>  
  52.     </body>  
  53. </html>  

 

登出,使用一个isAdmin属性为false的账户(jeff或paul)进行登陆

打开管理页面,注意,当前是用paul进行登陆的,非管理员权限,但是其可以看到所有的博文

这是不行的!非管理员只应该看到自己的博文!



 

虽然admin.html模板中,已经使用了判断是否为'admin'属性了,但是,请注意,play默认的check()返回的是true。就好像做登陆那里一样,子类没有重写authenticate()时,不管用什么用户登陆都成功一样。

 

在Security控制器中覆盖Secure.Security的check()

Java代码   收藏代码
  1. package controllers;  
  2.   
  3. import play.Logger;  
  4. import models.User;  
  5.   
  6. public class Security extends Secure.Security {  
  7.        
  8.      /** 
  9.       * 覆盖Secure.Security中的authenticate() 
  10.       * 这样,play在进行登录验证时,就会调用到子类写的方法了 
  11.       * @param username 
  12.       * @param password 
  13.       * @return 
  14.       */  
  15.      static boolean authenticate(String username, String password) {  
  16.          return User.login(username, password);  
  17.      }  
  18.        
  19.      /** 
  20.       * 登陆成功后会调用onAuthenticated() 
  21.       */  
  22.      static void onAuthenticated() {  
  23.          Logger.info(Secure.Security.connected()+"\tlogin");  
  24.      }  
  25.        
  26.      /** 
  27.       * 注销后会调用onDisconnected() 
  28.       */  
  29.      static void onDisconnected() {  
  30.          Logger.info(Secure.Security.connected()+"\tloginlogout");  
  31.          Application.index();  
  32.      }  
  33.        
  34.        
  35.      /** 
  36.       * 用户登陆成功后,继续对其操作权限进一步校验 
  37.       * 如果User的isAdmin属性为true,则返回true,即其状态为'admin' 
  38.       * @param profile  play将登陆用户的用户名传入 
  39.       * @return 
  40.       */  
  41.      static boolean check(String profile) {  
  42.          if("admin".equals(profile)) {  
  43.              return User.find("byUsername", Secure.Security.connected()).<User>first().isAdmin;  
  44.          }  
  45.          return false;  
  46.      }  
  47.   
  48. }  

 

 刷新页面,可以看到系统中所有实体对象的CRUD链接都没有呈现了,因为paul不是管理员



 

 登出,使用bob进行登陆

 由于bob的isAdmin属性为true,所以,他能看到所有的实体对象的CRUD链接

 

到这里,完成一半的工作了。

接下来考虑的是,普通用户登陆后,使用超管进行CRUD的链接直接操作资源

虽然他的权限不够导致页面无法呈现管理模块的链接,但他可以自己手动输入链接来访问资源

比如,paul不是超管,但是他知道管理博文的地址:http://localhost:9000/admin/posts 

paul登陆后,手动链接到这个地址,一样可以进行CRUD操作

该如何是好?

 

第一,对CURD进行控制

当前,任何人都能通过http://localhost:9000/admin/访问CRUD管理对象

现在就对其进行控制

在每个实体对象对应的Controller上加入注解@With(Secure.class),表示访问该资源需要进行身份认证操作,如果认证失败,则由play自动跳转到index页面

Java代码   收藏代码
  1. package controllers;  
  2.   
  3. import play.mvc.With;  
  4.   
  5. @With(Secure.class)  
  6. public class Posts extends CRUD {  
  7.   
  8. }  

 

Java代码   收藏代码
  1. package controllers;  
  2.   
  3. import play.mvc.With;  
  4.   
  5. @With(Secure.class)  
  6. public class Comments extends CRUD {  
  7.   
  8. }  

 

Java代码   收藏代码
  1. package controllers;  
  2.   
  3. import play.mvc.With;  
  4.   
  5. @With(Secure.class)  
  6. public class Users extends CRUD {  
  7.   
  8. }  

 

现在,未登陆用户虽然能进入CRUD页面,但是无法进行对象的操作了!

 

第二,防止无超管权限的用户手工输入CRUD的管理链接进行非法操作

同样是通过注解来完成@check("admin"),检查当前用户是否具备超管权限

play考虑很周到,每个小功能都用一个注解来完成,很贴心!

Java代码   收藏代码
  1. package controllers;  
  2.   
  3. import play.mvc.With;  
  4.   
  5. //访问Post对象的列表,需要检查是是否登陆成功,没有,则返回登陆页面  
  6. @With(Secure.class)  
  7.   
  8. //操作Post对象,是否具有超管权限,没有,则提示Access Denied  
  9. @Check("admin")  
  10. public class Posts extends CRUD {  
  11.   
  12. }  

 Comments、Users进行同样的操作即可。

 

使用非管理员账号登陆,手动输入Post对象的管理链接http://localhost:9000/admin/posts

 

 

 

到此,未登陆用户不能访问CRUD页面,普通用户不能进行实体对象的CRUD操作!

 

 

修改CRUD模块的布局

为了让CRUD模块的布局与博客系统整体布局一致,需要重写其模板

 

首先要得到CRUD模板元素的布局方案

(  play实际上就是将CRUD模块拷贝到了项目的views路径下:

   Copied

   E:\technology-hqh\soft\play-1.2.5\modules\crud\app/views/CRUD/layout.html

   to

   E:\technology-hqh\proj\play-framework\yabe\app/views/CRUD/layout.html

)

E:\technology-hqh\proj\play-framework\yabe>play crud:ov --layout

 

然后运行eclipsify命令,以便让IDE能够更新到新的内容

E:\technology-hqh\proj\play-framework\yabe>play eclipsify

 

此时,IDE的views目录下便多了一个目录-CRUD,其下有一个layout.html文件

修改这个文件,让其继承admin.html,并做一些调整

Html代码   收藏代码
  1. #{extends 'admin.html' /}  
  2. #{set 'moreStyles'}  
  3.     <link rel="stylesheet" type="text/css" media="screen" href="@{'/public/stylesheets/crud.css'}" />  
  4. #{/set}  
  5.   
  6. <div id="crud">  
  7.   
  8.     #{if flash.success}  
  9.         <div class="crudFlash flashSuccess">  
  10.             ${flash.success}  
  11.         </div>  
  12.     #{/if}  
  13.     #{if flash.error || error}  
  14.         <div class="crudFlash flashError">  
  15.             ${error ?: flash.error}  
  16.         </div>  
  17.     #{/if}  
  18.   
  19.     <div id="crudContent">  
  20.         #{doLayout /}  
  21.     </div>  
  22.   
  23. </div>  

 

刷新页面,此时CRUD页面的布局发生了变化,与系统的基本保持你一致了



 

 

 

 

修改登陆页面的样式

为登陆页面定制样式

 

cmd命令行:

E:\technology-hqh\proj\play-framework\yabe>play secure:ov --css

E:\technology-hqh\proj\play-framework\yabe>play eclipsify

 

刷新IDE

 

编辑yabe\public\stylesheets\secure.css

在其文本最上方加入一行

Html代码   收藏代码
  1. @import url(main.css);   

 

默认登陆页面



 

 

刷新页面

继承了main.css,感觉没默认的漂亮哦~~~

 

 

 

修改登陆页面的文字

打开yabe\conf\messages,加入以下内容替换登陆页面的文字

Html代码   收藏代码
  1. secure.username=用户名:   
  2.   
  3. secure.password=密码:   
  4.   
  5. secure.remember=下次自动登陆  
  6.   
  7. secure.signin=登陆  

 

刷新页面



 

 

  • 大小: 12.7 KB
  • 大小: 19.3 KB
  • 大小: 27.3 KB
  • 大小: 14.6 KB
  • 大小: 29.2 KB
  • 大小: 25.7 KB
  • 大小: 30.8 KB
  • 大小: 31.1 KB
  • 大小: 27.4 KB
  • 大小: 19 KB
  • 大小: 105.6 KB
  • 大小: 67.3 KB
  • 大小: 28.4 KB
  • 大小: 28.9 KB
  • 大小: 27.9 KB

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值