尚融宝

MP

  • 创建数据库

    1
    2
    3
    4
    CREATE DATABASE
    IF
    NOT EXISTS `mybatis_plus` CHARACTER
    SET "utf8";
  • 创建表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    create table user
    (
    id bigint(20) not null comment '主键',
    name varchar(30) null default null comment '姓名',
    age int(11) null default null comment '年龄',
    email varchar(50) null default null comment '邮箱',
    primary key (id)
    );

    insert into user(id, name, age, email)
    values (1, 'Jone', 18, 'jone@qq.com'),
    (2, 'Jack', 20, 'jack@qq.com'),
    (3, 'Tom', 28, 'tom@qq.com'),
    (4, 'Sandy', 21, 'sandy@qq.com'),
    (5, 'Billie', 24, 'billie@qq.com');
  • MP 使用步骤

    1. 引入Mybatis-Plus 之后请不要再次引入Mybatis 以及Mybatis-Spring,以避免因版本误差导致的问题

      Mybatis-Plus
    2. 配置数据源

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      spring:
      datasource:
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: root
      url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=UTC

      mybatis-plus:
      configuration:
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

    3. 编写业务逻辑类,自定义mapper 接口继承BaseMapper<T>,继承了BaseMapper 中的增删改查方法

    4. 启动类添加包扫描@MapperScan("com.example.demo.mapper")

    5. 在使用的地方直接注入mapper

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      package com.example.demo;

      @SpringBootTest
      class DemoApplicationTests {
      @Resource
      private UserMapper userMapper;

      @Test
      void contextLoads() {
      List<User> users = userMapper.selectList(null);
      for (User user : users) {
      System.out.println(user);
      }
      }

      }

  • 表问题

    • MP 如何确定是查询那个表的

      是通过继承BaseMapper 时指定的实体类
    • 表名称不一致问题

      通过mybatis-plus 提供的注解进行配置,也可以在配置文件中全局配置
    • 数据库字段映射

      1
      2
      @TableField("user_info")
      private String aa;
    • 主键映射

      1
      @TableId(value = "id", type = IdType.AUTO)
    • 自动填充

      1
      2
      alter table user add column create_time datetime;
      alter table user add column update_time datetime;
      1
      2
      3
      4
      5
      6
      // 1. 添加注解
      @TableField(fill = FieldFill.INSERT)
      private Date createTime;

      @TableField(fill = FieldFill.INSERT_UPDATE)
      private Date updateTime;
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      // 2. 实现对应的接口
      package com.example.demo.autofill;

      import java.util.Date;

      @Component
      public class MyMetaObjectHandler implements MetaObjectHandler {
      @Override
      public void insertFill(MetaObject metaObject) {
      this.setFieldValByName("createTime",new Date(),metaObject);
      this.setFieldValByName("updateTime",new Date(),metaObject);
      }

      @Override
      public void updateFill(MetaObject metaObject) {
      this.setFieldValByName("updateTime",new Date(),metaObject);
      }
      }

      1
      2
      // 3. 测试添加
      userMapper.insert(new User(8l, "auto fill", 8, "autofill@qq.com",null,null));
    • 主键生成策略

      1. none mp 默认的主键生成策略,默认使用的是雪花算法生成递增的数值类型的19 位唯一的标识符
      2. input 表示自己动手输入id
      3. auto 表示使用数据表的自增主键,要求表的列必须是int、bigint、auto increment
      4. assign_id 表示如果用户自己指定了id,就用用户自己指定的id,如果用户没有指定id,就使用默认的雪花算法生成的19 位唯一标识
      5. assign_uuid 表示使用mp 生成的uuid 作为表的主键
  • 乐观锁(使用与配置流程)

    • 主要使=适用场景: 当要更新一条新记录的时候,希望这条记录没有被别人更新,也就是实现线程安全的数据更新

    • 乐观锁实现方式

      • 取出记录时,获取当前的version

      • 更新时,带上这个version

      • 执行更新时L set version = newVersion where version = oldVersion

      • 如果version 不对,就更新失败

      • 数据库中添加version 字段

        1
        2
        -- 1. 添加字段
        alter table user add column `version` int;
        1
        2
        3
        // 2. 标记这个乐观锁的版本号
        @Version
        private Integer version;
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        // 3. 配置(新版本 mp 配置)
        package com.example.demo.config;

        import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
        import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;

        @Configuration
        public class MpConfig {
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
        }

        }


        1
        2
        3
        4
        5
        6
        7
        8
        // 自动填充配置
        @Version
        @TableField(fill = FieldFill.INSERT)
        private Integer version;


        this.setFieldValByName("version",1,metaObject);

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        // 测试 乐观锁

        @Test
        void testOptimisticLocker() {
        // 使用新增数据的 id
        User user = userMapper.selectById(9l);
        // 修改
        user.setName("李四");
        // 不需要手动设置: user.setVersion(user.getVersion()+1) MP => 已实现
        userMapper.updateById(user);
        }
  • 批量查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * 批量查询(1)
    SELECT id,name,age,email,create_time,update_time,version FROM user WHERE id IN ( ? , ? , ? )
    */
    @Test
    void textSelectAsList() {
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
    System.out.println("users = " + users);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * SELECT id,name,age,email,create_time,update_time,version FROM user WHERE name = ? AND age = ?
    */
    @Test
    void textSelectMap() {
    Map<String,Object> map = new HashMap<>();
    map.put("name","李四");
    map.put("age","18");
    List<User> users = userMapper.selectByMap(map);
    System.out.println("users = " + users);
    }
  • 分页查询

    • 配置(这里是拦截器,切勿配置重复MybatisPlusInterceptor,不能创建在两个类中使用此名称)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      package com.example.demo.config;

      import com.baomidou.mybatisplus.annotation.DbType;
      import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
      import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
      import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
      import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;


      /**
      * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)

      官网地址: https://baomidou.com/pages/2976a3/#spring-boot
      */
      @Configuration
      public class MpConfig {
      @Bean
      public MybatisPlusInterceptor mybatisPlusInterceptor() {
      MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
      // 乐观锁配置
      interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
      // 分页插件配置
      interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
      return interceptor;
      }
      @Bean
      public ConfigurationCustomizer configurationCustomizer() {
      return configuration -> configuration.setUseDeprecatedExecutor(false);
      }
      }

    • 使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      @Test
      void textSelectLimit() {
      Page<User> page = new Page<>(1,3);
      Page<User> userPage = userMapper.selectPage(page,null);
      System.out.println("userPage = " + userPage);
      // 返回对象得到分页所有数据
      long pages = userPage.getPages(); // 总页数
      System.out.println("pages = " + pages);

      long current = userPage.getCurrent(); // 当前页
      System.out.println("current = " + current);

      List<User> records = userPage.getRecords(); // 查询数据集合
      for (User record : records) {
      System.out.println("record = " + record);
      }

      long total = userPage.getTotal();// 总记录数
      System.out.println("total = " + total);

      boolean hasNext = userPage.hasNext(); // 下一条
      System.out.println("hasNext = " + hasNext);

      boolean hasPrevious = userPage.hasPrevious(); // 上一条
      System.out.println("hasPrevious = " + hasPrevious);

      }
      实现分页
  • 删除

    • 根据id 删除

      1
      int deleteById = userMapper.deleteById(9l);
    • 批量删除

      1
      2
      3
      4
      5
      6
      7
      8
      /**
      * DELETE FROM user WHERE id IN ( ? , ? , ? )
      */
      @Test
      void testDeleteById() {
      int deleteAsList = userMapper.deleteBatchIds(Arrays.asList(7, 8, 9));
      System.out.println("deleteAsList = " + deleteAsList);
      }
    • 逻辑删除

      1
      2
      -- 1. 添加标识字段
      alter table user add column is_delete int default 0 comment '1: 已经被删除,0: 未被删除';
      1
      2
      3
      4
      5
      6
      # 默认配置
      mybatis-plus:
      global-config:
      db-config:
      logic-not-delete-value: 0 # 数据库未删除(0 实现逻辑删除)
      logic-delete-value: 1 # 删除后的数据标志
      1
      2
      3
      4
      5
      6
      7
      // 2. 实体类
      /**
      * 逻辑删除标识字段
      */
      @TableLogic
      @TableField(fill = FieldFill.INSERT)
      private Integer isDelete;
      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 3. 使用
      /**
      * UPDATE user SET is_delete=1 WHERE id=? AND is_delete=0
      */
      @Test
      void testDeleteById() {
      int deleteById = userMapper.deleteById(5l);
      System.out.println("deleteById = " + deleteById);
      }

数据库设计规范

  1. 库名与应用名称尽量一致

  2. 表名、字段名必须使用小写字母或数字,禁止出现数字开头,

  3. 表名不使用复数名词

  4. 表的命名最好是加上“业务名称_表的作用”。如,edu_teacher

  5. 表必备三字段:id, gmt_create, gmt_modified

    说明:

    其中id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1

    (如果使用分库分表集群部署,则id 类型为verchar,非自增,业务中使用分布式id 生成器)

    gmt_create, gmt_modified 的类型均为 datetime 类型,前者现在时表示主动创建,后者过去分词表示被 动更新。

  6. 单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。 说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。

  7. 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint1 表示是,0 表示否)。

    说明:任何字段如果为非负数,必须是 unsigned

    注意:POJO 类中的任何布尔类型的变量,都不要加is 前缀。数据库表示是与否的值,使用 tinyint 类型,坚持is_xxx 的 命名方式是为了明确其取值含义与取值范围。

    正例:表达逻辑删除的字段名 is_deleted1 表示删除,0 表示未删除。

  8. 小数类型为 decimal,禁止使用 float double。 说明:float double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不 正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。

  9. 如果存储的字符串长度几乎相等,使用char 定长字符串类型。

  10. varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索 引效率。

  11. 唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。

  12. 不得使用外键与级联,一切外键概念必须在应用层解决。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。

