骡窝窝项目总结

2020-06-27  本文已影响0人  建国同学

一、骡窝窝项目概要

技术路线

1,数据库:mongodb + elasticsearch;
2,持久化层:mongodb+Redis (缓存);
3,业务层:Springboot;
4,Web:SpringMVC;
5,前端:
管理后台:jQuery+Bootstrap3
前端展示:vue +jquery + css;

项目拆分

拆分便于项目的维护与扩展,更方便于多服务的部署。

构建多模块项目

项目拆解

parent项目如何对依赖做管理:

二、 用户注册

注册分析.png

1、手机号码格式

使用正则表达式验证

2、手机号的唯一验证

查询用户表的手机号字段是否存在数据库中

3、注册参数校验

校验参数是否为空值

4、短信验证码

验证码发送分析.png
  1. 页面倒计时

  2. 使用短信网关api接口(京东万象)


    短信发送分析.png 令牌登录分析.png
  3. 短信验证码存入redis,设置失效时间
    verify_code:13700001111 -- 设置有效性5分钟

三、 redis使用与设计

(一)、简介

key-value型的非关系型数据库,其实是一个缓存,数据可能会丢失
https://blog.csdn.net/aaronthon/article/details/81714528

问题:什么情况用缓存?为什么要用缓存

当某个操作需要频繁操作(读与写)数据库,使用缓存可以减少对数据库操作压力
提升系统性能。

Memcached与Redis有什么区别

https://www.cnblogs.com/middleware/articles/9052394.html

优点:

相比mysql性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
将io的操作变为在内存操作(缓存)
单线程(6版本之后是多线程)

缺点

无事务处理
redis定位是缓存,缓存有可能会丢失
Redis 事务的执行并不是原子性,本质是一个批量执行

实现缓存

(二)、配置

redis.windows.conf配置文件

(三)、redis命令

clear清除屏幕

String 字符串命令

redis基本命令

hash命令

java中使用spring封装的redis对象StringRedisTemplate来操作时,Mp<string, Map<string, ?>> 重复会覆盖,但hashvalue的值会累加


redis操作hash数据

list命令

redis操作list数据

(四)、Redis内存淘汰机制及过期Key处理

Redis内存淘汰机制及过期Key处理

(五)、 java 操作redis

Jedis基本使用

public class JedisTest {

    // 1:创建Jedis连接池
    JedisPool pool = new JedisPool("localhost", 6379);
    // 2:从连接池中获取Jedis对象
    Jedis jedis = pool.getResource();
    /* 设置密码jedis.auth(密码); */
    // jedis将redis中命令封装方法,名字都不改

    public static void close(Jedis jedis, JedisPool pool) {
        // 4:关闭资源
        jedis.close();
        pool.destroy();
    }

    @Test
    public void testJedisPoolString() {
        jedis.set("age", "2");
        jedis.set("sex", "女");
        System.out.println("根据键取出值:" + jedis.get("age"));
        System.out.println("把值递增1:" + jedis.incr("age"));
        System.out.println("把值递减1:" + jedis.decr("age"));
        System.out.println("偏移值+2:" + jedis.incrBy("age", 2));
        System.out.println("设置失效时间:" + jedis.expire("age", 10));
        System.out.println("查询key过期时间:" + jedis.ttl("age"));
        System.out.println("批量查询键值:" + jedis.mget("age","sex"));
        System.out.println("批量查询键值:(新键值长度为)" + jedis.append("sex","性"));
        System.out.println("根据键取出值:" + jedis.get("sex"));
        System.out.println("存入键值对,键存在时不存入:" + jedis.setex("age",10,"键同名不存入"));
        System.out.println("批量查询键值:" + jedis.mget("age","sex"));
        System.out.println("修改键对应的值(长度为):" + jedis.setrange("age",0,"changeValue"));
        System.out.println("根据键取出值:" + jedis.get("age"));
        System.out.println("根据键删除键值对:" + jedis.del("age"));
        System.out.println("根据键删除键值对:" + jedis.del("sex"));
        close(jedis, pool);
    }

    @Test
    public void testJedisPoolHash() {
        jedis.hset("study", "english", "1");
        jedis.hset("study", "math", "2");
        jedis.hset("study", "history", "3");
        System.out.println("根据hash对象键取去值:" + jedis.hget("study", "math"));
        System.out.println("获取该key所有hash对象:" + jedis.hkeys("study"));
        System.out.println("判断hash对象是含有某个键:" + jedis.hexists("study", "math"));
        System.out.println("根据hashkey删除hash对象键值对:" + jedis.hdel("study", "math"));
        System.out.println("修改hash对象值:" + jedis.hset("study", "history", "6"));
        System.out.println("获取hsah对象值:" + jedis.hget("study","history"));
        System.out.println("根据键删除键值对:" + jedis.del("study"));
        close(jedis, pool);
    }

