Springboot

Springboot

SpringBoot所具备的特征有:

​ (1)可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs;

​ (2)内嵌Tomcat或Jetty等Servlet容器;

​ (3)提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置;

​ (4)尽可能自动配置Spring容器;

​ (5)提供准备好的特性,如指标、健康检查和外部化配置;

​ (6)绝对没有代码生成,不需要XML配置。

Springboot–Maven的依赖

 <!-- 版本仲裁中心 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.3.RELEASE</version>
    <relativePath/>
</parent>
    <dependencies>
        <!-- 场景启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
         <!-- 集成swgger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        
        <!-- 集成SpringSecurity-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- thymeleaf整合springsecurity5 -->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
            <version>3.0.4.RELEASE</version>
        </dependency>
        
         <!--  Shiro依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.5.3</version>
        </dependency>
           <!--shiro整合spring的包-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>
        
        <!-- 集成lombok 自动生成getset -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- 集成redis -->
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        
        <!--导入配置文件处理器,配置文件进行绑定就会有提示-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        
       <!-- 集成jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        
        <!-- juint测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        
        <!-- druid数据源 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.18</version>
        </dependency>
        
        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        
        <!-- 集成jquery -->
        <dependency>
            <groupId>org.webjars.bower</groupId>
            <artifactId>jquery</artifactId>
            <version>3.3.0</version>
        </dependency>
        
        <!-- Springboot对thymeleaf的支持  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        
        <!-- 分页助手 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.3</version>
        </dependency>
        
        <!-- 集成fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>
        
        <!--集成 Mybaits -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.2</version>
        </dependency>
        
        <!-- jsr303数据校验 -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        
        <!-- 集成swgger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        
           <!-- 阿里云的短信服务 -->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.5.3</version>
        </dependency>
        
         <!-- Springboot继承jpa -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        
    </dependencies>

Springboot–properties

#  主配置文件
# 设置端口号
server.port=8181
# 设置项目访问路径
# server.servlet.context-path=/crud
# 设置日志输出的级别
logging.level.com.bdqn.augiugn=trace
logging.level.com.bdqn.dao=debug
#
spring.output.ansi.enabled=ALWAYS

# 指定运行配置文件 application-dev.properties
spring.profiles.active=dev

# 不指定路径在当前项目下生成springboot.log日志
# 可以指定完整的路径;
logging.file.name=spring_boot.log
# 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用?spring.log 作为默认文件
#logging.path=/spring/log

#  在控制台输出的日志的格式
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
# 指定文件中日志输出的格式
logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n

##配置数据源
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/smbms?useEncoding=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
#选择druid数据源
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 配置mybaits
#  指定全局配置文件的位置
#mybatis.config-location=classpath:mybatis/mybatis-config.xml
#  所有的mapper映射文件
mybatis.mapper-locations=classpath:mybaits/mapper/*.xml
#  定义所有操作类的别名所在包
mybatis.type-aliases-package=com.bdqn.entity

# thymeleaf前缀
spring.thymeleaf.prefix=classpath:/templates/
# thymeleaf后缀
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
# 开发过程中 建议关闭缓存  false就是关闭,避免改了模板还要重启服务器
spring.thymeleaf.cache=false

#文件上传  限制
spring.http.multipart.maxFileSize=30MB
spring.http.multipart.maxRequestSize=30MB
#不限制大小
#spring.http.multipart.maxFileSize=-1
#spring.http.multipart.maxRequestSize=-1

# 配置redis  
spring.redis.host=192.168.92.130  
spring.redis.port=6379
# --- jpa ---
# 更新或者创建数据表结构
spring.jpa.hibernate.ddl-auto: update
# 控制台显示SQL
spring.jpa.show-sql=true
#国际化
spring.messages.basename=i18n.index

Config配置

dirud监控

package com.bdqn.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DruidConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druid(){
        return  new DruidDataSource();
    }

    //配置Druid的监控
    //1、配置一个管理后台的Servlet
    @Bean
    public ServletRegistrationBean statViewServlet(){
         //参数2  设置druid后台的访问路径
        ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        Map<String,String> initParams = new HashMap<>();

         //设置登录名密码
        initParams.put("loginUsername","admin"); 
        initParams.put("loginPassword","123456");
        initParams.put("allow","");//默认就是允许所有访问
        initParams.put("deny","192.168.15.21");

        bean.setInitParameters(initParams);
        return bean;
    }


    //2、配置一个web监控的filter
    @Bean
    public FilterRegistrationBean webStatFilter(){
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());

        Map<String,String> initParams = new HashMap<>();
        initParams.put("exclusions","*.js,*.css,/druid/*");

        bean.setInitParameters(initParams);

        bean.setUrlPatterns(Arrays.asList("/*"));

        return  bean;
    }

}
加载dirud相关配置
package com.bdqn.util;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

/**
 * Generic DataSource configuration.
 */