代码生成器

  • 添加代码生成器依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.3.1</version>
    </dependency>

    <dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.0</version>
    </dependency>
  • 修改代码中路径、数据库、包和表,复制到test 目录下

  • MP 代码生成器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    package core;

    import com.baomidou.mybatisplus.annotation.DbType;
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.generator.AutoGenerator;
    import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
    import com.baomidou.mybatisplus.generator.config.GlobalConfig;
    import com.baomidou.mybatisplus.generator.config.PackageConfig;
    import com.baomidou.mybatisplus.generator.config.StrategyConfig;
    import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
    import org.junit.jupiter.api.Test;

    public class CodeGenerator {
    @Test
    public void genCode() {
    // 1、创建代码生成器
    AutoGenerator mpg = new AutoGenerator();
    // 2、全局配置
    GlobalConfig gc = new GlobalConfig();
    String projectPath = System.getProperty("user.dir");
    gc.setOutputDir("E:\\code\\srb\\service-core" + "/src/main/java");
    gc.setAuthor("coder-itl");
    gc.setOpen(false);
    // 生成后是否打开资源管理器
    gc.setServiceName("%sService");
    // 去掉Service接口的首字母I
    gc.setIdType(IdType.AUTO);
    // 主键策略
    gc.setSwagger2(true);
    // 开启Swagger2模式
    mpg.setGlobalConfig(gc);
    // 3、数据源配置
    DataSourceConfig dsc = new DataSourceConfig();
    dsc.setUrl("jdbc:mysql://localhost:3306/srb_core?serverTimezone=GMT%2B8&characterEncoding=utf-8");
    dsc.setDriverName("com.mysql.cj.jdbc.Driver");
    dsc.setUsername("root");
    dsc.setPassword("root");
    dsc.setDbType(DbType.MYSQL);
    mpg.setDataSource(dsc);
    // 4、包配置
    PackageConfig pc = new PackageConfig();
    pc.setParent("com.coderitl.srb.core");
    pc.setEntity("pojo.entity");
    // 此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
    mpg.setPackageInfo(pc);
    // 5、策略配置
    StrategyConfig strategy = new StrategyConfig();
    strategy.setNaming(NamingStrategy.underline_to_camel);
    // 数据库表映射到实体的命名策略
    strategy.setColumnNaming(NamingStrategy.underline_to_camel);
    // 数据库表字段映射到实体的命名策略
    strategy.setEntityLombokModel(true);
    // lombok
    strategy.setLogicDeleteFieldName("is_deleted");
    //逻辑删除字段名
    strategy.setEntityBooleanColumnRemoveIsPrefix(true);
    //去掉布尔值的is_前缀(确保tinyint(1))
    strategy.setRestControllerStyle(true);
    //restful api风格控制器
    mpg.setStrategy(strategy);
    // 6、执行
    mpg.execute();
    }
    }