    @Test
    public void testJedisPoolList() {
        jedis.rpush("name", "liChina", "max", "Laura");
        System.out.println("范围显示列表数据,全显示则设置0 -1 :" + jedis.lrange("name", 0, -1));
        System.out.println("弹出列表最左边的数据:" + jedis.lpop("name"));
        System.out.println("往列表左边添加数据:" + jedis.lpush("name", "left"));
        System.out.println("弹出列表最右边的数据:" + jedis.rpop("name"));
        System.out.println("往列表右边添加数据:" + jedis.rpush("name", "right"));
        System.out.println("范围显示列表数据:" + jedis.lrange("name", 0, -1));
        System.out.println("根据索引修改元素值:" + jedis.lset("name", 1,"center"));
        System.out.println("范围显示列表数据:" + jedis.lrange("name", 0, -1));
        System.out.println("获取列表长度:" + jedis.llen("name"));
        System.out.println("根据键删除键值对:" + jedis.del("name"));
        close(jedis, pool);
    }

    @Test
    public void testJedisPoolSet() {
        jedis.sadd("hobby1", "java", "php", "c");
        jedis.sadd("hobby2", "c++", "python", "c");
        System.out.println("列出set集合中的元素:" + jedis.smembers("hobby1"));
        System.out.println("列出set集合中的元素:" + jedis.smembers("hobby2"));
        System.out.println("随机弹出集合中的元素:" + jedis.spop("hobby1"));
        System.out.println("返回hobby1中特有元素(差集):" + jedis.sdiff("hobby1", "hobby2"));
        System.out.println("返回两个set集合的交集:" + jedis.sinter("hobby1", "hobby2"));
        System.out.println("返回两个set集合的并集:" + jedis.sunion("hobby1", "hobby2"));
        System.out.println("随机获取set集合中元素:" + jedis.srandmember("hobby1"));
        System.out.println("删除set集合中的元素:" + jedis.srem("hobby1", "c"));
        System.out.println("列出set集合中的元素:" + jedis.smembers("hobby1"));
        System.out.println("根据键删除键值对:" + jedis.del("hobby1"));
        System.out.println("根据键删除键值对:" + jedis.del("hobby2"));
        close(jedis, pool);
    }

    @Test
    public void testJedisPoolZset() {
        jedis.zadd("myzset", 9, "num1");
        jedis.zadd("myzset", 10, "num2");
        jedis.zadd("myzset", 12, "num3");
        System.out.println("zset按照分数升序:" + jedis.zrange("myzset", 0, -1));
        System.out.println("zset按照分数降序:" + jedis.zrevrange("myzset", 0, -1));
        System.out.println("zset元素个数:" + jedis.zcard("myzset"));
        System.out.println("zset元素偏移num2对应的分数-4:" + jedis.zincrby("myzset", -4, "num2"));
        System.out.println("zset修改元素值:" + jedis.zadd("myzset", 10, "num1"));
        System.out.println("zset返回指定成员的分数值:" + jedis.zscore("myzset", "num1"));
        System.out.println("zset升序返回num3排名:" + jedis.zrank("myzset", "num3"));
        System.out.println("zset降序返回num3排名:" + jedis.zrevrank("myzset", "num3"));
        System.out.println("根据键删除键值对:" + jedis.del("myzset"));
        close(jedis, pool);
    }

    public static void main(String[] args) {
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        ////最大连接数, 默认8个
        config.setMaxTotal(100);
        ////最大空闲连接数, 默认8个
        config.setMaxIdle(20);
        //获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,  默认-1
        config.setMaxWaitMillis(-1);
        //在获取连接的时候检查有效性, 默认false
        config.setTestOnBorrow(true);
        JedisPool pool = new JedisPool(config, "127.0.0.1", 6379, 5000);
        Jedis j = pool.getResource();

        j.set("name", "li");
        String name = j.get("name");
        System.out.println(name);

        j.close();
        pool.close();
        pool.destroy();
    }
}

集成SpringBoot RedisTemplate

@SpringBootTest
public class SpringRedisTest {

