简历问题分析

简历问题

一、使用MyBatis Plus 自动代码生成器,极大提高了开发效率

1、pom依赖

<dependency>
    <groupId>com.baomidougroupId>
    <artifactId>mybatis-plus-boot-starterartifactId>
dependency>


<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
dependency>


<dependency>
    <groupId>org.apache.velocitygroupId>
    <artifactId>velocity-engine-coreartifactId>
dependency>
1、创建代码生成器:CodeGenerator.java
public class CodeGenerator {
    @Test
    public void run() {
        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir("E:\\IdeaProjects\\guli_parent\\service\\service_edu" + "/src/main/java");

        gc.setAuthor("Tzc");
        gc.setOpen(false); // 生成后是否打开资源管理器
        gc.setFileOverride(false); // 重新生成时文件是否覆盖

        // UserServie
        gc.setServiceName("%sService");	// 去掉Service接口的首字母I

        gc.setIdType(IdType.ID_WORKER_STR); // 主键策略
        gc.setDateType(DateType.ONLY_DATE);// 定义生成的实体类中日期类型
        gc.setSwagger2(true);//开启Swagger2模式

        mpg.setGlobalConfig(gc);

        // 3、数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8");
        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.setModuleName("eduservice"); // 模块名
        // 包  com.atguigu.eduservice
        pc.setParent("com.atguigu");
        // 包  com.atguigu.eduservice.controller
        pc.setController("controller");
        pc.setEntity("entity");
        pc.setService("service");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();

        strategy.setInclude("edu_course","edu_course_description","edu_chapter","edu_video");

        strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
        strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀

        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
        strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

        strategy.setRestControllerStyle(true); //restful api风格控制器
        strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

        mpg.setStrategy(strategy);


        // 6、执行
        mpg.execute();
    }
}

二、使用MetaObjectHandler 对某些字段进行预处理

1、创建自动填充类 MyMetaObjectHandler
/**
 * @author Tzc
 * 对字段生成值(如 gmtCreate创建时间 gmtModified修改时间),实现公共字段自动写入
 */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        // 属性名称,不是字段名称
        this.setFieldValByName("gmtCreate", new Date(), metaObject);
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }
}
2、在实体类添加自动填充注解
// 主键自增策略 19位
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;

// 根据开发规范一个实体类应该包含 创建时间和更新时间
@ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;

@ApiModelProperty(value = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;

三、使用validation 进行请求参数校验,自定义注解校验,校验异常处理

请参考浏览器第三方文档文件夹

四、使用Swagger 生成接口API 文档;

1、pom依赖

<dependency>
    <groupId>io.springfoxgroupId>
    <artifactId>springfox-swagger2artifactId>
    <version>${swagger.version}version>
dependency>

<dependency>
    <groupId>io.springfoxgroupId>
    <artifactId>springfox-swagger-uiartifactId>
    <version>${swagger.version}version>
dependency>
2、配置类
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
 * 访问地址 域名+swagger-ui.html
 * @EnableSwagger2 //swagger注解
 * @Configuration //配置类
 * @author Tzc
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket webApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .paths(Predicates.not(PathSelectors.regex("/admin/.*")))
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();
    }
    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                .title("网站-课程中心API文档")
                .description("本文档描述了课程中心微服务接口定义")
                .version("1.0")
                .contact(new Contact("java", "www.crowd-funding.com", "[email protected]"))
                .build();
    }
}
3、接口测试地址

测试地址:主机域名:8001/swagger-ui.html

4、常用注解

// 用在controller类上,对controller功能进行解释
@Api(description=“课程管理”)

// 用在controller里的方法上,解释方法作用
@ApiOperation(value = “Excel批量导入”)

// 用在方法参数上,解释参数意思
@ApiParam(name = “id”, value = “课程ID”, required = true)

// 用在entity类上,对entity类做注释
@ApiModel(value=“EduChapter对象”, description=“课程”)
// 用在entity类的属性上,对属性做注释
@ApiModelProperty(value = “章节ID”)

五、使用ECharts生成统计图表、使用cron表达式定时任务,把前一天数据进行数据查询添加(细节查看 csdn在线教育业务笔记05- 统计分析模块)

1、安装ECharts js依赖

npm install --save [email protected]

简历问题分析_第1张图片

2、定时任务应用场景

简历问题分析_第2张图片
简历问题分析_第3张图片

/**
 * @author Tzc
 */
@Component
public class ScheduledTask {

    @Autowired
    private StatisticsDailyService staService;

    /**
     * 在每天凌晨1点,把前一天数据进行数据查询添加
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void task2() {
        staService.registerCount(DateUtil.formatDate(DateUtil.addDays(new Date(), -1)));
    }
   /* 
    * 在线生成cron表达式 http://cron.qqe2.com/
    * 0/5 * * * * ?表示每隔5秒执行一次这个方法
    @Scheduled(cron = "0/5 * * * * ?")
    public void task1() {
        System.out.println("**************task1执行了..");
    }
    */
}
3、基于接口的定时任务(从数据库读取cron表达式执行定时任务)
@Configuration
@EnableScheduling  // 2.开启定时任务
public class DynamicScheduleTask implements SchedulingConfigurer {

    @Mapper
    public interface CronMapper {
        @Select("select cron from cron limit 1")
        public String getCron();
    }

    @Autowired      //注入mapper
    CronMapper cronMapper;

    /**
     * 执行定时任务.
     */
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {

        taskRegistrar.addTriggerTask(
                //1.添加任务内容(Runnable)
                () -> System.out.println("执行动态定时任务: " + LocalDateTime.now().toLocalTime()),
                //2.设置执行周期(Trigger)
                triggerContext -> {
                    //2.1 从数据库获取执行周期
                    String cron = cronMapper.getCron();
                    //2.2 合法性校验.
                    if (StringUtils.isEmpty(cron)) {
                        // Omitted Code ..
                    }
                    //2.3 返回执行周期(Date)
                    return new CronTrigger(cron).nextExecutionTime(triggerContext);
                }
        );
    }

}
4、基于注解的多线程定时任务
//@Component注解用于对那些比较中立的类进行注释;
//相对与在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释
@Component
@EnableScheduling   // 1.开启定时任务
@EnableAsync        // 2.开启多线程
public class MultithreadScheduleTask {