在线工具

阿里云短信业务的开通

  • 前提

    不支持个人用户申请未上线业务,若产品未上线建议使用API发送测试功能 升级企业账号

  • 流程分析

    发送短信流程分析
  • 搜索短信服务

    短信服务
  • 个人用户使用测试

    测试
  • 属性获取工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    #阿里云短信
    aliyun:
    sms:
    region-id: cn-langzhou
    key-id: LTAI5t7Ltpt38cxPFQLfHgg92
    key-secret: oowxyilBE0KUeTaBiouLvz10xfzEj12
    template-code: SMS_1549509092
    sign-name: 阿里云短信测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    package com.coderitl.srb.sms.utils;


    /**
    * 读取配置文件属性
    */
    @Data
    @Component
    @ConfigurationProperties(prefix = "aliyun.sms")
    public class SmsProperties implements InitializingBean {
    private String regionId;
    private String keyId;
    private String keySecret;
    private String templateCode;
    private String signName;


    public static String REGION_Id;
    public static String KEY_ID;
    public static String KEY_SECRET;
    public static String TEMPLATE_CODE;
    public static String SIGN_NAME;

    /**
    * 当私有成员被赋值后,此方法自动被调用,从而初始化常量
    */
    @Override
    public void afterPropertiesSet() throws Exception {
    REGION_Id = regionId;
    KEY_ID = keyId;
    KEY_SECRET = keySecret;
    TEMPLATE_CODE = templateCode;
    SIGN_NAME = signName;
    }
    }

  • YML 自动感知配置

    • 添加依赖

      1
      2
      3
      4
      5
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
      </dependency>
    • 出现如下内容,进行项目重新构建

      ④(出现感知)
    • Test 包引起的错误(结果都是正常输出)

      import org.junit.jupiter.api.Test; import org.junit.Test;
    • SDK

      发送请求
    • 创建SmsService

      1
      2
      3
      4
      5
      6
      package com.coderitl.srb.sms.service;

      public interface SmsService {

      void send(String mobile, String templateCode, Map<String,Object> param);
      }
      • 创建实现SmsServiceImpl

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        package com.coderitl.srb.sms.service.impl;

        @Service
        @Slf4j
        public class SmsServiceImpl implements SmsService {

        @Override
        public void send(String mobile, String templateCode, Map<String,Object> param) {

        //创建远程连接客户端对象
        DefaultProfile profile = DefaultProfile.getProfile(
        SmsProperties.REGION_Id,
        SmsProperties.KEY_ID,
        SmsProperties.KEY_SECRET);
        IAcsClient client = new DefaultAcsClient(profile);

        //创建远程连接的请求参数
        CommonRequest request = new CommonRequest();
        request.setSysMethod(MethodType.POST);
        request.setSysDomain("dysmsapi.aliyuncs.com");
        request.setSysVersion("2017-05-25");
        request.setSysAction("SendSms");
        request.putQueryParameter("RegionId", SmsProperties.REGION_Id);
        request.putQueryParameter("PhoneNumbers", mobile);
        request.putQueryParameter("SignName", SmsProperties.SIGN_NAME);
        request.putQueryParameter("TemplateCode", templateCode);

        Gson gson = new Gson();
        String json = gson.toJson(param);
        request.putQueryParameter("TemplateParam", json);

        try {
        //使用客户端对象携带请求对象发送请求并得到响应结果
        CommonResponse response = client.getCommonResponse(request);
        boolean success = response.getHttpResponse().isSuccess();
        //ALIYUN_RESPONSE_FAIL(-501, "阿里云响应失败"),
        Assert.isTrue(success, ResponseEnum.ALIYUN_RESPONSE_FAIL);

        String data = response.getData();
        HashMap<String, String> resultMap = gson.fromJson(data, HashMap.class);
        String code = resultMap.get("Code");
        String message = resultMap.get("Message");
        log.info("阿里云短信发送响应结果:");
        log.info("code:" + code);
        log.info("message:" + message);

        //ALIYUN_SMS_LIMIT_CONTROL_ERROR(-502, "短信发送过于频繁"),//业务限流
        Assert.notEquals("isv.BUSINESS_LIMIT_CONTROL", code, ResponseEnum.ALIYUN_SMS_LIMIT_CONTROL_ERROR);
        //ALIYUN_SMS_ERROR(-503, "短信发送失败"),//其他失败
        Assert.equals("OK", code, ResponseEnum.ALIYUN_SMS_ERROR);

        } catch (ServerException e) {
        log.error("阿里云短信发送SDK调用失败:");
        log.error("ErrorCode=" + e.getErrCode());
        log.error("ErrorMessage=" + e.getErrMsg());
        throw new BusinessException(ResponseEnum.ALIYUN_SMS_ERROR , e);
        } catch (ClientException e) {
        log.error("阿里云短信发送SDK调用失败:");
        log.error("ErrorCode=" + e.getErrCode());
        log.error("ErrorMessage=" + e.getErrMsg());
        throw new BusinessException(ResponseEnum.ALIYUN_SMS_ERROR , e);
        }
        }
        }
      • 生成验证码工具类

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        package com.coderitl.common.utils;

        import java.text.DecimalFormat;
        import java.util.ArrayList;
        import java.util.HashMap;
        import java.util.List;
        import java.util.Random;

        /**
        * 生成四位和六位的随机数字
        */
        public class RandomUtils {

        private static final Random random = new Random();

        private static final DecimalFormat fourdf = new DecimalFormat("0000");

        private static final DecimalFormat sixdf = new DecimalFormat("000000");

        public static String getFourBitRandom() {
        return fourdf.format(random.nextInt(10000));
        }

        public static String getSixBitRandom() {
        return sixdf.format(random.nextInt(1000000));
        }

        /**
        * 给定数组,抽取n个数据
        * @param list
        * @param n
        * @return
        */
        public static ArrayList getRandom(List list, int n) {

        Random random = new Random();

        HashMap<Object, Object> hashMap = new HashMap<Object, Object>();

        // 生成随机数字并存入HashMap
        for (int i = 0; i < list.size(); i++) {

        int number = random.nextInt(100) + 1;

        hashMap.put(number, i);
        }

        // 从HashMap导入数组
        Object[] robjs = hashMap.values().toArray();

        ArrayList r = new ArrayList();

        // 遍历数组并打印数据
        for (int i = 0; i < n; i++) {
        r.add(list.get((int) robjs[i]));
        System.out.print(list.get((int) robjs[i]) + "\t");
        }
        System.out.print("\n");
        return r;
        }
        }

      • 手机号验证正则

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        package com.coderitl.common.utils;

        import java.util.regex.Matcher;
        import java.util.regex.Pattern;

        /**
        * 使用正则表达式进行表单验证
        */
        public class RegexValidateUtils {

        static boolean flag = false;
        static String regex = "";

        public static boolean check(String str, String regex) {
        try {
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(str);
        flag = matcher.matches();
        } catch (Exception e) {
        flag = false;
        }
        return flag;
        }

        /**
        * 验证邮箱
        *
        * @param email
        * @return
        */
        public static boolean checkEmail(String email) {
        String regex = "^\\w+[-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$ ";
        return check(email, regex);
        }

        /**
        * 验证手机号码
        *
        * 移动号码段:139、138、137、136、135、134、150、151、152、157、158、159、182、183、187、188、147
        * 联通号码段:130、131、132、136、185、186、145
        * 电信号码段:133、153、180、189
        *
        * @param cellphone
        * @return
        */
        public static boolean checkCellphone(String cellphone) {
        // String regex = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\\d{8}$"; https://www.jianshu.com/p/1e8eab706a63
        String regex = "^1(3\\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$";
        return check(cellphone, regex);
        }

        /**
        * 验证固话号码
        *
        * @param telephone
        * @return
        */
        public static boolean checkTelephone(String telephone) {
        String regex = "^(0\\d{2}-\\d{8}(-\\d{1,4})?)|(0\\d{3}-\\d{7,8}(-\\d{1,4})?)$";
        return check(telephone, regex);
        }

        /**
        * 验证传真号码
        *
        * @param fax
        * @return
        */
        public static boolean checkFax(String fax) {
        String regex = "^(0\\d{2}-\\d{8}(-\\d{1,4})?)|(0\\d{3}-\\d{7,8}(-\\d{1,4})?)$";
        return check(fax, regex);
        }

        /**
        * 验证QQ号码
        *
        * @param QQ
        * @return
        */
        public static boolean checkQQ(String QQ) {
        String regex = "^[1-9][0-9]{4,} $";
        return check(QQ, regex);
        }
        }

    • 控制器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      package com.coderitl.srb.sms.controller.api;


      import com.coderitl.common.exception.Assert;
      import com.coderitl.common.result.ResponseEnum;
      import com.coderitl.common.result.Result;
      import com.coderitl.common.utils.RandomUtils;
      import com.coderitl.common.utils.RegexValidateUtils;
      import com.coderitl.srb.sms.service.SmsService;
      import com.coderitl.srb.sms.utils.SmsProperties;
      import io.swagger.annotations.Api;
      import io.swagger.annotations.ApiParam;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.data.redis.core.RedisTemplate;
      import org.springframework.web.bind.annotation.*;

      import javax.annotation.Resource;
      import java.util.HashMap;
      import java.util.concurrent.ExecutionException;
      import java.util.concurrent.TimeUnit;

      @Slf4j
      @CrossOrigin //跨域
      @Api(tags = "短信管理")
      @RestController
      @RequestMapping("/api/sms")
      public class ApiSmsController {
      @Resource
      private SmsService smsService;
      @Resource
      private RedisTemplate redisTemplate;

      @GetMapping("/send/{mobile}")
      public Result send(
      @ApiParam(value = "手机号", required = true)
      @PathVariable String mobile
      ) throws ExecutionException, InterruptedException {
      log.info("手机号码: [{}]", mobile);

      // 检验手机号为空
      Assert.notEmpty(mobile, ResponseEnum.MOBILE_NULL_ERROR);
      // 是否是合法的手机号码
      Assert.isTrue(RegexValidateUtils.checkCellphone(mobile), ResponseEnum.MOBILE_ERROR);

      HashMap<String, Object> map = new HashMap<>();
      String code = RandomUtils.getFourBitRandom();
      map.put("code", code);
      smsService.send(mobile, SmsProperties.TEMPLATE_CODE, map);
      // 将验证码存储在 redis 用于校验
      // redisTemplate.opsForValue().set("srb:sms:code:" + mobile, code, 5, TimeUnit.MINUTES);
      return Result.success().message("短信发送成功");
      }
      }

