Springboot SpringSecurity+Mybatis+MySQL+Thymeleaf+layui实现用户权限管理实例

Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。本文通过区分用户及管理员两个权限,实现对这两角色不同的权限控制,使得不同权限的用户登录时显示不同的页面。

首先用户注册用户信息,输入账号、密码,选定身份,之后将用户信息存入数据库的两个表中,两个表通过CustomUserDetailService中的方法相关联,登陆后用户权限和管理员权限会显示不同的页面,而用户无权访问管理员可以访问的某些页面。

SpringSecyrity认证、授权流程:
用户访问资源→security拦截→跳转到login→提交表单→处理用户信息→借助UserDetailsService 获取用户信息→认证成功/失败
目录结构如下
在这里插入图片描述

1. pom.xml和application.yml

springboot使用的版本是2.3.1.RELEASE

1.1 pom.xml文件配置

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		
		<dependency>
    		<groupId>org.thymeleaf</groupId>
    		<artifactId>thymeleaf</artifactId>
    		<version>3.0.11.RELEASE</version><!--$NO-MVN-MAN-VER$-->
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		
		<dependency>
    		<groupId>org.thymeleaf.extras</groupId>
    		<artifactId>thymeleaf-extras-springsecurity5</artifactId>
    		<version>3.0.4.RELEASE</version><!--$NO-MVN-MAN-VER$-->
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
		<dependency>
    		<groupId>org.projectlombok</groupId>
    		<artifactId>lombok</artifactId>
    		<scope>provided</scope>
    		<optional>true</optional>
		</dependency>

1.2 application.yml

Spring: 
  dataSource: 
    username: root
    password: password
    url: jdbc:mysql://localhost:3306/salary?characterEncoding=utf-8&useUnicode=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    #联接MYSQL数据库
  main:
    allow-bean-definition-overriding: true
    #所有bean允许重写

  jpa:
    database: MYSQL 
    show-sql: true
    hibernate:
      ddl-auto: update
  thymeleaf: 
    mode: HTML
    #开发引擎页面,修改要实时生效,1.禁用缓存,   2.页面修改完成后重新编译
    cache: false
   
server: 
  port: 8088
  #自定义端口
  
mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml
  type-aliases-package: com.gui.entity
  configuration:
    map-underscore-to-camel-case: true
  #mybatis配置文件

logging:
  level:
    org:
      springframework:
        security : INFO
 #日志配置文件

2.创建数据库

数据库表有 用户表,角色表,用户角色关系表三张表:
在这里插入图片描述
插入数据库数据

insert into SYS_USER (id,username, password) values (1,'admin', '123456');
insert into SYS_USER (id,username, password) values (2,'user', '123456');

insert into SYS_ROLE(id,name) values(1,'ROLE_ADMIN');
insert into SYS_ROLE(id,name) values(2,'ROLE_USER');
//ROLE_ADMIN 和 ROLE_USER是SpringSecurity里的固定写法,可参照SpringSecurity中的源码

insert into SYS_ROLE_USER(SYS_USER_ID,ROLES_ID) values(1,1);
insert into SYS_ROLE_USER(SYS_USER_ID,ROLES_ID) values(2,2);

3.启动类UserApplication

@SpringBootApplication()
@EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class })
@MapperScan("com.gui.mapper")  //扫描mapper层
public class UserApplication {
	public static void main(String[] args) { 
		       SpringApplication.run(UserSalaryApplication.class, args);
		   }
	}

4.编写实体类

实体类需要sysUser sysRole sysUserRole 三个实体类

/
	*SysUser类 用户信息类
/
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
//上面的注解帮助我们自动写出含参和无参的set、get方法。
//使用了eclipse中lombok的插件 下载详见https://www.cnblogs.com/romulus/p/11109610.html

public class SysUser implements Serializable{
	static final long serialVersionUID = 1L;
	//一般情况下,建议序列化的class都给一个序列化的ID,这样可以保证序列化的成功,版本的兼容性。
	//详解请见 https://blog.csdn.net/u013325815/article/details/52041103
	
	@Id
	@GeneratedValue
	private Integer id;       //数据库中设置id自增
	private String username;  //账号
	private String password;  //密码
	private String password1; //再次输入密码确认两次输入是否一致
}
/
	*SysRole类 用户权限类