        @Async
        @Scheduled(fixedDelay = 1000)  //间隔1秒
        public void first() throws InterruptedException {
            System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
            System.out.println();
            Thread.sleep(1000 * 10);
        }

        @Async
        @Scheduled(fixedDelay = 2000)
        public void second() {
            System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
            System.out.println();
        }
    }

六、使用Mybatis Plus 对查询数据进行分页,并更加灵活调用封装方法与数据库交互

1、分页应用场景(讲师列表分页和条件查询)

简历问题分析_第4张图片
简历问题分析_第5张图片

2、注入MybatisPlus分页插件
/**
  * 分页插件
  */
@Bean
public PaginationInterceptor paginationInterceptor() {
    return new PaginationInterceptor();
}
3、Controller
  /**
     * 分页条件查询
     * @param current 当前页
     * @param limit 每页记录数
     * @param teacherQuery 查询条件 为空时查询所有
     * @return
     */
    @ApiOperation(value = "分页条件查询")
    @PostMapping("pageTeacherCondition/{current}/{limit}")
    public R pageTeacherCondition(@PathVariable long current,@PathVariable long limit,
                                  @RequestBody(required = false)  TeacherQuery teacherQuery) {
        // 创建page对象
        Page<EduTeacher> pageTeacher = new Page<>(current,limit);

        // 构建条件
        QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
        // 多条件组合查询
        // mybatis学过 动态sql
        String name = teacherQuery.getName();
        Integer level = teacherQuery.getLevel();
        String begin = teacherQuery.getBegin();
        String end = teacherQuery.getEnd();
        // 判断条件值是否为空,如果不为空拼接条件
        if(!StringUtils.isEmpty(name)) {
            // 构建条件
            wrapper.like("name",name);
        }
        // 教师级别
        if(!StringUtils.isEmpty(level)) {
            wrapper.eq("level",level);
        }
        // 添加开始时间(范围)
        if(!StringUtils.isEmpty(begin)) {
            wrapper.ge("gmt_create",begin);
        }
        // 添加结束时间(范围)
        if(!StringUtils.isEmpty(end)) {
            wrapper.le("gmt_create",end);
        }

        // 排序 让他加的数据在第一页显示
        wrapper.orderByDesc("gmt_create");

        // 调用方法实现条件查询分页
        // pageTeacher 翻页对象 wrapper 实体对象封装操作类
        // 调用方法时候,底层封装,把分页所有数据封装到pageTeacher对象(page对象)里面
        teacherService.page(pageTeacher,wrapper);

        // 总记录数
        long total = pageTeacher.getTotal();
        // 数据list集合
        List<EduTeacher> records = pageTeacher.getRecords();
        return R.ok().data("total",total).data("rows",records);
    }
4 、接收Controller发送的数据
export default {
  // 1 讲师列表(条件查询分页)
  // current当前页 limit每页记录数 teacherQuery条件对象
  getTeacherListPage(current, limit, teacherQuery) {
    return request({
      url: `/eduservice/teacher/pageTeacherCondition/${current}/${limit}`,
      method: 'post',
      // teacherQuery条件对象,后端使用RequestBody获取数据
      // data表示把对象转换json进行传递到接口里面
      data: teacherQuery
    })
  }
5、el-ui 分页实现(前端)
<!-- 分页 -->
<el-pagination
:current-page="page"
:page-size="limit"
:total="total"
style="padding: 30px 0; text-align: center"
layout="total, prev, pager, next, jumper"
@current-change="getList"
/>

methods: {
 // 创建具体的方法,调用teacher.js定义的方法
 // 讲师列表(查询加分页)
  getList(page = 1) {
    this.page = page;
    teacher
    .getTeacherListPage(this.page, this.limit, this.teacherQuery)
    .then((response) => {
    // 请求成功
    // response接口返回的数据
    // console.log(response)
    this.list = response.data.rows;
    this.total = response.data.total;
    console.log(this.list);
    console.log(this.total);
            });
       }
}
6、Mybatis Plus 的基本使用
6.1、对字段进行预处理
/**
 * @author Tzc
 * 对字段生成值(如 gmtCreate创建时间 gmtModified修改时间),实现公共字段自动写入
 */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(MyMetaObjectHandler.class);
    @Override
    public void insertFill(MetaObject metaObject) {

        // 属性名称,不是字段名称
        this.setFieldValByName("gmtCreate", new Date(), metaObject);
        this.setFieldValByName("gmtModified", new Date(), metaObject);

        // 版本号默认值
        this.setFieldValByName("version", 1, metaObject);

        // 逻辑删除默认值 0代表没逻辑删除 可以显示
        this.setFieldValByName("deleted", 0, metaObject);
    }

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

}
6.2、MybatisPlus组件注入
@EnableTransactionManagement // 开启事务支持
@Configuration
@MapperScan("cn.tt.mapper")
public class MybatisPlusConfig {

    /**
     * 乐观锁插件
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }

    /**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

    /**
     * 逻辑删除 被删除数据的deleted 字段的值必须是 0,才能被选取出来执行逻辑删除的操作
     * @return
     */
    @Bean
    public ISqlInjector sqlInjector() {
        return new LogicSqlInjector();
    }