阿里云对象存储服务

  • 搜索oss

    oss
  • 开通

  • 进入管理控制台

    进入管理控制台
  • 创建Bucket

    • 开启公共
  • 文件管理

    • 概述: 获取地域节点

      地域节点
    • 上传后获取文件的url 地址

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41

      /**
      * 文件上传(serviceImpl)
      *
      * @param inputStream
      * @param module
      * @param fileName
      * @return
      */
      @Override
      public String upload(InputStream inputStream, String module, String fileName) {
      // 创建OSSClient实例。
      OSS ossClient = new OSSClientBuilder().build(
      OssProperties.ENDPOINT,
      OssProperties.KEY_ID,
      OssProperties.KEY_SECRET);
      // 判断 oss 实例是否存在:如果不存在则创建,如果存在则获取
      if (!ossClient.doesBucketExist(OssProperties.BUCKET_NAME)) {
      // 创建bucket
      ossClient.createBucket(OssProperties.BUCKET_NAME);
      // 设置oss实例的访问权限:公共读
      ossClient.setBucketAcl(OssProperties.BUCKET_NAME, CannedAccessControlList.PublicRead);
      }

      // 构建日期路径:avatar/2022/10/02/文件名
      String folder = new DateTime().toString("yyyy/MM/dd");

      // 文件名:uuid.扩展名
      fileName = UUID.randomUUID().toString() + fileName.substring(fileName.lastIndexOf("."));
      // 文件根路径
      String key = module + "/" + folder + "/" + fileName;

      // 文件上传至阿里云
      ossClient.putObject(OssProperties.BUCKET_NAME, key, inputStream);

      // 关闭OSSClient。
      ossClient.shutdown();

      // 阿里云文件绝对路径
      return "https://" + OssProperties.BUCKET_NAME + "." + OssProperties.ENDPOINT + "/" + key;
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      @Resource
      private FileService fileService;

      /**
      * 文件上传
      */
      @ApiOperation("文件上传")
      @PostMapping("/upload")
      public Result upload(
      @ApiParam(value = "文件", required = true)
      @RequestParam("file") MultipartFile file,

      @ApiParam(value = "模块", required = true)
      @RequestParam("module") String module) {

      try {
      InputStream inputStream = file.getInputStream();
      String originalFilename = file.getOriginalFilename();
      // 文件上传成功后的 url 地址
      String uploadUrl = fileService.upload(inputStream, module, originalFilename);

      // 返回 Result 对象
      return Result.success().message("文件上传成功").data("url", uploadUrl);
      } catch (IOException e) {
      throw new BusinessException(ResponseEnum.UPLOAD_ERROR, e);
      }
      }
      上传文件后获取URL
    • delete 是一个特殊词,前端端使用时尽可能避免,推荐使用remove

      • 文件的Object Name

        Object Name
      • 删除实现

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        /**
        * 根据路径删除文件(ServiceImpl)
        *
        * @param url
        */
        @Override
        public void removeFile(String url) {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(
        OssProperties.ENDPOINT,
        OssProperties.KEY_ID,
        OssProperties.KEY_SECRET);

        // 文件名(服务器上的文件路径)
        // https://oos-com-coderitl.oss-cn-hangzhou.aliyuncs.com/ 截断部分
        // test/2022/10/03/4ad1fa9d-f0eb-444a-b71d-b18e85e499a4.png 保留部分
        String host = "https://" + OssProperties.BUCKET_NAME + "." + OssProperties.ENDPOINT + "/";
        String objectName = url.substring(host.length());

        // 删除文件
        ossClient.deleteObject(OssProperties.BUCKET_NAME, objectName);

        // 关闭OSSClient。
        ossClient.shutdown();
        }
        1
        2
        3
        4
        5
        6
        7
        8
        @ApiOperation("删除OSS文件")
        @DeleteMapping("/remove")
        public Result remove(
        @ApiParam(value = "要删除的文件路径", required = true)
        @RequestParam("url") String url) {
        fileService.removeFile(url);
        return Result.success().message("删除成功");
        }

使用 alias 命令自定义别名

  • 关机

    1
    alias myShutdown='shutdown -h now'
  • 使用 unalias 命令删除别名

  • 如果想要永久保存定义的alias,可以将其写入到 /etc/profile 或者 ~/.bash_rc 中去,再使其生效source ~/.bash_rc

  • 检查默认情况下shell 脚本中是否开启alias 扩展

    1
    2
    3
    expand_aliases off
    # 开启
    expand_aliases on
  • git 配置

    1
    2
    3
    4
    git config --global alias.co checkout
    git config --global alias.br branch
    git config --global alias.ci commit
    git config --global alias.st status
  • 自定义快捷方式

    1
    2
    3
    4
    5
    alias addAll='git add .'
    alias ..='cd ..'
    alias pullMain='git pull origin main'
    alias pushMain='git push origin main'
    alias toBlub='cd D:\\GithubCode\\Private\\source\\_posts'
  • 永久配置

    1
    2
    3
    cd /etc/profile.d
    # 编辑aliases文件 写入自定义快捷方式内容
    vim aliases.sh

单一服务器模式和单点登录

  • 单点登录

    流程图
    • 一般过程
      1. 用户向服务器发送用户名和密码
      2. 验证服务器后,相关数据(如用户名、用户角色等)将保存在当前会话(session)
      3. 服务器向用户返回session_idsession 信息都会写入到用户的Cookie
      4. 用户后续的请求都将通过Cookie 中取出session_id 传给服务器
      5. 服务器收到session_id 并对比之前保存的数据,确认用户身份
    • 缺点
      • 单点性能压力,无法扩展
      • 分布式架构中,需要session 共享方案,session 共享方案存在性能瓶颈
  • SSO(Single Sign On) 模式:CAS单点登录、OAuth2

    SSO

    SSO: 在多个应用系统中,只需要登陆一次,就可以访问其他相互信任的应用系统

Token 单点登录

  • Token 模式

    Token
  • 优点

    • 无状态:token 是无状态、session 是有状态的
    • 基于标准化: 可以采用标准化的 JSON Web Token(JWT)
  • 缺点

    • 占用带宽
    • 无法在服务器端销毁
  • JWT

    • 作用: JWT 最重要的作用就是对token 信息的防伪作用

    • 原理:

      1. 一个JWT 由三个部分组成: JWT、有效载荷、签名哈希

      2. 最后由这三者组合进行base64 编码得到JWT

        组成
    • 有效载荷

      有效载荷部分,JWT 的主题内容部分,也是一个JSON 对象。包含需要传递的数据。JWT 指定七个默认字段供选择

      1. sub: 主题
      2. iss :JWT 签发者
      3. aud: 接JWT 的一方
      4. iatJWT 的签发时间
      5. expJWT 的过期时间,这个过期时间必须要大于签发时间
      6. nbf:定义在什么时间之前,该JWT 都是不可用的
      7. jti: JWT 的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

      除默认字段外,我们还可以自定义私有字段

  • JWT 的问题和趋势

    1. JWT 默认不加密,但可以加密,生成原始令牌后,可以使用该令牌再次对其进行加密
    2. JWT 未加密是,一般私密数据无法通过JWT 传输
    3. JWT 不仅可以用于认证、还可以用于信息交换。善用JWT 有助于减少服务器请求数据库的次数
    4. JWT 的最大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或者更改令牌的权限。也就是说,一旦JWT 签发,在有效期内将会一直有效
    5. JWT 本身包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。为了减少盗用,JWT 的有效期内不易设置太长。对于某些重要操作,用户在使用时应该每次都进行身份验证
    6. 为了减少盗用和窃取,JWT 不建议使用HTTP 协议来传输代码,而是使用HTTPS 协议进行传输
  • token

    • 添加依赖

      1
      2
      3
      4
      5
      6
      <!-- JWT -->
      <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.7.0</version>
      </dependency>
    • 生成token

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      /**
      * 生成 JWT 测试
      */
      @Test
      public void testCreateToken() {
      String token = Jwts.builder()
      .setHeaderParam("typ", "JWT") //令牌类型
      .setHeaderParam("alg", "HS256") //签名算法

      .setSubject("guli-user") // 令牌主题
      .setIssuer("coder-itl")// 签发者
      .setAudience("coder-itl")// 接收者
      .setIssuedAt(new Date())// 签发时间
      .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)) // 过期时间
      .setNotBefore(new Date(System.currentTimeMillis() + 20 * 1000)) // 20秒后可用
      .setId(UUID.randomUUID().toString())

      .claim("nickname", "coder-itl")
      .claim("avatar", "1.jpg")

      .signWith(SignatureAlgorithm.HS256, tokenSignKey) // 签名哈希
      .compact(); // 转换成字符串

      System.out.println(token);
      }
    • 验证token

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      @Test
      public void testGetUserInfo(){

      String token = "token串";
      Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);

      Claims claims = claimsJws.getBody();

      String subject = claims.getSubject();
      String issuer = claims.getIssuer();
      String audience = claims.getAudience();
      Date issuedAt = claims.getIssuedAt();
      Date expiration = claims.getExpiration();
      Date notBefore = claims.getNotBefore();
      String id = claims.getId();

      System.out.println(subject);
      System.out.println(issuer);
      System.out.println(audience);
      System.out.println(issuedAt);
      System.out.println(expiration);
      System.out.println(notBefore);
      System.out.println(id);;

      String nickname = (String)claims.get("nickname");
      String avatar = (String)claims.get("avatar");

      System.out.println(nickname);
      System.out.println(avatar);
      }