    // 约定:所有redis操作, key value 都是字符串
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Test
    public void testRedisTemplateString() {
        System.err.println("========== string命令");
        redisTemplate.opsForValue().set("age","2" );
        System.out.println("根据键取出值:" + redisTemplate.opsForValue().get("age"));
        System.out.println("把值递增1:" + redisTemplate.opsForValue().increment("age"));
        System.out.println("把值递减1:" + redisTemplate.opsForValue().decrement("age"));
        System.out.println("偏移值+2:" + redisTemplate.opsForValue().increment("age",2));
        // 存入键值对,timeout表示失效时间,单位s
        System.out.println("设置失效时间:" + redisTemplate.expire("age", 10, TimeUnit.SECONDS));
        System.out.println("查询key过期时间:" + redisTemplate.opsForValue().getOperations().getExpire("age"));
        // 修改
        redisTemplate.opsForValue().set("age","3",0);
        System.out.println("根据键取出值:" + redisTemplate.opsForValue().get("age"));
        System.out.println("根据键删除键值对:" + redisTemplate.delete("age"));
    }

    @Test
    public void testRedisTemplateList() {
        System.err.println("========== list命令");
        redisTemplate.opsForList().rightPush("mylist", "apple");
        redisTemplate.opsForList().rightPush( "mylist", "banana");
        redisTemplate.opsForList().leftPush("mylist", "pear");
        System.out.println("范围显示列表数据,全显示则设置0 -1 :" + redisTemplate.opsForList().range("mylist",0,-1));
        System.out.println("弹出列表最左边的数据:" + redisTemplate.opsForList().leftPop("mylist"));
        System.out.println("往列表左边添加数据:" + redisTemplate.opsForList().leftPush("mylist","left"));
        System.out.println("弹出列表最右边的数据:" + redisTemplate.opsForList().rightPop("mylist"));
        System.out.println("往列表右边添加数据:" + redisTemplate.opsForList().rightPush("mylist","right"));
        System.out.println("范围显示列表数据:" + redisTemplate.opsForList().range("mylist",0,-1));
        System.out.println("获取列表长度:" + redisTemplate.opsForList().size("mylist"));
        System.out.println("根据键删除键值对:" + redisTemplate.delete("mylist"));
    }

    @Test
    public void testRedisTemplateHash() {
        System.err.println("========== hash命令");
        redisTemplate.opsForHash().increment("study", "english",1);
        redisTemplate.opsForHash().increment("study", "math",2);
        redisTemplate.opsForHash().increment("study", "history",3);
        System.out.println("获取key所有hashkey:" + redisTemplate.opsForHash().keys("study"));
        System.out.println("根据hash对象键取值:" + redisTemplate.opsForHash().get("study", "math"));
        System.out.println("判断hash对象是含有某个键:" + redisTemplate.opsForHash().hasKey("study", "math"));
        System.out.println("根据hashkey删除hash对象键值对:" + redisTemplate.opsForHash().delete("study","math"));
        redisTemplate.opsForHash().increment("study", "english",6);
        System.out.println("根据键删除键值对:" + redisTemplate.delete("study"));
    }

    @Test
    public void testRedisTemplateZset() {
        System.err.println("========== zset命令");
        redisTemplate.opsForZSet().add("myzset", "num1", 9);
        redisTemplate.opsForZSet().add("myzset", "num2", 10);
        redisTemplate.opsForZSet().add("myzset", "num3", 12);
        System.out.println("zset按照分数升序:" + redisTemplate.opsForZSet().range("myzset", 0, -1));
        System.out.println("zset按照分数降序:" + redisTemplate.opsForZSet().reverseRange("myzset", 0, -1));
        System.out.println("zset元素个数:" + redisTemplate.opsForZSet().zCard("myzset"));
        System.out.println("zset元素偏移num2对应的分数-4:" + redisTemplate.opsForZSet().incrementScore("myzset", "num2", -4));
        System.out.println("zset升序返回num3排名:" + redisTemplate.opsForZSet().rank("myzset", "num3"));
        System.out.println("zset降序返回num3排名:" + redisTemplate.opsForZSet().reverseRank("myzset", "num3"));
        // 修改值
        redisTemplate.opsForZSet().add("myzset", "num1", 16);
        System.out.println("zset返回指定成员:" + redisTemplate.opsForZSet().score("myzset", "num1"));
        System.out.println("根据键删除键值对:" + redisTemplate.delete("myzset"));
    }