    /**
     * SQL 执行性能分析插件
     * 开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长
     */
    @Bean
    @Profile({"dev","test"})// 设置 dev test 环境开启
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        performanceInterceptor.setMaxTime(100);//ms,超过此处设置的ms则sql不执行
        performanceInterceptor.setFormat(true);
        return performanceInterceptor;
    }
}
6.3、基本方法
@RunWith(SpringRunner.class)
@SpringBootTest
class MybatisPlusApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    void testSelectList() {
        System.out.println(("----- selectAll method test ------"));
        //UserMapper 中的 selectList() 方法的参数为 MP 内置的条件封装器 Wrapper
        //所以不填写就是无任何条件
        List<User> userList = userMapper.selectList(null);
        userList.forEach(System.out::println);
    }

    @Test
    public void testInsert(){
        User user = new User();
        user.setName("Helen");
        user.setAge(18);
        user.setEmail("[email protected]");

        int result = userMapper.insert(user);
        System.out.println(result); //影响的行数
        System.out.println(user); //id自动回填
    }

    @Test
    public void testUpdateById(){
        User user = new User();
        user.setId(1L);
        user.setAge(28);

        int result = userMapper.updateById(user);
        System.out.println(result);

    }

    /**
     * 乐观锁插件
     */
    @Test
    public void testOptimisticLocker() {

        //查询
        User user = userMapper.selectById(1506203888129167362L);
        //修改数据
        user.setName("Helen Yao");
        user.setEmail("[email protected]");
        //执行更新
        userMapper.updateById(user);
    }

    /**
     * 批量查询
     */
    @Test
    public void testSelectBatchIds(){

        List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
        users.forEach(System.out::println);
    }

    /**
     * 简单分页
     */
    @Test
    public void testSelectPage() {

        Page<User> page = new Page<>(1,5);
        userMapper.selectPage(page, null);

        page.getRecords().forEach(System.out::println);
        System.out.println(page.getCurrent());
        System.out.println(page.getPages());
        System.out.println(page.getSize());
        System.out.println(page.getTotal());
        System.out.println(page.hasNext());
        System.out.println(page.hasPrevious());
    }

    /**
     * 测试 逻辑删除
     */
    @Test
    public void testLogicDelete() {

        int result = userMapper.deleteById(2L);
        System.out.println(result);
    }

    /**
     * 测试 性能分析插件
     */
    @Test
    public void testPerformance() {
        User user = new User();
        user.setName("我是Helen");
        user.setEmail("[email protected]");
        user.setAge(18);
        userMapper.insert(user);
    }

    /**
     * 复杂条件查询
     * ge、gt、le、lt、isNull、isNotNull
     * SQL:UPDATE user SET deleted=1
     * WHERE deleted=0
     * AND name IS NULL
     * AND age >= ?
     * AND email IS NOT NULL
     */
    @Test
    public void testDelete() {

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper
                .isNull("name")
                .ge("age", 12)
                .isNotNull("email");
        int result = userMapper.delete(queryWrapper);
        System.out.println("delete return count = " + result);
    }

    /**
     * eq、ne
     * seletOne返回的是一条实体记录,当出现多条时会报错
     * SELECT id,name,age,email,create_time,update_time,deleted,version
     * FROM user
     * WHERE deleted=0 AND name = ?
     */
    @Test
    public void testSelectOne() {

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        // 对比字符串
        queryWrapper.eq("name", "Tom");

        User user = userMapper.selectOne(queryWrapper);
        System.out.println(user);
    }

    /**
     * between、notBetween
     * 包含大小边界
     * SELECT COUNT(1)
     * FROM user
     * WHERE deleted=0 AND age BETWEEN ? AND ?
     */
    @Test
    public void testSelectCount() {

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 在两数之间
        queryWrapper.between("age", 20, 30);

        Integer count = userMapper.selectCount(queryWrapper);
        System.out.println(count);
    }

    /**
     * allEq
     *SELECT id,name,age,email,create_time,update_time,deleted,version
     * FROM user
     * WHERE deleted=0 AND name = ? AND id = ? AND age = ?
     */
    @Test
    public void testSelectList01() {

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        Map<String, Object> map = new HashMap<>();
        map.put("id", 2);
        map.put("name", "Jack");
        map.put("age", 20);

        queryWrapper.allEq(map);
        List<User> users = userMapper.selectList(queryWrapper);

        users.forEach(System.out::println);
    }

    /**
     * like、notLike、likeLeft、likeRight
     * selectMaps返回Map集合列表
     * SELECT id,name,age,email,create_time,update_time,deleted,version
     * FROM user
     * WHERE deleted=0 AND name NOT LIKE ? AND email LIKE ?
     */
    @Test
    public void testSelectMaps() {

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper
                .notLike("name", "e")
                .likeRight("email", "t");

        List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);//返回值是Map列表
        maps.forEach(System.out::println);
    }

    /**
     * in、notIn、inSql、notinSql、exists、notExists
     * SELECT id,name,age,email,create_time,update_time,deleted,version
     * FROM user
     * WHERE deleted=0 AND id IN (select id from user where id < 3)
     *
     * inSql、notinSql:可以实现子查询
     * 例: inSql("age", "1,2,3,4,5,6")--->age in (1,2,3,4,5,6)
     * 例: inSql("id", "select id from table where id < 3")--->id in (select id from table where id < 3)
     */
    @Test
    public void testSelectObjs() {

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        //queryWrapper.in("id", 1, 2, 3);
        queryWrapper.inSql("id", "select id from user where id < 3");

        List<Object> objects = userMapper.selectObjs(queryWrapper);//返回值是Object列表
        objects.forEach(System.out::println);
    }

    /**
     * or、and
     * 这里使用的是 UpdateWrapper 不调用or则默认为使用 and 连
     *UPDATE user SET name=?, age=?, update_time=?
     * WHERE deleted=0
     * AND name LIKE ? OR age BETWEEN ? AND ?
     */
    @Test
    public void testUpdate1() {

        //修改值
        User user = new User();
        user.setAge(99);
        user.setName("Andy");

        //修改条件
        UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
        userUpdateWrapper
                .like("name", "h")
                .or()
                .between("age", 20, 30);

        int result = userMapper.update(user, userUpdateWrapper);

        System.out.println(result);
    }

    /**
     * 嵌套or、嵌套and
     * 这里使用了lambda表达式,or中的表达式最后翻译成sql时会被加上圆括号
     * UPDATE user SET name=?, age=?, update_time=?
     * WHERE deleted=0
     * AND name LIKE ?
     * OR ( name = ? AND age <> ? )
     */
    @Test
    public void testUpdate2() {
        //修改值
        User user = new User();
        user.setAge(99);
        user.setName("Andy");

        //修改条件
        UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
        userUpdateWrapper
                .like("name", "h")
                .or(i -> i.eq("name", "李白").ne("age", 20));

        int result = userMapper.update(user, userUpdateWrapper);

        System.out.println(result);
    }

    /**
     * orderBy、orderByDesc、orderByAsc
     * SELECT id,name,age,email,create_time,update_time,deleted,version
     * FROM user
     * WHERE deleted=0 ORDER BY id DESC
     */
    @Test
    public void testSelectListOrderBy() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("id");

        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

    /**
     * last 直接拼接到 sql 的最后
     * SELECT id,name,age,email,create_time,update_time,deleted,version
     * FROM user
     * WHERE deleted=0 limit 1
     */
    @Test
    public void testSelectListLast() {

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.last("limit 1");

        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

    /**
     * 指定要查询的列
     * SELECT id,name,age FROM user WHERE deleted=0
     */
    @Test
    public void testSelectListColumn() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "name", "age");

        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

    /**
     * set、setSql
     * 最终的sql会合并 user.setAge(),以及 userUpdateWrapper.set()  和 setSql() 中 的字段
     * UPDATE user SET age=?, update_time=?, name=?, email = '[email protected]'
     * WHERE deleted=0 AND name LIKE ?
     */
    @Test
    public void testUpdateSet() {
        //修改值
        User user = new User();
        user.setAge(99);

        //修改条件
        UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
        userUpdateWrapper
                .like("name", "h")
                .set("name", "老李头")//除了可以查询还可以使用set设置修改的字段
                .setSql(" email = '[email protected]'");//可以有子查询

        int result = userMapper.update(user, userUpdateWrapper);
    }
}