SEO

  • seo: 是网站为了提高自已的网站排名,获得更多的流量,对网站的结构及内容进行调整和优化,以便搜索引擎 (百度,google 等)更好抓取到优质网站的内容。

  • 流程

    搜索引擎工作流程
  • 常见的SEO 方法

    • url 链接的规范化,多用restful 风格的url,多用静态资源url
    • 注意keywords、description、title 的设置;
    • h1-h6、a 标签的使用

服务端渲染与客户端渲染

  • 什么是服务端渲染

    服务端渲染又称 SSR (Server Side Render)是在服务端完成页面的内容渲染,而不是在客户端完成页面内容的渲染

    服务端渲染
    • 服务端渲染特点
      • 在服务端生成html 网页的dom 元素
      • 客户端(浏览器)只负责显示dom 元素内容
  • 客户端渲染

    客户端(浏览器)使用Ajax 向服务端发起http 请求,获取到了想要的数据,开始渲染html 网页,生成的dom 元素,并最终将网页内容展示给用户

    客户端渲染
  • 两种方式的优缺点

    • 客户端渲染
      • 缺点: 不利于网站进行SEO,因为网站大量使用javascript 技术,不利于搜索抓取网页
      • 优点: 客户端负责渲染,用户体验型好,服务端只提供数据不关心用户界面的内容,有利于提高服务端的开发效率
      • 适用场景: 对SEO 没有要求的系统,比如后台管理类的系统,如电商后台管理,用户管理等
    • 服务端渲染
      • 优点:有利于SEO,网站通过href url 将搜索引擎直接引到服务端,服务端提供优质的网页内容给搜索引擎。
      • 缺点:服务端完成一部分客户端的工作,通常完成一个需求需要修改客户端和服务端的代码,开发效率低,不利于系统的稳定性。
      • 适用场景:对SEO 有要求的系统,比如:门户首页、商品详情页面等。