    @Test
    public void testRedisTemplateSet() {
        System.err.println("========== set命令");
        redisTemplate.opsForSet().add("hobby1", "java", "php", "c");
        redisTemplate.opsForSet().add("hobby2", "c++", "python", "c");
        System.out.println("列出set集合中的元素:" + redisTemplate.opsForSet().members("hobby1"));
        System.out.println("列出set集合中的元素:" + redisTemplate.opsForSet().members("hobby2"));
        System.out.println("随机弹出集合中的元素:" + redisTemplate.opsForSet().pop("hobby1"));
        System.out.println("返回hobby1中特有元素(差集):" + redisTemplate.opsForSet().difference("hobby1","hobby2"));
        System.out.println("返回两个set集合的交集:" + redisTemplate.opsForSet().intersect("hobby1","hobby2"));
        System.out.println("返回两个set集合的并集:" + redisTemplate.opsForSet().union("hobby1","hobby2"));
        System.out.println("随机获取set集合中元素:" + redisTemplate.opsForSet().randomMember ("hobby1"));
        System.out.println("删除set集合中的元素:" + redisTemplate.opsForSet().remove("hobby1","c"));
        System.out.println("列出set集合中的元素:" + redisTemplate.opsForSet().members("hobby1"));
        System.out.println("根据键删除键值对:" + redisTemplate.delete("hobby1"));
        System.out.println("根据键删除键值对:" + redisTemplate.delete("hobby2"));
    }
}

(六)、总结

1: 项目操作涉及到缓存操作, 首选 redis

2: 如果确定使用redis, 此时需要考虑使用哪个数据类型

1>如果要排序选用zset
2>如果数据是多个且允许重复选用list
3>如果数据是多个且不允许重复选用set
4>剩下的使用string

3:怎么设计 key 与 value值

https://blog.csdn.net/ahilll/article/details/84564153

4:redis持久化机制

https://www.cnblogs.com/tdws/p/5754706.html

AOF有序的记录了redis的命令操作。意外情况下数据丢失甚少。他不断地对aof文件添加操作日志记录

四、用户登录

跨域访问

解决跨域访问问题
跨域请求原理:

  1. 浏览器如果发起异步请求,发现请求路径url是跨域请求,浏览器会使用POTIONS方式发起总攻url请求
    ,携带是否许可访问请求数据
  2. 服务器接收这个请求,进行对比根据之前的跨域配置判断是否允许这个ip端口访问呢,如果允许响应允许访问信息,如果不允许,响应会不允许
  3. 浏览器接收这个服务器反馈,根据反馈的结果来决定是否发起真正请求
    如果允许跨域,以真正请求方式发起,如果不允许跨域,返回时不允许跨域的异常
步骤1:implements WebMvcConfigurer

步骤2:
//跨域访问
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            //重写父类提供的跨域请求处理的接口
            public void addCorsMappings(CorsRegistry registry) {
                //添加映射路径
                registry.addMapping("/**")
                        //放行哪些原始域
                        .allowedOrigins("*")
                        //是否发送Cookie信息
                        .allowCredentials(true)
                        //放行哪些原始域(请求方式)
                        .allowedMethods("GET", "POST", "PUT", "DELETE","OPTIONS")
                        //放行哪些原始域(头部信息)
                        .allowedHeaders("*")
                        //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
                        .exposedHeaders("Header1", "Header2");
            }
        };
    }

token令牌方式登录流程

令牌登录分析.png

登录控制

在配置类里配置CheckLoginInterceptor拦截器

定义:CheckLoginInterceptor

配置:CheckLoginInterceptor

在主配置类中implements WebMvcConfigurer
 @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(checkLoginInterceptor())
                .addPathPatterns("/**");
    }

使用自定义注解@RequireLogin的方式区分需要拦截登录的控制器

当前用户注入

  1. 自定义参数解析器
  2. 添加自定义参数解析器
    使用addArgumentResolvers 在启动类里注册该参数解析器

五、 目的地

后端

区域管理

  1. 列表
    PageRequest进行分页操作
@Override
    public Page<Region> query(QueryObject qo) {
        //1: 创建查询条件
        Query query = new Query();
        //2: 每页显示条数集合: list
        //设置页面显示条数, 还有当前页
        PageRequest pageable = PageRequest.of(qo.getCurrentPage() - 1, qo.getPageSize(),
                Sort.Direction.DESC, "_id");
        //3:调用dbhelper类
        return DBHelper.query(template, Region.class, query, pageable);
    }
  1. 添加
    带搜索框下拉框bootstrap-select
  2. 编辑
    回显
$('#refIds').selectpicker('val', refIds);
$('#refIds').selectpicker('refresh');
  1. 查看
  2. 删除
  3. 热门
    改变区域表里ishot的字段(0/1)

目的地管理