//@ConditionalOnMissingBean(DataSource.class)
//@ConditionalOnProperty(name = "spring.datasource.type")
public class Generic {

    @Bean
    public DataSource dataSource(DataSourceProperties properties) {
        //使用DataSourceBuilder创建数据源,利用反射创建响应type的数据源,并且绑定相关属性
        return properties.initializeDataSourceBuilder().build();
    }

}

Springboot自定义异常处理

package com.bdqn.config;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class MyExceptionHandler {

    @ResponseBody
    @ExceptionHandler(Exception.class)
    public Map<String,Object> handleException(Exception e){
        Map<String,Object> map = new HashMap<>();
        map.put("code","user.notexist");
        map.put("message",e.getMessage());
        return map;
    }
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        //传入我们自己的错误状态码  4xx 5xx,否则就不会进入定制错误页面的解析流程
        /**
         * Integer statusCode = (Integer) request
         .getAttribute("javax.servlet.error.status_code");
         */
        request.setAttribute("javax.servlet.error.status_code",500);
        map.put("code","user.notexist");
        map.put("message",e.getMessage());
        //转发到/error  下的4xx或者5xx页面
        return "forward:/error";
    }
}

Swagger

package com.bdqn.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
// 项目目录加swagger-ui.html访问swagger
@Configuration //配置类
@EnableSwagger2// 开启Swagger2的自动配置
public class SwaggerConfig {

    //如果要设置多个分组 则设置多个docket
    @Bean
    public Docket docket1(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("group1");
    }
    @Bean
    public Docket docket2(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("group2");
    }
    @Bean
    public Docket docket3(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("group3");
    }

    @Bean //配置docket以配置Swagger具体参数
    public Docket docket(Environment environment) {

        // 设置要显示swagger的环境  判断当前是否处于该环境
        Profiles of = Profiles.of("dev", "test");
        // 通过 enable() 接收此参数判断是否要显示   如果是dev或者text环境则返回true
        boolean b = environment.acceptsProfiles(of);

        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
                .groupName("阿桐幕") // 配置分组
                .enable(true) // 配置是否开启Swagger
                .select()  //通过select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
                .apis(RequestHandlerSelectors.basePackage("com.bdqn.controller"))
                .build();
    }
    //配置文档信息
    private ApiInfo apiInfo() {
        Contact contact = new Contact("阿桐幕", "https://gitee.com/", "2661184062@QQ.com");
        return new ApiInfo(
                "阿桐幕的Swggger", // 标题
                "讨厌自己明明不甘于平凡,却又不好好努力!", // 描述
                "v1.0", // 版本
                "https://www.cnblogs.com/3306La/articles/13790270.html", // 组织链接
                contact, // 联系人信息
                "Apach 2.0 许可", // 许可
                "https://gitee.com/", // 许可连接
                new ArrayList<>()// 扩展
        );
    }

}

自定义拦截器

package com.bdqn.config;


import com.bdqn.util.LoginHandlerInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

//@Configuration
public class MvcInterceptorConfig extends WebMvcConfigurationSupport{

    @Autowired
    private LoginHandlerInterceptor loginInterceptor;

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {


        // 多个拦截器组成一个拦截器链
        // addPathPatterns 用于添加拦截规则,/**表示拦截所有请求
        // excludePathPatterns 用户排除拦截  不拦截的请求
        registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
                .excludePathPatterns("/login.jsp","/","/user/Login.do");
        super.addInterceptors(registry);
    }
}
拦截处理页
package com.bdqn.util;