七、使用RedisTemplate 缓存Banner数据、清空指定的缓存、更新缓存数据(细节查看在线教育业务笔记03)

简历问题分析_第6张图片
简历问题分析_第7张图片

1、注解介绍
1、 @Cacheable

根据方法对其返回结果进行缓存(redis),下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。

属性值:
value :缓存名,必填,它指定了你的缓存存放在哪块命名空间
cacheNames: 与 value 差不多,二选一即可
key:可选属性,可以使用 SpEL 标签自定义缓存的key

2、 @CacheEvict

会清空指定的缓存 一般用在更新或者删除方法上

属性值:
value :缓存名,必填,它指定了你的缓存存放在哪块命名空间
cacheNames: 与 value 差不多,二选一即可
key:可选属性,可以使用 SpEL 标签自定义缓存的key
allEntries: 是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存
beforeInvocation : 是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存

3、 @CachePut

使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。

属性值:
value :缓存名,必填,它指定了你的缓存存放在哪块命名空间
cacheNames: 与 value 差不多,二选一即可
key:可选属性,可以使用 SpEL 标签自定义缓存的key

八、使用EasyExcel导入Excel表格内容实现分类添加、使用递归实现课程分类列表以树形显示(细节查看在线教育业务笔记02)

1、EasyExcel应用场景(添加课程分类)
1.1、表结构

简历问题分析_第8张图片

1.2、EasyExcel写操作

简历问题分析_第9张图片

1.3、EasyExcel读操作

简历问题分析_第10张图片

/**
 * 对应上传的Excel表格格式模板 用于数据保存
 * @author Tzc
 */
@Data
public class SubjectData {
    // 一级分类名称 index = 0 代表表格第一列
    @ExcelProperty(index = 0)
    private String oneSubjectName;
    // 二级分类名称 index = 1 代表表格第二列
    @ExcelProperty(index = 1)
    private String twoSubjectName;
}
/**
 * 业务 用于读取excel表格内容 并保存到数据库
 * @author Tzc
 */
public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {

    // 因为SubjectExcelListener不交给spring进行管理,需要自己new,不能注入其他对象
    // 不能实现数据库操作
    public EduSubjectService subjectService;

    public SubjectExcelListener() {}
    
    // 创建有参数构造,传递subjectService用于操作数据库
    public SubjectExcelListener(EduSubjectService subjectService) {
        this.subjectService = subjectService;
    }


    /**
     * 读取excel表格内容,一行一行进行读取 并保存到数据库中
     * @param subjectData
     * @param analysisContext
     */
    @Override
    public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
        if(subjectData == null) {
            throw new GuliException(20001,"文件数据为空");
        }

        // 一行一行读取,每次读取有两个值,第一个值一级分类,第二个值二级分类
        // 判断一级分类是否重复(多个一级目录下有多个二级目录,只显示一个一级目录)
        EduSubject existOneSubject = this.existOneSubject(subjectService, subjectData.getOneSubjectName());
        // 添加一级分类
        if(existOneSubject == null) {
            // 没有相同一级分类,进行添加
            existOneSubject = new EduSubject();
            existOneSubject.setParentId("0");
            // 一级分类名称
            existOneSubject.setTitle(subjectData.getOneSubjectName());
            subjectService.save(existOneSubject);
        }

        // 获取一级分类id值
        String pid = existOneSubject.getId();

        // 判断二级分类是否重复
        EduSubject existTwoSubject = this.existTwoSubject(subjectService, subjectData.getTwoSubjectName(), pid);
        // 添加二级分类
        if(existTwoSubject == null) {
            existTwoSubject = new EduSubject();
            existTwoSubject.setParentId(pid);
            // 二级分类名称
            existTwoSubject.setTitle(subjectData.getTwoSubjectName());
            subjectService.save(existTwoSubject);
        }
    }


    /**
     * 根据parent_id=0 可以判断是一级课程级分类 如果查询结果唯一返回查询的对象 用于于保存
     * @param subjectService
     * @param name 调用者传入的Excel表格一级分类名称
     * @return
     */
    private EduSubject existOneSubject(EduSubjectService subjectService,String name) {
        QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
        wrapper.eq("title",name);
        wrapper.eq("parent_id","0");
        EduSubject oneSubject = subjectService.getOne(wrapper);
        return oneSubject;
    }


    /**
     * 根据二级课程分类的parent_id=一级课程分类的id值 可以判断是二级课程分类 如果查询结果唯一返回查询的对象 用于余保存
     * @param subjectService
     * @param name 调用者传入的Excel表格二级分类名称
     * @param pid 一级分类id值
     * @return
     */
    private EduSubject existTwoSubject(EduSubjectService subjectService, String name, String pid) {

        QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
        wrapper.eq("title", name);
        wrapper.eq("parent_id", pid);
        EduSubject twoSubject = subjectService.getOne(wrapper);
        return twoSubject;
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
}