/
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysRole {
	@Id
	private Integer id;   //id自增
	private String name;  //权限
}
/
	*SysUserRole类  关联SysUser和SysRole两个实体类
/
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysUserRole implements Serializable{
	static final long serialVersionUID = 1L;
	@Id
	private Integer id;
	
	private Integer userId;
	private Integer roleId;
}

5.编写mapper service serviceImpl层

5.1 SysUser对应的业务层如下

//SysUserMapper类
@Repository
public interface SysUserMapper { 
	public Integer insertSysUser(SysUser sysUser);
	public SysUser selectByName(@Param("username") String username); 
	//@Param的作用是在Mybatis提供的作为dao层注解,传递参数的工具
	public SysUser selectById(@Param("id") Integer id);
	public Integer deleteSysUser(String username);
	public Integer updateSysUser(SysUser sysUser);
	public Integer insertInfoSysUser(SysUser sysUser);
	public List<SysUser> selectSysUser();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC 
  "-//mybatis.org//DTD mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gui.mapper.SalaryMapper">
  
  <select id="listSalaryAll" resultType="com.gui.entity.Salary" parameterType="String">
   select * from salary
  </select>
  
  <select id="selectSalaryById" parameterType="String" resultType="com.gui.entity.Salary">
  	select * from salary where id=#{id}
  </select>
  
  <delete id="deleteSalary" parameterType="String">
  	delete from salary where id=#{id}
  </delete>
  