import com.bdqn.config.MvcInterceptorConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * 自定义拦截器
 */
@Component
public class LoginHandlerInterceptor  implements HandlerInterceptor {
    private final Logger logger = LoggerFactory.getLogger(MvcInterceptorConfig.class);

    /**
     * controller方法执行前,进行拦截的方法
     * return true放行
     * return false拦截
     * 可以使用转发或者重定向直接跳转到指定的页面。
     * */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //request.getHeader(String) 从请求头中获取数据
        //从请求头中获取用户token(登陆凭证根据业务而定)
        logger.info("request请求地址path[{}] uri[{}]", request.getServletPath(),request.getRequestURI());

        Object user = request.getSession().getAttribute("user");
        System.out.println(121);
        System.out.println(user);
        if (user == null){
            response.sendRedirect("login.jsp");
            return false;
        }
        return  true;
    }

    /**
     * 后处理回调方法,实现处理器(controller)的后处理,但在渲染视图之前
     * 此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    /**
     * 后处理回调方法,实现处理器(controller)的后处理,但在渲染视图之前
     * 此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

分页插件Config

package com.bdqn.config;

import com.github.pagehelper.PageHelper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

//加入此配置类才能使用分页插件
@Configuration
public class MyBatisPageHelperConfigurition {
    @Bean
    public PageHelper pageHelper() {
        System.out.println("MyBatisConfiguration.pageHelper()");
        PageHelper pageHelper = new PageHelper();
        Properties p = new Properties();
        p.setProperty("offsetAsPageNum", "true");
        p.setProperty("rowBoundsWithCount", "true");
        p.setProperty("reasonable", "true");
        pageHelper.setProperties(p);
        return pageHelper;
    }
}
pagehelper分页插件简单使用
@RequestMapping("/getAll")
public String getAll(Model model, Integer pageNum , HttpSession session){
    System.out.println(pageNum);
    Integer pagenum = 1;
    if (pageNum != null){
        pagenum = pageNum;

    }
    //封装pagehelper的分页对象  参数 页面 页大小
    PageHelper.startPage(pagenum,5);
    List<user> userList = users.findAllByempty();
    //将集合存入到pageinfo对象中
    PageInfo<user> pageInfo = new PageInfo<user>(userList);
   // session.setAttribute("pageinfo",pageInfo);
    model.addAttribute("pageinfo",pageInfo);
    return "index";
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-osxf6ajr-1602653907210)(C:\Users\阿桐慕\AppData\Roaming\Typora\typora-user-images\image-20201013093909810.png)]

自定义RedisTemplate

package com.bdqn.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
    // 这是我给大家写好的一个固定模板,大家在企业中,拿去就可以直接使用!
    // 自己定义了一个 RedisTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        // 我们为了自己开发方便,一般直接使用 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<String,Object>();
        template.setConnectionFactory(factory);
        // Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new  StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

权限控制

SpringSecurity
package com.spring_boot_security.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@EnableWebSecurity // 开启WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 定制请求的授权规则
        // 首页所有人可以访问
        http.authorizeRequests().antMatchers("/").permitAll()
                //   antMatchers中的请求  需要hasRole对应的角色权限
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
        // 开启自动配置的登录功能
        // /login 请求来到登录页
        // /login?error 重定向到这里表示登录失败
        http.formLogin();
        //开启自动配置的注销的功能
        // /logout 注销请求
        http.logout();
    }
    //定义认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        //在内存中定义,也可以在jdbc中去拿....
        //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
        //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
        //spring security 官方推荐的是使用bcrypt加密方式。

        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                // 给 角色赋予相应的权限
                .withUser("loser").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
    }

}
shior
shior验证流程
shior验证流程:
   1、先走controller中的登录请求 Subject的login(AuthenticationToken)方法.
   2、再走myrealm类中的doGetAuthenticationInfo()方法的进行登录验证如果登录失败就抛出异常给controller中的登录请求来获取,         然后进行相应的错误处理。如果登录成功(即密码比对成功后)则将当前登录的用户对象存入到Subject对象中后
        再执行AuthorizationInfo()方法的授权管理 即对当前登录的用户(注:用户对象通过Subject来获取)进行权限给予
   3、最后走ShiroConfig配置类中的getshiroFilterFactoryBean()方法进行请求权限控制 === >>>
        如果authc 权限不通过就走setLoginUrl设置的url 即:shiroFactoryBean.setLoginUrl("/toLogin");
        如果perms未授权通过页面就走:shiroFactoryBean.setUnauthorizedUrl("/pected");
自定义realm
package com.bdqn.config;

import com.bdqn.dao.Userdao;
import com.bdqn.pojo.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

<!--
      Subject  用户
      SecurityManager 管理所有用户
      Realm 连接数据库
-->
public class myrealm extends AuthorizingRealm {

    @Autowired
    Userdao userdao;
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了授权---------》》》》》》");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //拿到当前登录的这个对象
        Subject subject = SecurityUtils.getSubject();
        User currentUser = (User)subject.getPrincipal();//拿到下面认证时存入的user对象
        //设置当前用户的权限  即 user:?
        info.addStringPermission("user:"+currentUser.getGender().toString());
        return info;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了认证---------》》》》》》");
        UsernamePasswordToken userToken = (UsernamePasswordToken)token;
        User user = userdao.login(userToken.getUsername());
        if (user == null){
            return null;  //抛出UnknownAccountException异常  用户名不存在
        }
        // 密码认证,shiro做     将user对象存入到Subject对象中
        return new SimpleAuthenticationInfo(user,user.getUserPassword(),"");
    }
}
编写配置ShiroConfig
package com.bdqn.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class shiroConfig {
    //3. ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getshiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager SecurityManager){
        ShiroFilterFactoryBean shiroFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFactoryBean.setSecurityManager(SecurityManager);
        /**
          - anon: 无需认证就可以访问
          - authc: 必须认证了才能访问
          - user: 必须拥有记住我功能才能用
          - perms: 拥有对某个资源的权限才能访问
          - role: 拥有某个角色权限
         */
        Map<String,String> filterMap = new LinkedHashMap<>();
        //对指定的请求进行权限控制
        filterMap.put("/level1/2","perms[user:1]");
        filterMap.put("/level2/2","perms[user:1]");
        filterMap.put("/level3/2","perms[user:2]");

        filterMap.put("/level1/1","anon");
        filterMap.put("/level2/1","authc");
        filterMap.put("/level3/1","authc");
        shiroFactoryBean.setFilterChainDefinitionMap(filterMap);
        //  authc 权限不通过就走  setLoginUrl  设置的url
        shiroFactoryBean.setLoginUrl("/toLogin");
        // perms 未授权 页面就走
        shiroFactoryBean.setUnauthorizedUrl("/pected");
        return shiroFactoryBean;
    }

    //2. DefaultWebSecurityManager
    @Bean(name = "SecurityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("returnMyrealm") myrealm mrealm){
         DefaultWebSecurityManager SecurityManager = new DefaultWebSecurityManager();
         // 关联myrealm
         SecurityManager.setRealm(mrealm);
         return SecurityManager;
     }

    //1. 创建realm对象,需要自定义类
    @Bean
     public myrealm returnMyrealm(){
         return new myrealm();
     }

}
登录验证
@PostMapping("/login")
public String login(String username , String password , Model model){
    //获取一个用户
    Subject subject = SecurityUtils.getSubject();
    // 封装用户的登录数据
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    try {
        subject.login(token);// 执行登录的方法,如果没有异常就说明登录成功成功了
        return "index";
    } catch (UnknownAccountException e) { //用户名不存在
        model.addAttribute("msg","用户名错误");
        return "views/login";
    } catch (IncorrectCredentialsException e) {//密码不存在
        model.addAttribute("msg","密码错误");
        return "views/login";
    }

}