1.4、实现添加课程分类

简历问题分析_第11张图片
简历问题分析_第12张图片

2、树形显示课程分类流程(课程分类显示)

简历问题分析_第13张图片

九、利用 MD5 对用户密码进行加盐加密;

1、Spring Security加盐加密应用场景(用户注册)
1.1、注入加密类
@Autowired
private BCryptPasswordEncoder passwordEncoder;
1.2、对密码进行加密
public String encode(CharSequence rawPassword) {
   String salt;
   if (strength > 0) {
      if (random != null) {
         salt = BCrypt.gensalt(strength, random);
      }
      else {
         salt = BCrypt.gensalt(strength);
      }
   }
   else {
      salt = BCrypt.gensalt();
   }
   return BCrypt.hashpw(rawPassword.toString(), salt);
}
1.2、加密码与未加密密码进行匹配(hash算法不能解密)
public boolean matches(CharSequence rawPassword, String encodedPassword) {
   if (encodedPassword == null || encodedPassword.length() == 0) {
      logger.warn("Empty encoded password");
      return false;
   }

   if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
      logger.warn("Encoded password does not look like BCrypt");
      return false;
   }

   return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
}

十、使用阿里云短信服务,实现注册验证码

简历问题分析_第14张图片

重要:用户注册流程

简历问题分析_第15张图片

重要:用户注册前端短信倒计时效果

简历问题分析_第16张图片

export default {
    layout: 'sign',
    data() {
      return {
        params: { //封装注册输入数据
          mobile: '',
          code: '',  //验证码
          nickname: '',
          password: ''
        },
        sending: true,      //是否发送验证码
        second: 60,        //倒计时间
        codeTest: '获取验证码'
      }
    },
    methods: {
       timeDown() {
        let result = setInterval(() => {
          // 倒计时每次减一(每执行一次方法)
          --this.second;
          // 倒计时赋值个静态 “获取验证码” 字符串
          this.codeTest = this.second
          if (this.second < 1) {
            clearInterval(result);
            this.sending = true;
            //this.disabled = false;
            this.second = 60;
            this.codeTest = "获取验证码"
          }
        }, 1000);

      },
       //通过输入手机号发送验证码
       getCodeFun() {
         registerApi.sendCode(this.params.mobile)
          .then(response => {
              this.sending = false
              //调用倒计时的方法
              this.timeDown()
          })
       }
    }
1、pom依赖引入
<dependencies>
    <dependency>
       <groupId>com.alibabagroupId>
       <artifactId>fastjsonartifactId>
    dependency>
    <dependency>
       <groupId>com.aliyungroupId>
       <artifactId>aliyun-java-sdk-coreartifactId>
    dependency>
dependencies>
2、controller
@RestController
@RequestMapping("/edumsm/msm")
@CrossOrigin
public class MsmController {

    @Autowired
    private MsmService msmService;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    // 发送短信
    @GetMapping("send/{phone}")
    public R sendMsm(@PathVariable String phone) {
        //1 从redis获取验证码,如果获取到直接返回
        String code = redisTemplate.opsForValue().get(phone);
        if(!StringUtils.isEmpty(code)) {
            return R.ok();
        }
        //2 如果redis获取 不到,进行阿里云发送
        //生成随机值,传递阿里云进行发送
        code = RandomUtil.getFourBitRandom();
        Map<String,Object> param = new HashMap<>();
        param.put("code",code);
        //调用service发送短信的方法
        boolean isSend = msmService.send(param,phone);
        if(isSend) {
            //发送成功,把发送成功验证码放到redis里面
            //设置有效时间
            redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);
            return R.ok();
        } else {
            return R.error().message("短信发送失败");
        }
    }
}
3、创建配置类(设置发送相关信息)
package com.atguigu.crowd.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author Tzc
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
@Component
@ConfigurationProperties(prefix = "short.message")
public class ShortMessageProperties {

   private String host;
   private String path;
   private String method;
   private String appCode;
   private String sign;
   private String skin;

}
4、设置配置类参数
short:
  message:
    app-code: 130790069fcf4dd4a3dc9749779969b7
    host: https://jumsendsms.market.alicloudapi.com
    method: GET
    path: /sms/send-upgrade
    sign: 阿里云短信测试
    skin: SMS_154950909
ribbon:
  ReadTimeout: 10000
  ConnectTimeout: 10000
5、Service注入配置类
@Autowired
private ShortMessageProperties shortMessageProperties;
6、Service
@Service
public class MsmServiceImpl implements MsmService {
    
    @Autowired
    private ShortMessageProperties shortMessageProperties;
/*    public static void main(String[] args_) throws Exception {
        java.util.List args = java.util.Arrays.asList(args_);
        com.aliyun.dysmsapi20170525.Client client = Sample.createClient("accessKeyId", "accessKeySecret");
        SendSmsRequest sendSmsRequest = new SendSmsRequest()
                .setSignName("阿里云短信测试")
                .setTemplateCode("SMS_154950909")
                .setPhoneNumbers("收短信的电话号码")
                .setTemplateParam("{\"code\":\"1234\"}");
        // 复制代码运行请自行打印 API 的返回值
        client.sendSms(sendSmsRequest);
    }*/
    /**
     * 发送短信
     * @param param 
     * @param phone 手机号
     * @return
     */
    @Override
    public boolean send(Map<String, Object> param, String phone) {
        if(StringUtils.isEmpty(phone)) {
            return false;
        }

        DefaultProfile profile = DefaultProfile.getProfile("default", "LTAI5t71z3dRfTPu5gcqomJi", "2jtLZrb6GXcu95oopardJoE4d1fzLb");
        IAcsClient client = new DefaultAcsClient(profile);

        // 设置相关固定的参数
        CommonRequest request = new CommonRequest();
        // request.setProtocol(ProtocolType.HTTPS);
        request.setMethod(MethodType.POST);
        request.setDomain("dysmsapi.aliyuncs.com");
        request.setVersion("2017-05-25");
        request.setAction("SendSms");

        // 设置发送相关的参数
        // 手机号
        request.putQueryParameter("PhoneNumbers",phone);
                        ,
        // 读取配置类参数       
        // 申请阿里云 签名名称
        request.putQueryParameter("SignName",shortMessageProperties.getSign());
        // 申请阿里云 模板code
        request.putQueryParameter("TemplateCode", shortMessageProperties.getSkin());
        // 验证码数据,转换json数据传递
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));

        try {
            // 最终发送
            CommonResponse response = client.getCommonResponse(request);
            boolean success = response.getHttpResponse().isSuccess();
            return success;
        }catch(Exception e) {
            e.printStackTrace();
            return false;
        }

    }
}