目的地的crud
吐司

 @Override
    public List<Destination> getToasts(String parentId) {
        // 中国 》 广东 》 广州
        if (!StringUtils.hasLength(parentId)) {
            return Collections.emptyList();
        }
        List<Destination> list = new ArrayList<>();
        createToast(list, parentId);
        Collections.reverse(list); // 集合反转
        return list;
    }

    private void createToast(List<Destination> list, String parentId) {
        // 广州
        Destination dest = this.get(parentId);
        list.add(dest);

        // 有父节点则调用自身
        if (StringUtils.hasLength(dest.getParentId())) {
            createToast(list, dest.getParentId());
        }
    }

前端

@Override
    public List<Destination> queryByRegionIdForApi(String regionId) {
        List<Destination> list;
        // 区分是否国内,查询省份
        if ("-1".equals(regionId)) {
            // 查询所有省份
            list = repository.findByParentName("中国");
        } else {
            // 非国内
            Region region = regionService.get(regionId);
            List<String> ids = region.getRefIds();
            list = repository.findByIdIn(ids);
        }

        // 查询第二层 :找儿子
        for (Destination dest : list) {
            // 显示前5个: 知识点:jpa方法怎么分页显示i
            PageRequest pageRequest = PageRequest.of(0, 5, Sort.Direction.ASC, "_id");
            List<Destination> children = repository.findByParentId(dest.getId(), pageRequest);
            dest.setChildren(children);
        }

        return list;
    }

六、 vue的使用

(一)、vue的使用

直接用 <script> 引入
开发版本:https://cn.vuejs.org/js/vue.js

(二)、生命周期图示

下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。

Vue 实例生命周期

(三)、vue常见指令

(四)、vue事件

(五)、vue的属性

dataFormat:function () {
},
sexFilter:function (sex) {
      return sex == 0? '女':'男'
}

(六)、前后端分离

浏览器怎么发起请求

前后端分离操作流程.png

vue怎么接受处理请求

浏览器访问页面,vue的mounted 属性初始化发起跨域异步请求,调用后端暴露的接口,控制器处理返回json数据data,使用js加工,设置到vue的data:属性,通过v-for循环取出数据

vue怎么发起异步请求

mounted 属性初始化操作,发起$.get异步请求

接口服务器怎么接受并处理异步请求

控制器通过$get异步请求,调用后端暴露的接口,再通过业务方法查询出list数据后返回json数据给前端

vue怎么处理接口返回json格式结果

通过控制器出来返回json数据,使用异步请求的回调函数来接收参数,设置进vue的data属性里

(七 )、其他

跨域

@SpringBootApplication
public class MongodbApplication implements WebMvcConfigurer {

   //跨域访问
   @Bean
   public WebMvcConfigurer corsConfigurer() {
       return new WebMvcConfigurer() {
           @Override
           //重写父类提供的跨域请求处理的接口
           public void addCorsMappings(CorsRegistry registry) {
               //添加映射路径
               registry.addMapping("/**")
                       //放行哪些原始域
                       .allowedOrigins("*")
                       //是否发送Cookie信息
                       .allowCredentials(true)
                       //放行哪些原始域(请求方式)
                       .allowedMethods("GET", "POST", "PUT", "DELETE","OPTIONS")
                       //放行哪些原始域(头部信息)
                       .allowedHeaders("*")
                       //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
                       .exposedHeaders("Header1", "Header2");
           }
       };
   }
   public static void main(String[] args) {
       SpringApplication.run(MongodbApplication.class,args);
   }
}

七、mongodb的使用

(一)、简介

(二)、 MongoDB范式化与反范式化

(三)、Spring Data

Spring Data方法命名规范

关键字 例子 JPQL
And findByNameAndAge(String name, Integer age) where name = ? and age = ?
Or findByNameOrAge(String name, Integer age) where name = ? or age = ?
Is findByName(String name) where name = ?
Between findByAgeBetween(Integer min, Integer max) where age between ? and ?
LessThan findByAgeLessThan(Integer age) where age < ?
LessThanEqual findByAgeLessThanEqual(Integer age) where age <= ?
GreaterThan findByAgeGreaterThan(Integer age) where age > ?
GreaterThanEqual findByAgeGreaterThanEqual(Integer age) where age >= ?
After 等同于GreaterThan
Before 等同于LessThan
IsNull findByNameIsNull() where name is null
IsNotNull findByNameIsNotNull() where name is not null
Like findByNameLike(String name) where name like ?
NotLike findByNameNotLike(String name) where name not like ?
StartingWith findByNameStartingWith(String name) where name like '?%'
EndingWith findByNameEndingWith(String name) where name like '%?'
Containing findByNameContaining(String name) where name like '%?%'
OrderByXx[desc] findByIdOrderByXx[Desc] (Long id) where id = ? order by Xx [desc]
Not findByNameNot(String name) where name != ?
In findByIdIn(List<Long> ids) where id in ( ... )
NotIn findByIdNotIn(List<Long> ids) where id not in ( ... )
True findByXxTrue() where Xx = true
False findByXxFalse() where Xx = false
IgnoreCase findByNameIgnoreCase(String name) where name = ? (忽略大小写)