  <insert id="insertSalary" parameterType="com.gui.entity.Salary">
  	insert into salary (id, name, sex, time, birth, salary, home) values (#{id}, #{name}, #{sex}, #{time}, #{birth}, #{salary}, #{home})
  </insert>
  
  <update id="updateSalary" parameterType="com.gui.entity.Salary">
  	update salary set id=#{id}, name=#{name}, sex=#{sex}, time=#{time}, birth=#{birth}, salary=#{salary}, home=#{home} where id=#{id}
  </update>
  
</mapper>
@Service
public interface SysUserService {
	public Integer insertUser(SysUser sysUser);
	public SysUser selectByName(String username);
	public SysUser selectById(Integer id);
	public Integer deleteSysUser(String username);
	public Integer updateSysUser(SysUser sysUser);
	public Integer insertInfoSysUser(SysUser sysUser);
	public List<SysUser> selectSysUser();
}
@Service
public class SysUserServiceImpl implements SysUserService{

		@Autowired
		private SysUserMapper sysUserMapper;
		
		@Override
		public Integer insertUser(SysUser sysUser) {
			return sysUserMapper.insertSysUser(sysUser);
		}
		
		@Override
		public SysUser selectByName(String username) {
			return sysUserMapper.selectByName(username);
		}
		
		@Override
		public SysUser selectById(Integer id) {
			return sysUserMapper.selectById(id);
		}
		
		@Override
		public Integer deleteSysUser(String username) {
			return sysUserMapper.deleteSysUser(username);
		}
		
		@Override
		public Integer updateSysUser(SysUser sysUser) {
			return sysUserMapper.updateSysUser(sysUser);
		}
		
		@Override
		public Integer insertInfoSysUser(SysUser sysUser) {
			return sysUserMapper.insertInfoSysUser(sysUser);
		}
		
		@Override
		public List<SysUser> selectSysUser() {
			return sysUserMapper.selectSysUser();
		}
}

5.2 sysRole对应的业务层

sysRole和sysUserRole层只列出mapper层,service层和serviceImpl过于简单 参照上面即可

SysRoleMapper类和SysRole.xml

@Repository
public interface SysRoleMapper {
	public SysRole selectById(Integer id);
	public Integer insertRole(String name);
	public Integer updateRole(String name, Integer id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gui.mapper.SysRoleMapper">
  
  <select id="selectById" parameterType="Integer" resultType="com.gui.entity.SysRole">
  	select * from sys_role where id = #{id}
  </select>
  
  <insert id="insertRole" parameterType="string">
  	insert into sys_role (name) values (#{name})
  </insert>
  
  <update id="updateRole" parameterType="com.gui.entity.SysRole">
    update sys_role set name = #{name} where id = #{id}
  </update>
</mapper>

5.3 SysUserRole对应的业务层

SysUserRoleMapper类和SysUserRoleMapper.xml

@Repository
public interface SysUserRoleMapper {
	List<SysUserRole> listByUserId(Integer userId);
	public Integer sysUserRoleInsert(Integer user_id, Integer role_id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gui.mapper.SysUserRoleMapper">
  
  <select id="listByUserId" parameterType="Integer" resultType="com.gui.entity.SysUserRole">
  	select * from sys_user_role where id = #{id}
  </select>
  
  <insert id="sysUserRoleInsert">
    insert into sys_user_role (user_id, role_id) values(#{user_id}, #{role_id})
  </insert>
</mapper>

6.config层

6.1 MyMvcConfig

@Configuration
//表示配置类

public class MyMvcConfig implements WebMvcConfigurer{  
	@Override
	public void addViewControllers(ViewControllerRegistry registry) {  
	//对视图控制器的快速配置  ViewControllerRegistry是视图控制器
		registry.addViewController("/register.html").setViewName("register");
		registry.addViewController("/login.html").setViewName("login");
		registry.addViewController("/index.html").setViewName("index");
	}
}

6.2 WebSecurityConfig

在SpringSecurity中,通过WebSecurityConfig类完成最关键的认证、授权配置。

configure(AuthenticationManagerBuilder auth)方法(身份验证管理器),调用UserDetailService 和 PasswordEncoder比对加密后密码的密码是否与数据库中密码匹配,如果匹配,则返回一个Authentication 对象。

configure(WebSecurity)用于影响全局安全性(配置资源,设置调试模式,通过实现自定义防火墙定义拒绝请求)的配置设置。

configure(HttpSecurity httpSecurity)方法用于实现业务层的功能,自定义登入登出,配置用户权限,匿名用户配置,增加CSRF支持等业务实现功能。

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 //SpringSecurity主要做两件事,一件是认证,一件是授权。
	@Bean
	public PasswordEncoder passwordEncoder(){
	    return new BCryptPasswordEncoder();  //注册BCrypt加密类
	}

	 @Bean
	 UserDetailsService customUserService(){ 
	 //注册UserDetailsService 的bean,实例化CustomUserDetailsService();
	 //该类实现UserDetailsServer接口,从数据库获取用户名,密码,角色权限
	      return new CustomUserDetailsService();
	 }
	 
	 @Override
	public void configure(WebSecurity web) throws Exception{
          super.configure(web);  
          //configure(WebSecurity)用于影响全局安全性(配置资源,设置调试模式通过实现自定义防火墙定义拒绝请求)的配置设置。
     }
	 
	 @Override
	 protected void configure(AuthenticationManagerBuilder auth) throws Exception {       //AuthenticationManagerBuilder身份验证管理器
	     auth.userDetailsService(customUserService()).passwordEncoder(passwordEncoder());
	      //把前端传来的数据加密
	 }
	
	@Override
	 protected void configure(HttpSecurity httpSecurity) throws Exception {  //HTTP请求安全处理
        httpSecurity
        	.authorizeRequests()
            	.antMatchers("/js/**").permitAll() //静态资源存放的位置
                .antMatchers("/register.html","/register","/login","/login.html").permitAll()//登陆、注册页面,和跳转的URL, permitAll()无条件允许访问
                .antMatchers("/index.html").hasAnyRole("ADMIN","USER") //hasAnyRole()用户拥有该权限可访问
               
     			.antMatchers("/UserSalary.html","/update.html","/insert.html").hasRole("ADMIN")
                .anyRequest().authenticated()  //任意匹配此url的用户需要身份验证
                .and()
            .formLogin()
                .loginPage("/login.html")          //未登录时跳转此页面
                .loginProcessingUrl("/login")
                .failureUrl("/login?error")        //登陆失败时的跳转
                .defaultSuccessUrl("/index.html")  //登陆成功后默认跳转路径
                .permitAll()
                .and()
            .rememberMe().tokenValiditySeconds(1209600).key("mykey")//设置浏览器的记忆remember功能
            	.and()
            .logout()
                .logoutSuccessUrl("/login?logout")  //退出登录
                .permitAll()
                .and()
            .csrf().disable(); //跨域保护
    }
}

7. CustomUserDetailService

@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService{
	@Autowired
	private SysUserService userService;
	
	@Autowired
	private SysRoleSecvice roleSecvice;
	
	@Autowired
	private SysUserRoleService userRoleService;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
	Collection<GrantedAuthority> authorities = new ArrayList<>();
		//从数据库中读取信息
		SysUser user = userService.selectByName(username);  //获得用户信息
		
		//判断用户是否存在
		if(user == null) {
			throw new UsernameNotFoundException("用户名不存在");
		}
		
		//添加权限
		List<SysUserRole> userRoles = userRoleService.listByUserId(user.getId());
		for(SysUserRole userRole : userRoles) {
			SysRole role = roleSecvice.selectById(userRole.getRoleId());
			authorities.add(new SimpleGrantedAuthority(role.getName()));//用authorities储存对应用户的权限
		}
		
		//返回UserDetails的实现类
		String password = user.getPassword(); //得到加密后的密码
		return new User(user.getUsername(), password, authorities);
	}
}

8.Controller层

8.1 LoginController

@Controller
public class LoginController {
	@RequestMapping("/login")
	public String login() {
		return "redirect:/index.html";  //认证通过后跳转到主页
	}
}

8.2 RegisterController

@Controller
public class RegisterController {
	@Autowired
	private SysUserServiceImpl sysUserService;
	
	@Autowired
	private SysRoleServiceImpl sysRoleService;
	
	@Autowired
	private SysUserRoleService sysUserRoleService;
	
	BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
	@PostMapping("/register")
	public String postRegisterController(SysUser sysUser, String name) {
		
		String encodedPassword = passwordEncoder.encode(sysUser.getPassword().trim());
		//将用户输入的密码加密
		sysUser.setPassword(encodedPassword);
		//将加密后的密码储存到sysUser中
		
		sysUserService.insertUser(sysUser);  
		sysRoleService.insertRole(name); //向数据库中插入数据
		
		Integer userId = sysUserService.selectByName(sysUser.getUsername()).getId();
		//获得插入数据后的该列id
		sysUserRoleService.sysUserRoleInsert(userId, userId);
		//将id插入到数据库sys_user_role中
		return "login.html";
	}
}

9.登录页,注册页面,主页

9.1登录页login.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="renderer" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>用户登陆界面</title>
   <link rel="stylesheet" th:href = "@{/js/layui/css/layui.css}"> 
   <script th:src="@{/js/layui/layui.js}"></script>
</head>
<body>
 
<!-- 你的HTML代码 -->
<form class="layui-form" th:action="@{login}" method="post">

	<fieldset class="layui-elem-field layui-field-title" style="margin-top: 50px;">
          <legend>登录</legend>
    </fieldset>

    <div class="layui-form-item">
      <label class="layui-form-label">账号</label>
      <div class="layui-input-inline">
        <input type="username" name="username" required  lay-verify="username" placeholder="请输入用户名" autocomplete="off" class="layui-input">
      </div>  <!-- lay-verify表单验证的关键字 -->
      <i class="layui-icon" style="font-size: 30px; color: #1E9FFF;"></i>
    </div>

    <div class="layui-form-item">
      <label class="layui-form-label">密码</label>
      <div class="layui-input-inline">
        <input type="password" name="password" required lay-verify="password" placeholder="请输入密码" autocomplete="off" class="layui-input">
      </div>
      <div class="layui-form-mid layui-word-aux"></div>
             <i class="layui-icon layui-icon-password" style="font-size: 30px; color: #1E9FFF;"></i>
    </div>
    
    <div class="layui-form-item">
        <div class="layui-input-block">
          <button class="layui-btn" lay-submit lay-filter="formDemo">登录</button>  <!-- lay-filter类似于选择器 -->
          <a href="register.html"><button type="button" class="layui-btn">注册</button></a>
        </div>
    </div>
    
 </form>
<script>
</script> 
</body>
</html>

9.2注册页面register.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="utf-8">
  <meta name="renderer" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <title>用户注册页面</title>
  <link rel="stylesheet" th:href = "@{/js/layui/css/layui.css}">
</head>
<body>
<!-- 你的HTML代码 -->
<form class="layui-form" method="POST" th:action="@{register}">

	<fieldset class="layui-elem-field layui-field-title" style="margin-top: 50px;">
          <legend>注册</legend>
    </fieldset>
	
    <div class="layui-form-item">
      <label class="layui-form-label"></label>
      <div class="layui-input-block">
        <input type="text" name="username" lay-verify="required" placeholder="请输入账号" autocomplete="off" class="layui-input">
      </div>
    </div>
    <div class="layui-form-item">
      <label class="layui-form-label"></label>
      <div class="layui-input-inline">
        <input type="password" name="password" lay-verify="required" placeholder="请输入密码" autocomplete="off" class="layui-input">
      </div>
      <div class="layui-form-mid layui-word-aux"></div>
    </div>
    
    <div class="layui-form-item">
      <label class="layui-form-label"></label>
      <div class="layui-input-inline">
        <input type="password" name="password1" lay-verify="required" placeholder="请再次输入密码" autocomplete="off" class="layui-input">
      </div>
      <div class="layui-form-mid layui-word-aux"></div>
    </div>
     <div class="layui-form-item">
    	<label class="layui-form-label">您的身份</label>
    	<div class="layui-input-block">
      		<input type="radio" name="name" value="ROLE_ADMIN" title="管理员">
      		<input type="radio" name="name" value="ROLE_USER" title="用户" checked>
    	</div>
  	</div>
    
      <div class="layui-form-item">
        <div class="layui-input-block">
          <button class="layui-btn" lay-submit lay-filter="formDemo">立即提交</button>
          <button type="reset" class="layui-btn layui-btn-primary">重置</button>
          <a href="login.html"><button class="layui-btn">返回</button></a>
        </div>
      </div>
      
</form> 

<script th:src="@{/js/layui/layui.js}"></script>    
<script>
layui.use(['form'], function(){
	  form = layui.form;
	});
</script>   
</body>
</html>

9.3主页index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" 
	  xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="renderer" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>首页</title>
  <script th:src="@{/js/layui/layui.js}"></script>
  <link rel="stylesheet" th:href = "@{/js/layui/css/layui.css}"> 
</head>
<body class="layui-layout-body">

<div class="layui-layout layui-layout-admin ">
<div class="layui-header">
    <div class="layui-logo">员工信息管理</div>
    <!-- 头部区域(可配合layui已有的水平导航) -->
    <ul class="layui-nav layui-layout-right">
      <li class="layui-nav-item">
        <a href="javascript:;">
          <span sec:authentication="name"></span>
        </a>
      </li>
      <li class="layui-nav-item"><a href="logout">退出</a></li>
    </ul>
  </div>
  
	<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
      		<ul class="layui-nav layui-nav-tree"  lay-filter="test">
      				<li class="layui-nav-item"><a href="index.html">首页</a></li>
      				<li class="layui-nav-item">
      					<a href="javascript:;">通知公告</a>
						<dl class="layui-nav-child">
      						<dd><a href="announcement.html">全部公告</a></dd>
      						<dd><a href="myAnnouncement.html">我发布的</a></dd>
   					 	</dl>
   					 </li>
				<div sec:authorize="hasRole('ADMIN')">
  					<li class="layui-nav-item"><a href="UserSalary.html">员工信息表</a></li>
  				</div>
  				<div sec:authorize="hasRole('ADMIN')">
  					<li class="layui-nav-item"><a href="userInfo.html">用户信息表</a></li>
  				</div>
      		</ul>
</div>
</div>
    
    <div class="layui-body">
    
    <fieldset class="layui-elem-field layui-field-title" style="margin-top: 50px;">
          <legend>首页</legend>
    </fieldset>
    
    	<div sec:authorize="${isAuthenticated()}">
			欢迎<span sec:authentication="name"></span>
			<br></br>
			您的角色是:<span sec:authentication="principal.authorities"></span>
		</div>
    </div>
</div>
	
</body>

<script>
    //注意:导航 依赖 element 模块,否则无法进行功能性操作
    layui.use('element', function(){
        var element = layui.element;

        //…
    });
</script>
</html>

10.页面展示

注册、登录页面:在这里插入图片描述

在这里插入图片描述
index主页展示界面:

当以管理员身份登录时:
在这里插入图片描述
当以用户身份登录时:
在这里插入图片描述

如有错误请各位指正。
完整代码详见https://github.com/2638565363/springboot.git
本文参考自https://blog.csdn.net/u012373815/article/details/54632176
https://blog.csdn.net/wanderlustLee/article/details/80032177?utm_source=blogxgwz0

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值