十一、使用 sso 单点登录,实现用户在不同微服务间的登录状态保持

简历问题分析_第17张图片
简历问题分析_第18张图片

1、jwt工具类
public class JwtUtils {

    // 常量
    public static final long EXPIRE = 1000 * 60 * 60 * 24; // token过期时间
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; // 秘钥

    // 生成token字符串
    public static String getJwtToken(String id, String nickname){

        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")

                .setSubject("guli-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))

                .claim("id", id)  // 设置token主体部分 ,存储用户信息
                .claim("nickname", nickname) // 用户昵称

                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {

        if (StringUtils.isEmpty(jwtToken)) {
            return false;
        }
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token字符串获取会员id
     * @param request
     * @return
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

简历问题分析_第19张图片

2、用户登录api
2.1、Controller
@ApiOperation(value = "会员登录")
@PostMapping("login")
public R loginUser(@RequestBody UcenterMember member) {

    // member对象封装手机号和密码
    // 调用service方法实现登录
    // 返回token值,使用jwt生成
    String token = memberService.login(member);
    return R.ok().data("token", token);
}
2.2、Service
@Override
public String login(UcenterMember member) {

    // 获取用户输入的登录手机号和密码
    String mobile = member.getMobile();
    String password = member.getPassword();

    // 手机号和密码非空判断
    if (StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
        throw new GuliException(20001, "手机号或密码不能为空,请重新输入!");
    }

    // 判断手机号是否正确
    QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
    wrapper.eq("mobile", mobile);
    UcenterMember mobileMember = baseMapper.selectOne(wrapper);
    if (mobileMember == null) {
        throw new GuliException(20001, "输入的手机号有误,请重新输入!");
    }

    // 判断密码
    // 因为存储到数据库密码肯定加密的
    // 把输入的密码进行加密,再和数据库密码进行比较
    // 加密方式 MD5
    if (!MD5.encrypt(password).equals(mobileMember.getPassword())) {
        throw new GuliException(20001, "输入的密码有误,请重新输入!");
    }

    // 判断用户是否禁用
    if (mobileMember.getIsDeleted()) {
        throw new GuliException(20001, "用户已被禁用!");
    }

    // 登录成功
    // 生成token字符串,使用jwt工具类
    String jwtToken = JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());

    return jwtToken;
}

简历问题分析_第20张图片

3、前端实现(实现登录检查)
3.1、调用后端接口
import request from '@/utils/request'

export default {
  // 登录的方法
  submitLoginUser(userInfo) {
    return request({
      url: `/educenter/member/login`,
      method: 'post',
      data: userInfo
    })
  },
  // 根据token获取用户信息
  getLoginUserInfo() {
    return request({
      url: `/educenter/member/getMemberInfo`,
      method: 'get'
    })
  }
}
3.2、前端实现
import request from '@/utils/request'

export default {
  // 登录的方法
  submitLoginUser(userInfo) {
    return request({
      url: `/educenter/member/login`,
      method: 'post',
      data: userInfo
    })
  },
  // 根据token获取用户信息
  getLoginUserInfo() {
    return request({
      url: `/educenter/member/getMemberInfo`,
      method: 'get'
    })
  }
}
 methods: {
      //登录的方法
      submitLogin() {
        //第一步 调用接口进行登录,返回token字符串
        loginApi.submitLoginUser(this.user) 
           .then(response => {
             // 第二步 获取token字符串放到cookie里面
             // 第一个参数cookie名称,第二个参数值,第三个参数作用范围
             cookie.set('guli_token',response.data.data.token,{domain: 'localhost'})
             
              //第四步 调用接口 根据token获取用户信息,为了首页面显示
              loginApi.getLoginUserInfo()
                .then(response => {
                  this.loginInfo = response.data.data.userInfo
                  //获取返回用户信息,放到cookie里面
                  cookie.set('guli_ucenter',JSON.stringify(this.loginInfo),{domain: 'localhost'})

                  //跳转页面
                  window.location.href = "/";
                })
           })
      }
 }
mport axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import cookie from 'js-cookie'

// 创建axios实例
const service = axios.create({
  baseURL: 'http://localhost:9001', // 网关地址 api的base_url
  timeout: 20000 // 请求超时时间
})

// http request 拦截器
service.interceptors.request.use(
  config => {
  //debugger
  if (cookie.get('guli_token')) {
    config.headers['token'] = cookie.get('guli_token');
  }
    return config
  },
  err => {
  return Promise.reject(err);
})

十二、使用微信第三方 api,实现微信扫码登录

1、请求固定地址生成登录二维码

简历问题分析_第21张图片

    //1 生成微信扫描二维码
    @GetMapping("login")
    public String getWxCode() {
        //固定地址,后面拼接参数
//        String url = "https://open.weixin.qq.com/" +
//                "connect/qrconnect?appid="+ ConstantWxUtils.WX_OPEN_APP_ID+"&response_type=code";

        // 微信开放平台授权baseUrl  %s相当于?代表占位符
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=%s" +
                "#wechat_redirect";

        //对redirect_url进行URLEncoder编码
        String redirectUrl = ConstantWxUtils.WX_OPEN_REDIRECT_URL;
        try {
            redirectUrl = URLEncoder.encode(redirectUrl, "utf-8");
        }catch(Exception e) {
        }

        //设置%s里面值
        String url = String.format(
                    baseUrl,
                    ConstantWxUtils.WX_OPEN_APP_ID,
                    redirectUrl,
                    "atguigu"
                 );

        //重定向到请求微信地址里面
        return "redirect:"+url;
    }
2、获取扫码人信息
2.1、在配置文件中配置了扫码成功后回调的地址(在实际开发中应配置为公司的域名服务器)

在这里插入图片描述

2.2、在回调方法中获取扫码人信息

简历问题分析_第22张图片

@Autowired
private UcenterMemberService memberService;
// 2 获取扫描人信息,添加数据
@GetMapping("callback")
public String callback(String code, String state) {
    try {
        //1 获取code值,临时票据,类似于验证码
        //2 拿着code请求 微信固定的地址,得到两个值 accsess_token 和 openid
        String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                "?appid=%s" +
                "&secret=%s" +
                "&code=%s" +
                "&grant_type=authorization_code";
        // 拼接三个参数 :id  秘钥 和 code值
        String accessTokenUrl = String.format(
                baseAccessTokenUrl,
                ConstantWxUtils.WX_OPEN_APP_ID,
                ConstantWxUtils.WX_OPEN_APP_SECRET,
                code
        );
        //请求这个拼接好的地址,得到返回两个值 accsess_token 和 openid
        //使用httpclient发送请求,得到返回结果
        String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);

        //从accessTokenInfo字符串获取出来两个值 accsess_token 和 openid
        //把accessTokenInfo字符串转换map集合,根据map里面key获取对应值
        //使用json转换工具 Gson
        Gson gson = new Gson();
        HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
        String access_token = (String)mapAccessToken.get("access_token");
        String openid = (String)mapAccessToken.get("openid");

        // 把扫描人信息添加数据库里面
        // 判断数据表里面是否存在相同微信信息,根据openid判断
        UcenterMember member = memberService.getOpenIdMember(openid);
        // memeber是空,表没用户表中没有相同微信数据,进行添加
        if(member == null) {

            // 3 拿着得到accsess_token 和 openid,再去请求微信提供固定的地址,获取到扫描人信息
            //访问微信的资源服务器,获取用户信息
            String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                    "?access_token=%s" +
                    "&openid=%s";
            //拼接两个参数
            String userInfoUrl = String.format(
                    baseUserInfoUrl,
                    access_token,
                    openid
            );
            // 4.使用HttpClient发送请求(因为不同客户端的请求方式不一样)
            String userInfo = HttpClientUtils.get(userInfoUrl);
            // 获取返回userinfo字符串(Json)扫描人信息(使用谷歌的gson转换为map集合)
            HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class);
            String nickname = (String)userInfoMap.get("nickname");// 昵称
            String headimgurl = (String)userInfoMap.get("headimgurl");// 头像

            // 将扫码人数据添加到用户数据库实现注册
            member = new UcenterMember();
            member.setOpenid(openid);
            member.setNickname(nickname);
            member.setAvatar(headimgurl);
            memberService.save(member);
        }

        // 5.使用jwt根据member对象生成token字符串
        String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());
        // 最后:返回首页面,通过路径传递token字符串
        return "redirect:http://localhost:3000?token="+jwtToken;
    }catch(Exception e) {
        throw new GuliException(20001,"登录失败");
    }
}
3、使用jwt生成用户数据在请求地址栏进行传递