(四)、spring data jpa

https://www.cnblogs.com/chenglc/p/11226693.html

八、 旅游攻略

后端

攻略分类

添加
修改
删除

攻略主题

添加
修改
删除

攻略明细

@RequestMapping("/uploadImg_ck")
    @ResponseBody
    public Map<String, Object> upload(MultipartFile upload, String module){
        Map<String, Object> map = new HashMap<String, Object>();
        String imagePath= null;
        if(upload != null && upload.getSize() > 0){
            try {
                //图片保存, 返回路径
                imagePath =  UploadUtil.uploadAli(upload);
                //表示保存成功
                map.put("uploaded", 1);
                map.put("url",imagePath);

            }catch (Exception e){
                e.printStackTrace();
                map.put("uploaded", 0);
                Map<String, Object> mm = new HashMap<String, Object>();
                mm.put("message",e.getMessage() );
                map.put("error", mm);
            }
        }
        return map;
    }

3>攻略添加注意冗余字段

前端

前端目的地明细
1>吐司
2>目的地下分类概况/明细

分类概况.png

3>目的地下点击量前3的攻略
4>攻略明细

九、 旅游日记

后端

1>游记表设计
2>游记的列表
3>游记查看
4>游记的审核/下架

前端

目的地明细中-游记

带范围条件查询.png

1>带范围条件查询分析
2>带范围条件查询实现
3>游记首页

游记的添加

游记明细

1>明细查看
2>吐司
3>点击量前3 攻略/游记

十、评论

前端

评论类型

盖楼式
微信评论式

步骤

 // 添加评论
    @RequireLogin
    @PostMapping("addComment")
    private Object addComment(StrategyComment comment, @UserParam UserInfo userInfo) {

        //评论数+1
        strategyStatisRedisService.increaseReplynum(comment.getStrategyId());

        //属性拷贝,参数1: 源数据  , 参数2: 目标数据对象
        //底层原理:使用内省方式,同名属性进行赋值
        BeanUtils.copyProperties(userInfo, comment);
        comment.setUserId(userInfo.getId());

        strategyCommentService.save(comment);
        return JsonResult.success();
    }

2>查询

3>点赞

@Override
    public void commentThumb(String cid, String uid) {
        // 获取评论操作对象
        StrategyComment comment = this.get(cid);
        // 获取评论数
        int thumbupnum = comment.getThumbupnum();
        // 获取点赞用户id集合
        List<String> userlist = comment.getThumbuplist();

        //1:判断当前用户是否点赞过
        if (!userlist.contains(uid)) {
            // 点赞
            comment.setThumbupnum(thumbupnum + 1);
            userlist.add(uid);
            comment.setThumbuplist(userlist);
        } else {
            // 取消点赞
            comment.setThumbupnum(thumbupnum - 1);
            userlist.remove(uid);
            comment.setThumbuplist(userlist);
        }
        // 2:保存修改后的对象
        repository.save(comment);
    }

十一、 数据统计

使用redis初始化统计数据后,回显到前端页面上,前端直接操作redis来修改统计数据,redis上的数据,经过spring定时任务持久化到monodb数据库中

统计的实体

/**
 * 攻略redis中统计数据
 * 运用模块:
 *  1:数据统计(回复,点赞,收藏,分享,查看)
 */
@Getter
@Setter
public class StrategyStatisVO implements Serializable {

    private Long strategyId;  //攻略id
    private int viewnum;  //点击数
    private int replynum;  //攻略评论数
    private int favornum; //收藏数
    private int sharenum; //分享数
    private int thumbsupnum; //点赞个数
}
缓存与统计对象分析.png

redis的初始化

1>分析

初始化.png

2>spring监听器
3>逻辑实现