跨域解决CorsConfig

package com.bdqn.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * <p>Title: CorsConfig .java</p>

 * <p>Description:跨域问题解决 </p>

 * @author youthMing

 * @date 2020年5月18日
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                .maxAge(3600);
    }
}

Springboot整合jpa

实体类配置
package com.bdqn.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import javax.persistence.*;
import java.util.Date;

// 用户表
//使用JPA注解配置映射关系
@Entity //告诉JPA这是一个实体类(和数据表映射的类)
@Table(name = "smbms_user") //@Table来指定和哪个数据表对应;如果省略默认表名就是user(类名首字母小写);
@Data  //自动生成getset
public class User {
    @Id //这是一个主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增主键
    private  int id ;   //id
    @Column(name = "username")  //省略的话 默认类名就是属性名
    private  String userName; //用户姓名
    @Column(name = "usercode",length = 50) //这是和数据表对应的一个列
    private  String userCode; //用户编码
    @Column(name = "userpassword")  //省略的话 默认类名就是属性名
    private  String userPassword; //用户密码
    @Column  //省略的话 默认类名就是属性名
    private  int gender; // 性别  (1女  2男)
    @Column  //省略的话 默认类名就是属性名
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;  //出生日期
    @Column  //省略的话 默认类名就是属性名
    private  String phone; //手机号码
    @Column  //省略的话 默认类名就是属性名
    private  String address; //地址
    @Column  //省略的话 默认类名就是属性名
    private  int createdBy; //创建者
    @Column  //省略的话 默认类名就是属性名
    private  Date creationDate; //创建时间
    @Column  //省略的话 默认类名就是属性名
    private  int modifyBy; //更新者
    @Column  //省略的话 默认类名就是属性名
    private  Date modifyDate; //更新时间
}
mapper接口配置
apackage com.bdqn.mapper;