简历问题分析_第23张图片
简历问题分析_第24张图片

/**
  * 根据token字符串获取会员id
  * @param request
  * @return
  */
public static String getMemberIdByJwtToken(HttpServletRequest request) {
    // 获取单个请求头name对应的value值
    String jwtToken = request.getHeader("token");
    if(StringUtils.isEmpty(jwtToken)) {return "";}
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
    Claims claims = claimsJws.getBody();
    return (String)claims.get("id");
}
/**
 * 根据token获取用户信息
 * @param request
 * @return
 */
@ApiOperation(value = "根据token获取登录用户信息")
@GetMapping("getMemberInfo")
public R getMemberInfo(HttpServletRequest request) {
    // 调用jwt工具类的方法 根据request对象获取头信息,返回用户id
    String memberId = JwtUtils.getMemberIdByJwtToken(request);
    // 查询数据库根据用户id获取登录用户信息
    UcenterMember member = memberService.getById(memberId);
    return R.ok().data("userInfo", member);
}
import cookie from 'js-cookie'
import loginApi from '@/api/login'

export default {
  data() {
    return {
        token:'',
        loginInfo: {
          id: '',
          age: '',
          avatar: '',
          mobile: '',
          nickname: '',
          sex: ''
        }
    }
  },
  created() {
    //获取路径里面token值
    this.token = this.$route.query.token
    if(this.token) {//判断路径是否有token值
       this.wxLogin()
    }
    this.showInfo()
  },
  methods:{
    //微信登录显示的方法
    wxLogin() {
      //console.log('************'+this.token)
      //把token值放到cookie里面
      cookie.set('guli_token',this.token,{domain: 'localhost'})
      cookie.set('guli_ucenter','',{domain: 'localhost'})
     //console.log('====='+cookie.get('guli_token'))
      //调用接口,根据token值获取用户信息
      loginApi.getLoginUserInfo()
        .then(response => {
          // console.log('################'+response.data.data.userInfo)
           this.loginInfo = response.data.data.userInfo
           cookie.set('guli_ucenter',this.loginInfo,{domain: 'localhost'})
        })
    },
    //创建方法,从cookie获取用户信息
    showInfo() {
      //从cookie获取用户信息
      var userStr = cookie.get('guli_ucenter')
      // 把字符串转换json对象(js对象)
      if(userStr) {
        this.loginInfo = JSON.parse(userStr)
      }
    },

    //退出
    logout() {
      //清空cookie值
      cookie.set('guli_token','',{domain: 'localhost'})
      cookie.set('guli_ucenter','',{domain: 'localhost'})
      //回到首页面
      window.location.href = "/";
    }

  }
};

十三、使用Spring Security定义前台拦截器,对请求进行用户登录、token 校验