// spring容器启动好之后马上执行的方法
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        System.out.println("=========================== vo对象的初始化 begin ====================");

        // 1:查询mongodb中的所有攻略
        List<Strategy> list = strategyService.list();
        // 2: 遍历这些攻略对象封装到统计vo对象
        for (Strategy strategy : list) {

            //第一次初始化完成之后, 如果页面进行操作, redis数据会发生变动, 在没有持久化入库前,再次启动
            //如果不做跳过处理,会出现旧数据覆盖信息数据

            // 如果redis已经存在vo对象,直接跳过
            if(strategyStatisRedisService.isVoExists(strategy.getId())){
                continue;
            }

            // 3:添加到redis中
            StrategyStatisVO vo = new StrategyStatisVO();
            BeanUtils.copyProperties(strategy, vo);
            vo.setStrategyId(strategy.getId());
            strategyStatisRedisService.setStrategyStatisVO(vo);
        }
        System.out.println("=========================== vo对象的初始化 end ====================");
    }

redis的持久化

1>分析


持久化.png

2>spring定时器

3>逻辑实现
使用Cron表达式实现定时

@Scheduled(cron="0/10 * * * * ?")
    public void dowWork() {
        System.out.println("=========== vo对象持久化-begin ==========" + new Date());
        // 1:获取所有vo对象
        List<StrategyStatisVO> vo = strategyStatisRedisService.queryByPattern(RedisKeys.STRATEGY_STATIS_VO.getPerfix());
        // 2:遍历对象集合,执行持久化
        for (StrategyStatisVO strategyStatisVO : vo) {
            strategyStatisRedisService.saveVo(strategyStatisVO);
        }
        System.out.println("=========== vo对象持久化-end ==========");
    }

十二、 网站首页

banner

后端

前端

十三、 elasticsearch的使用

image.png

(一)、kibana

ES操作客户端-kibana

操作

#添加
#设置5个片区
#设置1个备份
PUT my_index
{
  "settings": {
    "number_of_shards": 5,
    "number_of_replicas": 1 
  }
}
#### 新增和替换文档

语法:PUT /索引名/类型名/文档ID
{
field1: value1,
field2: value2,
...
}

注意:当索引/类型/映射不存在时,会使用默认设置自动添加
ES中的数据一般是从别的数据库导入的,所以文档的ID会沿用原数据库中的ID
索引库中没有该ID对应的文档时则新增,拥有该ID对应的文档时则替换

需求1:新增一个文档
需求2:替换一个文档


每一个文档都内置以下字段

>_index:所属索引
>
>_type:所属类型
>
>_id:文档ID
>
>_version:乐观锁版本号
>
>_source:数据内容

#### 查询文档

语法:
根据ID查询 -> GET /索引名/类型名/文档ID
查询所有(基本查询语句) -> GET /索引名/类型名/_search

需求1:根据文档ID查询一个文档
需求2:查询所有的文档


查询所有结果中包含以下字段

>took:耗时
>
>_shards.total:分片总数
>
>hits.total:查询到的数量
>
>hits.max_score:最大匹配度
>
>hits.hits:查询到的结果
>
>hits.hits._score:匹配度

#### 删除文档

语法:DELETE /索引名/类型名/文档ID
注意:这里的删除并且不是真正意义上的删除,仅仅是清空文档内容而已,并且标记该文档的状态为删除

需求1:根据文档ID删除一个文档
需求2:替换刚刚删除的文档




## 高级查询

Elasticsearch基于JSON提供完整的查询DSL(Domain Specific Language:领域特定语言)来定义查询。

基本语法:
GET /索引名/类型名/_search


一般都是需要配合查询参数来使用的,配合不同的参数有不同的查询效果

参数配置项可以参考博客:<https://www.jianshu.com/p/6333940621ec>

### 结果排序

参数格式:
{
"sort": [
{field: 排序规则},
...
]
}

排序规则:
asc表示升序
desc:表示降序
没有配置排序的情况下,默认按照评分降序排列

分页查询

参数格式:
{
  "from": start,
  "size": pageSize
}

需求1:查询所有文档按照价格降序排列
需求2:分页查询文档按照价格降序排列,显示第2页,每页显示3个


#查询所有
GET _cat/indices
#查询单个
GET my_index
#删除
DELETE my_index

倒排索引

image.png

Spring Data Elasticsearch

依赖

  <!--SpringBoot整合Spring Data Elasticsearch的依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>

实体类

/**
@Document:配置操作哪个索引下的哪个类型
@Id:标记文档ID字段
@Field:配置映射信息,如:分词器
*/
@Getter@Setter@ToString
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName="shop_product", type="shop_product")
public class Product {
@Id
private String id;

@Field(analyzer="ik_max_word",searchAnalyzer="ik_max_word", type=FieldType.Text)
private String title;

private Integer price;

@Field(analyzer="ik_max_word",searchAnalyzer="ik_max_word", type=FieldType.Text)
private String intro;

@Field(type=FieldType.Keyword)
private String brand;

}