Nuxt.js

  • 是什么: Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可以用来创建服务端渲染 (SSR) 应用,也可充当静态站点引擎生成静态站点应用,具有优雅的代码结构分层和热加载等特性。

  • 官网

    https://zh.nuxtjs.org/

  • 整体流程

    渲染流程
  • Nuxt 安装

    1
    2
    3
    4
    # yarn
    yarn add nuxt
    # npm
    npm install nuxt
  • 项目创建

    1
    2
    3
    4
    5
    # yarn
    yarn create nuxt-app <project-name>

    # npm
    npm init nuxt-app <project-name>
  • 项目运行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # yarn
    cd <project-name>
    yarn dev

    # npm
    cd <project-name>
    npm run dev

    The application is now running on http://localhost:3000 . Well done!

  • 页面

    • 创建pages 目录,之后创建vue 文件
  • 导航

    使用<NuxtLink/> 标签可以在程序中的不同页面之间导航,相当于a 标签的作用,一般情况下我们使用<NuxtLink> 连接程序内部的不同路由地址,使用a 标签连接站外地址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <template>
    <h1>
    Hello Nuxt
    <nuxt-link to="/about">About</nuxt-link>
    {{ message }}
    </h1>
    </template>

    <script>
    export default {
    data() {
    return {
    message: 'test',
    }
    },
    }
    </script>

    <style>
    </style>
  • 自动路由

    vue 项目中我们需要创建页面,并未每一个页面配置路由,而Nuxt 会根据pages 路径中的页面自动生成路由配置

    • 基本路由: /user 指向pages/user.vue 页面

    • lend 指向pages/lend/index.vue 页面

    • 动态路由页面: _id.vue

      动态路由的值 目录结构
    • 嵌套路由

      嵌套路由
  • 默认布局

    如果想要拥有统一的页面风格,例如: 一致的页头和页尾,可以使用Layout,布局文件的默认名字是default.vue,放在layouts 目录中

    注意: 新创建的layout 重启前端服务器后应用

    基础使用
  • 自定义布局

    1
    2
    3
    4
    5
    6
    // 在 layouts 下创建 自定义页面,在需要使用布局的 script 中 配置如下
    <script>
    export default {
    layout: 'my',
    }
    </script>
  • 配置文件

    • Meta Tags and SEO

      我们可以在nuxt.config.js 中配置如下内容,Nuxt 会自动生成网站的<head> 标签,这也是搜索引擎优化的一个必要手段

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      module.exports = {
      head: {
      title: '尚融宝',
      meta: [
      { charset: 'utf-8' },
      // 移动端适配
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
      hid: 'meta-key-words',
      name: 'keywords',
      content:
      'Java,Spring,SpringBoot,SpringMVC,SPringCloud',
      },
      {
      hid: 'description',
      name: 'description',
      content:
      '已经学习完毕Java框架'
      },
      ],
      link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
      },
      }
      自动生成head

      /favicon.ico 放在static 目录下

    • 样式

      1. 创建assets/css/main.css

        1
        2
        3
        body {
        background-color: orange;
        }
      2. 使用: 在nuxt.config.js 中配置样式(和head 同级别)

        1
        2
        3
        css:[
        '~/assets/css/main.css'
        ]
        样式使用
    • 端口号修改(和head 同级别)

      1
      2
      3
      server:{
      port: 3001
      }
  • axios

    • 配置nuxt.config.js

      1
      2
      3
      module.exports = {
      modules:['@nuxtjs/axios'] // 引入 axios 模块
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      <!-- axios 基础使用: 客户端渲染 -->
      <template>
      <div>
      <h1>您的IP地址是: {{ ip }}</h1>
      </div>
      </template>

      <script>
      export default {
      data() {
      return {
      ip: null,
      }
      },
      created() {
      // get(res) | $get(res.data)
      this.$axios.$get('http://icanhazip.com').then((res) => {
      console.log(res)
      this.ip = res
      })
      },
      }
      </script>

    • asyncData

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      /*
      服务端渲染:
      可已将异步调用改为同步调用,可以使用 async 和 await 关键字使远程调用改变为同步,同时让程序更简介
      */
      // {$axios} => es6 的解构 (asyncData: 服务端渲染)
      async asyncData({ $axios }) {
      // 必须是 $xxx
      let response = await $axios.$get('http://icanhazip.com')
      return {
      ip2: response, // 这种写法的问题是: 前面的远程调用是异步的,无法在这里获取到 response
      }
      },
    • axios 全局选项

      1
      2
      3
      4
      // nuxt.config.js
      axios:{
      baseURL: 'http://localhost'
      }
    • 创建拦截器插件plugins/axios.js

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      export default function({ $axios, redirect }) {
      $axios.onRequest((config) => {
      console.log('Making request to ' + config.url)
      })

      $axios.onResponse((response) => {
      console.log('Making resposne')
      return response
      })

      $axios.onError((error) => {
      console.log(error) // for debug
      })
      }
      1
      plugins:['~/plugins/axios']

用户登录模块

  • server-base 引入jwt 依赖

    1
    2
    3
    4
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    </dependency>
    • 工具类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      package com.coderitl.srb.base.utils;

      import com.coderitl.common.exception.BusinessException;
      import com.coderitl.common.result.ResponseEnum;
      import io.jsonwebtoken.*;
      import org.springframework.util.StringUtils;

      import javax.crypto.spec.SecretKeySpec;
      import javax.xml.bind.DatatypeConverter;
      import java.security.Key;
      import java.util.Date;

      public class JwtUtils {

      private static long tokenExpiration = 24 * 60 * 60 * 1000;
      private static String tokenSignKey = "A1t2g3uigu123456";

      private static Key getKeyInstance() {
      SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
      byte[] bytes = DatatypeConverter.parseBase64Binary(tokenSignKey);
      return new SecretKeySpec(bytes, signatureAlgorithm.getJcaName());
      }

      public static String createToken(Long userId, String userName) {
      String token = Jwts.builder()
      .setSubject("SRB-USER")
      .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
      .claim("userId", userId)
      .claim("userName", userName)
      .signWith(SignatureAlgorithm.HS512, getKeyInstance())
      .compressWith(CompressionCodecs.GZIP)
      .compact();
      return token;
      }

      /**
      * 判断 token 是否有效
      *
      * @param token
      * @return
      */
      public static boolean checkToken(String token) {
      if (StringUtils.isEmpty(token)) {
      return false;
      }
      try {
      Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token);
      return true;
      } catch (Exception e) {
      return false;
      }
      }


      public static Long getUserId(String token) {
      Claims claims = getClaims(token);
      Integer userId = (Integer) claims.get("userId");
      return userId.longValue();
      }

      public static String getUserName(String token) {
      Claims claims = getClaims(token);
      return (String) claims.get("userName");
      }

      public static void removeToken(String token) {
      //jwttoken无需删除,客户端扔掉即可。
      }

      /**
      * 校验 token 并返回 Claims
      *
      * @param token
      * @return
      */
      private static Claims getClaims(String token) {
      if (StringUtils.isEmpty(token)) {
      // LOGIN_AUTH_ERROR(-211, "未登录"),
      throw new BusinessException(ResponseEnum.LOGIN_AUTH_ERROR);
      }
      try {
      Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token);
      Claims claims = claimsJws.getBody();
      return claims;
      } catch (Exception e) {
      throw new BusinessException(ResponseEnum.LOGIN_AUTH_ERROR);
      }
      }
      }

    • 创建登录的VO 对象

      封装如下表单信息
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      package com.coderitl.srb.core.pojo.vo;

      @Data
      @ApiModel(description="登录对象")
      public class LoginVO {

      @ApiModelProperty(value = "用户类型")
      private Integer userType;

      @ApiModelProperty(value = "手机号")
      private String mobile;

      @ApiModelProperty(value = "密码")
      private String password;
      }
    • 用户信息对象vo

      登录之后需要展示用户信息
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      package com.coderitl.srb.core.pojo.vo;

      @Data
      @ApiModel(description="用户信息对象")
      public class UserInfoVO {

      @ApiModelProperty(value = "用户姓名")
      private String name;

      @ApiModelProperty(value = "用户昵称")
      private String nickName;

      @ApiModelProperty(value = "头像")
      private String headImg;

      @ApiModelProperty(value = "手机号")
      private String mobile;

      @ApiModelProperty(value = "1:出借人 2:借款人")
      private Integer userType;

      @ApiModelProperty(value = "JWT访问令牌")
      private String token;
      }