import com.bdqn.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface Usermapper extends JpaRepository<User,Integer> {

    // 自定义sql语句
    @Query(value = "select * from smbms_user where like username=?",nativeQuery = true)
    List<User> queryByNameUser(String name);
}
@GetMapping("/findAll/{pageindex}/{pagesize}")
public Page<User> findAll(@PathVariable(name = "pageindex") Integer pageindex , @PathVariable(name = "pagesize") Integer pagesize){
    //jpa的分页实现
    Pageable pageable = PageRequest.of(pageindex-1,pagesize);
    return  usermapper.findAll(pageable);
}

XML配置

Mybaits的mapper.xml配置
<?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">
<!-- namespace:接口名 -->
<mapper namespace="com.bdqn.dao.smbms_userdao">
 <!--
    id:语句的唯一标示  即dao接口中的方法名
    parameterType:入参的数据类型
    resultType:返回结果的数据类型
    #{}:点位符,相当于jdbc的?,里面写个传入对象的属性名字就好  将参数值都改成‘参数’
    ${}:字符串拼接指令,如果入参为普通数据类型,括号内只能写value  将参数值都改成  参数
  -->
    <select id="getAlluser" resultType="user">
        SELECT u.* , r.`roleName` ,YEAR(NOW())-YEAR(u.`birthday`) AS age FROM smbms_user u
        INNER JOIN smbms_role r ON u.`roleId`= r.`id` ORDER BY u.id DESC
         limit ${currentPageNo},${pagesize}
    </select>
    
     <select id="getbillByparam" resultMap="billList" parameterType="map">
        SELECT b.* ,p.* FROM smbms_bill AS b INNER JOIN smbms_provider AS p ON p.id = b.providerId
         where 1=1
          <!--
            prefix 是给sql语句拼接的前缀,
            suffix 是给sql语句拼接的后缀。
            prefixOverrides 去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定,
            假设该属性指定为"AND",当sql语句的开头为"AND",trim标签将会去除该"AND"
          -->
          <if test="id != null">and p.id = #{id}</if>
          <if test="ductname !=null  and ductname != '' ">AND  b.`productName` LIKE CONCAT('%',#                         {ductname},'%')</if>
          <if test="isPayment !=null ">AND b.`isPayment` = #{isPayment}</if>
           ORDER BY b.creationDate DESC limit #{currentPageNo},#{pageSize}
    </select>
    
       <!-- collection begin  一对多《集合》-->
    <resultMap id="providerListsS" type="provider">
        <!-- property为类中的属性  column为数据库中的字段名   即将column赋值给property -->
        <result property="id" column="id"/>
        <result property="proCode" column="proCode"/>
        <!-- ofType是属性对应的集合当中的类的名称   property表示所属类中的属性名  -->
        <collection property="billList" ofType="bill">
            <result property="id" column="id"/>
            <result property="productName" column="productName"/> 
        </collection>
    </resultMap>
    <!-- collection end  一对多-->
    <!-- association begin 一对一《类》-->
    <resultMap id="billList2" type="bill">
        <!-- property为类中的属性  column为数据库中的字段名   即将column赋值给property -->
        <result property="productName" column="productName"/>
        <result property="billCode" column="billCode"/>
        <result property="totalPrice" column="totalPrice"/>
        <result property="isPayment" column="isPayment"/>
        <result property="creationDate" column="creationDate"/>
        <!-- javaType表示该属性对应的类型名称  resultMap《使用外部resultMap》-->
            <association property="provi" javaType="provider" resultMap="providerList"/>
    </resultMap>
    <resultMap id="providerList" type="provider">
        <result property="proName" column="proName"/>
        <result property="proCode" column="proCode"/>
        <result property="proContact" column="proContact"/>
        <result property="proPhone" column="proPhone"/>
    </resultMap>
    <!-- association end 一对一-->
</mapper>
Spring.xml配置
<!-- applicationContext-mybaits.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">


    <!-- 可引入多个配置文件-->
     <!--<import resource="xx.xml"/>-->
    <!-- 设置spring创建容器时要扫描的包-->
    <context:component-scan base-package="bdqn">
        <!-- 不扫描控制器Controller注解 -->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>
    <!-- 开启aop注解 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!-- 引入properties文件 -->
    <context:property-placeholder location="classpath:database.properties"/>

    <!-- 配置 数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${user}"/>
        <property name="password" value="${pwd}"/>
    </bean>
    <!-- 配置数据源 -->
    <!--<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">-->
        <!--<property name="jndiName" value="java:comp/env/smbms"/>-->
    <!--</bean>-->
    <!-- 配置sqlsessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 引用数据源组件 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 引用mybaits配置文件 -->
        <property name="configLocation" value="classpath:mybaits-cofig.xml"/>
        <!-- 配置mapper映射文件 -->
        <property name="mapperLocations" value="classpath:bdqn/dao/mapper/*.xml">
            <!--<list>-->
                <!--<value>classpath:bdqn/dao/mapper/*.xml</value>-->
            <!--</list>-->
        </property>
        <!-- 取别名 -->
        <property name="typeAliasesPackage" value="classpath:bdqn.entity"/>
    </bean>
    <!-- 配置sqlsession -->
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
    <!-- 配置dao方式1 -->
    <!--<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">-->
        <!-- mapperInterface属性指定映射器 只能是接口类型 -->
        <!--<property name="mapperInterface" value="bdqn.dao.smbms_userdao"/>-->
        <!--<property name="sqlSessionFactory" ref="sqlSessionFactory"/>-->
     <!--</bean>-->

    <!-- 配置dao方式2 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
         <!-- 批量生产映射器  生成的dao层bean id为对应的dao接口名(首字母小写)会自动生成sqlSessionFactory-->
        <!-- 默认生成dao的实现类 -->
        <property name="basePackage" value="bdqn.dao"/>
        <!-- 配置sqlsession -->
        <!--<property name="sqlSessionFactory" ref="sqlSessionFactory"/>-->
     </bean>

    <!-- 配置dao方式3 (有实现类)-->
    <!--<bean id="userMapper" class="bdqn.dao.impl.userimpl">-->
        <!-- 给session赋值 -->
        <!--<property name="sqlSessionFactory" ref="sqlSessionFactory"/>-->
    <!--</bean>-->

    <!-- ================================================================== -->
    <!-- 定义事务管理器 -->
    <bean id = "txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
         <property name="dataSource" ref="dataSource"/>
     </bean>
    <!-- 为指定的事务管理器设置属性 -->
    <tx:advice id = "txAdvice" transaction-manager="txManager">
        <!-- 定义属性并声明事务规则 -->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" />
            <!-- read-only="true"  只读 -->
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <!--  定义切面 -->
    <aop:config>
        <!-- 定义切入点 -->
        <aop:pointcut id="pointcut" expression="execution( * bdqn.service.impl.*.*(..))"/>
        <!-- 将事务增强与切入点结合-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
    </aop:config>

    <tx:annotation-driven transaction-manager="txManager"/>


</beans>
SpringMvc.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation=" http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 开启注解扫描 -->
    <context:component-scan base-package="bdqn.controller"/>
    <!-- 配置视图解析器对象 -->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 文件所在目录 -->
        <property name="prefix" value="/"/>
        <!-- 文件的后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 注册自定义类型转换器 -->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="bdqn.util.StringToDateConverter"/>
            </set>
        </property>
    </bean>

    <!-- 前端控制器 哪些静态资源不拦截 -->
    <!--<mvc:resources mapping="/js/" location="/js/**"/>-->
    <!-- 1. location元素表示webapp目录下的包下的所有文件
         2. mapping元素表示以/static开头的所有请求路径,如/static/a 或者/static/a/b
      -->
    <mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->
    <mvc:resources location="/css/" mapping="/css/**"/>
    <mvc:resources location="/images/" mapping="/images/**"/>
    <mvc:resources location="/statics/" mapping="/statics/**"/>
    <mvc:resources location="/js/" mapping="/smbms/**"/>


    <!-- 配置自定义异常处理器 -->
    <bean id="sysExceptionResolver" class="bdqn.util.SysExceptionResolver"/>
    <!-- 配置全局异常 -->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="java.lang.RuntimeException">error</prop>
            </props>
        </property>
    </bean>


    <!-- 文件上传解析器 -->  <!-- id 的值是固定的-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 设置上传文件的最大尺寸为 5MB -->
        <property name="maxUploadSize" value="5242880"/>
        <!-- 1024*10*5  -->
    </bean>
    <!-- 配置拦截器 -->
    <!--<mvc:interceptors>-->
        <!--<mvc:interceptor>-->
            <!--&lt;!&ndash; 哪些方法进行拦截 &ndash;&gt;-->
            <!--<mvc:mapping path="/**"/>-->
             <!--&lt;!&ndash;哪些方法不进行拦截&ndash;&gt;-->
            <!--<mvc:exclude-mapping path="/Login.do"/>-->
            <!--<mvc:exclude-mapping path="/login.jsp"/>-->

            <!--&lt;!&ndash; 注册拦截器对象 &ndash;&gt;-->
            <!--<bean class="bdqn.util.MyInterceptor"/>-->
        <!--</mvc:interceptor>-->
    <!--</mvc:interceptors>-->

    <!--<bean name="/index.jsp" class="bdqn.controller.Usercontrollerntroller"/>  相当于resultmapper注解-->

    <!-- 开启SpringMvc注解 -->
    <mvc:annotation-driven >
        <!--消息转换器 -->
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <!--加ResponseBody注解 返回string内容运行  -->
                <property name="supportedMediaTypes">
                    <list>
                        <!--<value>application/json;charset=UTF-8</value>-->
                        <value>text/html;charset=utf-8</value>
                    </list>
                </property>
            </bean>
            <!--<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">-->
                <!--<property name="supportedMediaTypes">-->
                    <!--<list>-->
                        <!--<value>application/json;charset=UTF-8</value>-->
                    <!--</list>-->
                <!--</property>-->
            <!--</bean>-->

            <!-- 返回对象内容运行  -->
            <bean class = "com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>application/json;charset=utf-8</value>
                    </list>
                </property>
                <!--<property name="features" value="WriteDateUseDateFormat">-->
                <!--</property>-->
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
</beans>
web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
  <display-name>Archetype Created Web Application</display-name>
    <!--配置Spring的监听器,默认只加载WEB-INF目录下的applicationContext-mybaits.xml配置文件-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!--设置配置文件的路径 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext-mybaits.xml</param-value>
    </context-param>

  <!-- 配置前端控制器 -->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 加载spring的配置文件 -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:SpringMvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <!-- 拦截所有的jsp请求 -->
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <!-- 配置过滤器,解决中文乱码的问题 -->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <!-- 指定字符集 -->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <welcome-file-list>
        <welcome-file>/login.jsp</welcome-file>
    </welcome-file-list>
</web-app>

vue的初始化

vue init webpack myvue  创建一个vue-cil项目 名字为myvue

npm install  初始化项目  需要先进入到myvue项目目录下

npm install vue-router  引入路由

npm install axios  引入axios通信

npm i element-ui -S  引入 element-ui

cnpm install sass-loader node-sass --save-dev  引入sass加载

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值