简历问题分析_第25张图片
简历问题分析_第26张图片

1、用户输入用户名密码后被拦截器拦截进行用户密码效验
/**
 * 登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验
 */
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;

    // 登录地址拦截
    public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.authenticationManager = authenticationManager;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.setPostOnly(false);
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
    }

    /**
     * 进行用户密码验证
     * @param req
     * @param res
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException {
        try {
            // 得到用户输入的用户名和密码
            User user = new ObjectMapper().readValue(req.getInputStream(), User.class);

            // 会找到UserDetailsService
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
}

2、找到UserDetailsService进行用户权限查询组装
/**
 * @author Tzc
 * 用户关联角色 角色关联权限
 */
@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private AdminService adminService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private AuthService authService;


    /**
     * 根据表单提交的用户名查询admin对象,并添加角色、权限等信息封装到security的User对象中返回
     *
     * @param username
     * @return
     * @throws UsernameNotFoundException
     *     源码鉴赏
     *     public User(String username, String password, boolean enabled,
     *                 boolean accountNonExpired, boolean credentialsNonExpired,
     *                 boolean accountNonLocked, Collection authorities) {
     *
     *         if (((username == null) || "".equals(username)) || (password == null)) {
     *             throw new IllegalArgumentException(
     *                     "Cannot pass null or empty values to constructor");
     *         }
     *
     *         this.username = username;
     *         this.password = password;
     *         this.enabled = enabled;
     *         this.accountNonExpired = accountNonExpired;
     *         this.credentialsNonExpired = credentialsNonExpired;
     *         this.accountNonLocked = accountNonLocked;
     *         this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
     *     }
     *
     */

    /**
     *  // 设置角色前面要加ROLE_ 用于在集合中区分角色和权限
     *  源码鉴赏
     * 	public static List createAuthorityList(String... roles) {
     * 		List authorities = new ArrayList(roles.length);
     *
     * 		for (String role : roles) {
     * 			authorities.add(new SimpleGrantedAuthority(role));
     *                }
     *
     * 		return authorities;
     * 		}
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1.根据账号名称查询Admin对象
        Admin admin = adminService.getAdminByLoginAcct(username);

        // 2.获取adminId
        Integer adminId = admin.getId();

        // 用户关联角色 角色关联权限
        // 3.根据adminId查询登录用户拥有的角色
        List<Role> assignedRoleList = roleService.getAssignedRole(adminId);

        // 4.根据adminId查询登录用户拥有的权限
        List<String> authNameList = authService.getAssignedAuthNameByAdminId(adminId);

        // 5.创建集合对象用来存储GrantedAuthority
        ArrayList<GrantedAuthority> authorities = new ArrayList<>();

        // 6.遍历assignedRoleList存入角色信息
        for (Role role : assignedRoleList) {

            String roleName = "ROLE_" + role.getName();

            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(roleName);

            authorities.add(simpleGrantedAuthority);
        }


        // 7.遍历authNameList存入权限信息
        for (String authName : authNameList) {
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authName);

            authorities.add(simpleGrantedAuthority);
        }


        // 8.封装到SecurityAdmin对象 包含登录用户的原始Admin对象和拥有的角色和权限
        SecurityUser securityUser = new SecurityUser(admin, authorities);

        return SecurityUser;
    }
}
3、用户权限查询组装成功进入下一个过滤器链
    /**
     * 登录成功
     *
     * @param req
     * @param res
     * @param chain
     * @param auth
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {
        // 获取到已授权的用户
        SecurityUser user = (SecurityUser) auth.getPrincipal();
        // 在已授权用户对象中得到未授权的用户(当前登录用户) 生成token字符串
        String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
        // 将用户名(微信openid)作为key 用户拥有的权限作为value存入redis
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());

        ResponseUtil.out(res, R.ok().data("token", token));
    }
4、进行用户认证授权返回认证令牌
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
    // 从request中获取token
    String token = request.getHeader("token");
    if (token != null && !"".equals(token.trim())) {
        String userName = tokenManager.getUserFromToken(token);

        // 根据用户名从redis查询权限
        List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
        // 组装权限列表
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        for(String permissionValue : permissionValueList) {
            if(StringUtils.isEmpty(permissionValue)) {continue;}
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
            authorities.add(authority);
        }

        if (!StringUtils.isEmpty(userName)) {
            return new UsernamePasswordAuthenticationToken(userName, token, authorities);
        }
        return null;
    }
    return null;
}

十四、使用Spring Security定义后台拦截器,对登录用户进行权限校验、设置编码

十五、sessin共享原理

简历问题分析_第27张图片
简历问题分析_第28张图片
自定义springsession配置
简历问题分析_第29张图片

十六、JVM

简历问题分析_第30张图片
简历问题分析_第31张图片
简历问题分析_第32张图片

十七、Java8新特性

简历问题分析_第33张图片

十八、双检锁单例模式

package thread.lock.double_check;
 
/**
 * 双检锁单例模式
 */
public class Singleton {
 
    /**
     *  该类实例, volatile主要防止第29行指令重排序
     */
    private volatile static Singleton instance;
 
    /**
     *  获取实例的方法
     * @return
     */
    public static Singleton getInstance() {
        // 第一把锁, 如果实例为null, 则继续执行
        if (instance == null) {
            // 为该对象加锁
            synchronized (Singleton.class) {
                // 加完锁后, 再次判断实例是否为null, 主要解决第一次
                // 判断完是否为null的之后, 加锁之前是否创建好了对象
                if (instance == null) {
                    // 如果程序能够执行到这里, 按道理来讲, 该不会出现问题了
                    // 但是为了提高性能,编译器和处理器常常会对既定的代码
                    // 执行顺序进行指令重排序
                    // 为 instance 加上 volatile 可以防止指令重排序
                    instance = new Singleton();
                }
            }
        }
        return instance;
 
    }
 
}

十九、其他

简历问题分析_第34张图片
简历问题分析_第35张图片
简历问题分析_第36张图片
简历问题分析_第37张图片

简历问题分析_第38张图片

你可能感兴趣的:(spring,spring,cloud,spring,boot,java,中间件)