GET 请求

  • 错误写法

    get 请求是没有请求体的

Swagger2

  • 切换UI

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!-- swagger 1.9.2 -->
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    </dependency>
    <dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>swagger-bootstrap-ui</artifactId>
    <version>1.9.2</version>
    </dependency>


    <!--swagger ui-->
    <!--
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    </dependency>
    -->
  • 访问地址:xxx/doc.html

  • Token 全局设置

    文档管理
  • 调试token

    调试

前端Cookie

  • 下载

    1
    npm install js-cookie
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    import cookie from 'js-cookie'

    // 存储
    cookie.set('userInfo', response.data.userInfo)

    // 判断 cookie 中是否存有用户信息
    let userInfo = cookie.get('userInfo')

Token校验

  • token 校验

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 校验 token 是否合法(局部)
    this.$axios({
    url: '/api/core/userInfo/checkToken',
    method: 'get',
    // headers: {
    // token: userInfo.token,
    // },
    }).then((response) => {
    console.log('校验成功')
    this.userInfo = userInfo
    })
  • 拦截器处理检验

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    // nuxt 的 axios 拦截器检验
    import { Message } from 'element-ui'
    import cookie from 'js-cookie'
    // 解构 { $axios, redirect }
    export default function({ $axios, redirect }) {
    // 请求拦截器
    $axios.onRequest((config) => {
    let userInfo = cookie.get('userInfo')
    if (userInfo) {
    // debugger
    userInfo = JSON.parse(userInfo)
    config.headers['token'] = userInfo.token
    }
    console.log('Making request to ' + config.url)
    })

    $axios.onRequestError((error) => {
    console.log('onRequestError', error) // for debug
    })
    // 响应拦截器
    $axios.onResponse((response) => {
    console.log('Reciving resposne', response)
    if (response.data.code === 0) {
    return response
    } else if (response.data.code === -211) {
    console.log('用户校验失败')
    // debugger
    cookie.set('userInfo', '')
    window.location.href = '/'
    } else {
    Message({
    message: response.data.message,
    type: 'error',
    duration: 5 * 1000,
    })
    return Promise.reject(response)
    }
    })

    //通信失败
    $axios.onResponseError((error) => {
    console.log('onResponseError', error) // for debug
    })
    }

Query 封装

  • 封装数据源

    手机号、用户类型、用户状态等参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package com.coderitl.srb.core.pojo.query;

    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;

    @Data
    @ApiModel(description = "会员搜索对象")
    public class UserInfoQuery {

    @ApiModelProperty(value = "手机号")
    private String mobile;

    @ApiModelProperty(value = "状态")
    private Integer status;

    @ApiModelProperty(value = "1:出借人 2:借款人")
    private Integer userType;
    }