配置信息

#application.properties
# 配置集群名称,名称写错会连不上服务器,默认elasticsearch
spring.data.elasticsearch.cluster-name=elasticsearch
# 配置集群节点
spring.data.elasticsearch.cluster-nodes=localhost:9300

十四、主页搜索

es数据初始化操作

@GetMapping("/dataInit")
    public Object dataInit() {

        // 用户初始化
        List<UserInfo> us = userInfoService.list();
        for (UserInfo userInfo : us) {
            UserInfoEs userInfoEs = new UserInfoEs();
            BeanUtils.copyProperties(userInfo, userInfoEs);
            userInfoEsService.save(userInfoEs);
        }

        // 目的地初始化
        List<Destination> dsts = destinationService.list();
        for (Destination destination : dsts) {
            DestinationEs destinationEs = new DestinationEs();
            BeanUtils.copyProperties(destination, destinationEs);
            destinationEsService.save(destinationEs);
        }

        // 游记初始化
        List<Travel> trs = travelService.list();
        for (Travel travel : trs) {
            TravelEs travelEs = new TravelEs();
            BeanUtils.copyProperties(travel, travelEs);
            travelEsService.save(travelEs);
        }

        // 攻略初始化
        List<Strategy> sts = strategyService.list();
        for (Strategy strategy : sts) {
            StrategyEs strategyEs = new StrategyEs();
            BeanUtils.copyProperties(strategy, strategyEs);
            strategyEsService.save(strategyEs);
        }

        return "ok";
    }

关键字搜索

1>目的地精确搜索

// 查询目的地
    private Object searchDest(SearchQueryObject qo) {
        SearchResultVo result = new SearchResultVo();
        // 1.判断目的地是否存在
        // es: 通过destName匹配,然后找到ids集合,再通过ids查询mongodb得到数据集合
        // mongodb: 先通过destName匹配,得到数据集合
        Destination dest = destinationService.findByName(qo.getKeyword());

        // 2.如果存在,查询该目的地下所有攻略,游记
        if (dest != null) {
            // 攻略
            List<Strategy> sts = strategyService.findByDestName(dest.getName());
            result.setStrategys(sts);
            // 游记
            List<Travel> ts = travelService.findByDestName(dest.getName());
            result.setTravels(ts);
            // 用户
            List<UserInfo> us = userInfoService.findByCity(dest.getName());
            result.setUsers(us);

            result.setTotal(sts.size() + ts.size() + us.size() + 0L);
        }

        // 3:如果不存在,页面提示
        Map<String, Object> map = new HashMap<>();
        map.put("qo", qo);
        map.put("dest", dest);
        map.put("result", result);
        return JsonResult.success(map);
    }

2>其他全文搜索

// --------- 全文搜索 ---------
    // 查询攻略
    private Object searchStrategy(SearchQueryObject qo) {
        Page<StrategyEs> page = searchService.searchWithHighlight(StrategyEs.INDEX_NAME,
                StrategyEs.TYPE_NAME, StrategyEs.class, qo,  "title", "subTitle", "summary");
        return JsonResult.success(new ParamMap().put("page",page ).put("qo", qo));
    }

    // 查询游记
    private Object searchTravel(SearchQueryObject qo) {
        Page<TravelEs> page = searchService.searchWithHighlight(TravelEs.INDEX_NAME,
                TravelEs.TYPE_NAME, TravelEs.class, qo, "title", "summary");
        return JsonResult.success(new ParamMap().put("page",page ).put("qo", qo));
    }

    // 查询用户
    private Object searchUser(SearchQueryObject qo) {
        Page<UserInfoEs> page = searchService.searchWithHighlight(UserInfoEs.INDEX_NAME,
                UserInfoEs.TYPE_NAME, UserInfoEs.class, qo, "city", "nickname");
        return JsonResult.success(new ParamMap().put("page",page ).put("qo", qo));
    }

十五、一问一答

mongodb的事务

mongodb3.0开始WiredTiger引擎可以针对单个文档来保证ACID特性, MongoDB 4.0, 支持复制集多文档事务,支持多文档ACID特性

Redis支持的数据类型?

支持string、hash、list(元素允许重复,有顺序区别)、set(不允许重复,无序集合)、zset(有序集合,分数score却可以重复。)

上一篇 下一篇

猜你喜欢

热点阅读