做企业平台的网站有哪些方面工具类网站开发
- 作者: 五速梦信息网
- 时间: 2026年04月18日 10:01
当前位置: 首页 > news >正文
做企业平台的网站有哪些方面,工具类网站开发,陕西省西安市制作网站,广西建设学院网站总体框架
本项目主要着手于获取最新最热新闻资讯#xff0c;以微服务构架为技术基础搭建校内仅供学生教师使用的校园新媒体app。以文章为主线的核心业务主要分为如下子模块。自媒体模块实现用户创建功能、文章发布功能、素材管理功能。app端用户模块实现文章搜索、文章点赞、…总体框架
本项目主要着手于获取最新最热新闻资讯以微服务构架为技术基础搭建校内仅供学生教师使用的校园新媒体app。以文章为主线的核心业务主要分为如下子模块。自媒体模块实现用户创建功能、文章发布功能、素材管理功能。app端用户模块实现文章搜索、文章点赞、关注、商家优惠卷秒杀等功能。业务可以帮助商家引流增加曝光度也可以为用户提供查看提供附近消费场所。
主要技术SpringBootSpringcloudMySQLMyBatisPlusRedisElasticSearchKafkaMongoDBxxl-job
主要职责
1 采用Redis作缓存并手动封装了一个工具类通过互斥锁逻辑过期的方式解决缓存击穿问题·。 1.1 Redis工具类
我们主要对这部分功能的java代码进行讲解
Slf4j //日志
Component//把bean给Spring维护
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;private static final ExecutorService CACHE_REBUILD_EXECUTOR Executors.newFixedThreadPool(10);//构造函数注入stringRedisTemp也可以用resource注解public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate stringRedisTemplate;}
//方法1 把value转为JsonStringpublic void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}
//方法2public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {// 设置逻辑过期RedisData redisData new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));// 写入RedisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}
//方法3 作为工具返回的是泛型public R,ID R queryWithPassThrough(//前缀id返回值类型数据库查询的逻辑有参有返回值的函数ttlttl单位String keyPrefix, ID id, ClassR type, FunctionID, R dbFallback, Long time, TimeUnit unit){String key keyPrefix id;// 1.从redis查询商铺缓存String json stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(json)) {// 3.存在直接返回return JSONUtil.toBean(json, type);}//不存在// 判断命中的是否是空值if (json ! null) {// 返回一个错误信息return null;}// 4.不存在根据id查询数据库
//how从数据库查?涉及类型,sql语句等逻辑让调用者写R r dbFallback.apply(id);// 5.不存在返回错误if (r null) {// 将空值写入redisstringRedisTemplate.opsForValue().set(key, , CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息return null;}// 6.存在写入redisthis.set(key, r, time, unit);//方法1return r;}
//方法4public R, ID R queryWithLogicalExpire(//前缀id返回值类型数据库查询的逻辑有参有返回值的函数ttlttl单位String keyPrefix, ID id, ClassR type, FunctionID, R dbFallback, Long time, TimeUnit unit) {String key keyPrefix id;// 1.从redis查询商铺缓存String json stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isBlank(json)) {// 3.存在直接返回return null;}// 4.命中需要先把json反序列化为对象RedisData redisData JSONUtil.toBean(json, RedisData.class);R r JSONUtil.toBean((JSONObject) redisData.getData(), type);LocalDateTime expireTime redisData.getExpireTime();// 5.判断是否过期if(expireTime.isAfter(LocalDateTime.now())) {// 5.1.未过期直接返回店铺信息return r;}// 5.2.已过期需要缓存重建// 6.缓存重建// 6.1.获取互斥锁String lockKey LOCK_SHOP_KEY id;boolean isLock tryLock(lockKey);// 6.2.判断是否获取锁成功if (isLock){// 6.3.成功开启独立线程实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() - {try {// 查询数据库R newR dbFallback.apply(id);// 重建缓存 方法2this.setWithLogicalExpire(key, newR, time, unit);} catch (Exception e) {throw new RuntimeException(e);}finally {// 释放锁unlock(lockKey);}});}// 6.4.返回过期的商铺信息return r;}
//互斥锁 解决缓存击穿public R, ID R queryWithMutex(String keyPrefix, ID id, ClassR type, FunctionID, R dbFallback, Long time, TimeUnit unit) {String key keyPrefix id;// 1.从redis查询商铺缓存String shopJson stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {// 3.存在直接返回return JSONUtil.toBean(shopJson, type);}// 判断命中的是否是空值if (shopJson ! null) {// 返回一个错误信息return null;}// 4.实现缓存重建// 4.1.获取互斥锁String lockKey LOCK_SHOP_KEY id;R r null;try {boolean isLock tryLock(lockKey);// 4.2.判断是否获取成功if (!isLock) {// 4.3.获取锁失败休眠并重试Thread.sleep(50);return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);}// 4.4.获取锁成功根据id查询数据库r dbFallback.apply(id);// 5.不存在返回错误if (r null) {// 将空值写入redisstringRedisTemplate.opsForValue().set(key, , CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息return null;}// 6.存在写入redisthis.set(key, r, time, unit);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {// 7.释放锁unlock(lockKey);}// 8.返回return r;}
//尝试拿锁private boolean tryLock(String key) {Boolean flag stringRedisTemplate.opsForValue().setIfAbsent(key, 1, 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}
//释放锁private void unlock(String key) {stringRedisTemplate.delete(key);}
}
我们可以直接看方法4这里通过泛型的方式对输入的参数做了以下处理 1、从redis查询商铺缓存判断是否存在 2、存在需要先把json反序列化为对象 3、判断是否过期没有过期直接返回过期了进行缓存重建 4、过期了尝试获取锁对象此处采用Redis中的setnx方法实现分布式锁的功能。使用店铺前缀加商品id作为key 5、没有获取到返回过期数据。获取到了使用线程池的submit方法开启新的线程去重建缓存也就是更新过期时间 6、重建完成释放锁
1.2 ShopServiceImpl 中调用工具类封装的方法三解决缓存穿透
Resource
private CacheClient cacheClient;//注入Overridepublic Result queryById(Long id) {// 解决缓存穿透Shop shop cacheClient.queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);// 互斥锁解决缓存击穿// Shop shop cacheClient// .queryWithMutex(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);// 逻辑过期解决缓存击穿// Shop shop cacheClient// .queryWithLogicalExpire(CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.SECONDS);if (shop null) {return Result.fail(店铺不存在);}// 7.返回return Result.ok(shop);}这里传入缓存击穿工具类的泛型参数包括
CACHE_SHOP_KEY店铺的关键字id商品的idShop.class店铺的类型-JSON序列化时指定对象的类型this::getById方法的泛型可以使用dbFallback.apply(传入的参数)调用这个也可以定义为任何可执行方法20L逻辑过期时间TimeUnit.SECONDS时间的单位
2 使用Redis作为分布式锁解决集群下的线程并发安全问题基于消费者组模式完成异步下单功能。
2.1 分布式锁的概率和可能出现的问题
分布式锁满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁只要大家使用的是同一把锁那么我们就能锁住线程不让线程进行让程序串行执行这就是分布式锁的核心思路
我们可以怎么描述这个问题呢
1、解决错误删除的问题2、解决释放锁条件判断成功后出现阻塞把别的线程释放的问题。
基本思路是使用setnx但是为了防止死锁我们加入过期时间。但这这样如果一个线程阻塞会导致别的线程把自己的锁给释放为此我们加入了线程id的验证。但是存在一个高并发情况下有可能线程id相同的情况为此我们加入了UUID保证每个线程的唯一。释放锁的过程必须是原子性因为如果释放过程条件判断是自己线程id的时候后出现阻塞的话也会导致锁的自动释放等别的进程来了之后刚获取的锁会被这个阻塞的释放锁过程释放所以我们采用了lua脚本的方式保证了获取锁和释放锁的原子性。最后为了进一步提高响应速度我们使用了异步下单的方式提高并发能力。 java代码
利用setnx方法进行加锁同时增加过期时间防止死锁此方法可以保证加锁和增加过期时间具有原子性
private string name;//锁的名称
private StringRedisTemplate stringRedisTemplate//构造函数 传入参数
public SimpleRedisLock( string name,StringRedisTemplate stringRedisTemplate){this.namename;this.stringRedisTemplate stringRedisTemplate;
}private static final String KEY_PREFIXlock://key的统一前缀
Override
public boolean tryLock(long timeoutSec) {// 获取线程标示 对应redis的value 代表 哪个线程获得锁了String threadId Thread.currentThread().getId()// 获取锁Boolean success stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, threadId , timeoutSec, TimeUnit.SECONDS);
//封装函数 导致返回值 自动拆箱 变成 boolean 但是有null空指针风险所以用.TRUE.equals return Boolean.TRUE.equals(success);
}2.2 Redis分布式锁误删问题
2.2.1 误删问题原因
1、由于为了防止死锁问题设计过期时间。当过期时间后又有线程获取了锁对象之前没有执行完的方法就会释放对象锁。 2、在解决1的情况下如果释放锁的过程中出现了阻塞那么该线程会以为自己一直是要释放自己的锁然而却把别的刚拿到的锁给释放了。
2.2.2 误删解决方法
在获取锁时存入线程标示在释放锁时先获取锁中的线程标示判断是否与当前线程标示一致。
如果一致则释放锁如果不一致则不释放锁
2.2.3 Lua脚本解决多条命令原子性问题
Redis提供了Lua脚本功能在一个脚本中编写多条Redis命令确保多条命令执行时的原子性
lua脚本
– 这里的 KEYS[1] 就是锁的key这里的ARGV[1] 就是当前线程标示
– 获取锁中的标示判断是否与当前线程标示一致
if (redis.call(GET, KEYS[1]) ARGV[1]) then– 一致则删除锁return redis.call(DEL, KEYS[1])
end
– 不一致则直接返回
return 0java代码
//泛型Long是返回值类型
private static final DefaultRedisScriptLong UNLOCK_SCRIPT;static {//静态代码块 初始化UNLOCK_SCRIPT new DefaultRedisScript();//设置脚本 位置ClassPathResource这个类会默认去classpath下面找我们的lua就放在resources下面滴 “文件名称”UNLOCK_SCRIPT.setLocation(new ClassPathResource(unlock.lua));UNLOCK_SCRIPT.setResultType(Long.class);//返回值}public void unlock() {// 调用lua脚本stringRedisTemplate.execute(UNLOCK_SCRIPT,//提前读取文件 等到释放锁的时候读取 会产生IO流 降低性能//.singletonList 把锁的key转化成单元素 集合形式 返回Collections.singletonList(KEY_PREFIX name),ID_PREFIX Thread.currentThread().getId());//线程表示
}
经过以上代码改造后我们就能够实现 拿锁比锁删锁的原子性动作了~2.3分布式锁的总结
以上是我们解决分布式锁的查询的问题该分布式锁具有以下中重点特性 1、利用setnx方法满足互斥性 2、利用lua脚本可以实现释放锁的原子性 3、利用逻辑过期时间可以避免死锁提高安全性 4、利用redis集群具有高可用和高并发特性
2.4 秒杀之异步下单功能
//异步处理线程池 秒杀订单 处理器
private static final ExecutorService SECKILL_ORDER_EXECUTOR Executors.newSingleThreadExecutor();//在当前 Impl类初始化之后执行 因为当这个类初始化好了之后随时都是有可能要执行的
PostConstruct
private void init() {SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
}
// 用于线程池处理的任务
// 当初始化完毕后就会去从队列 中去拿信息private class VoucherOrderHandler implements Runnable{Overridepublic void run() {while (true){//不断地从队列 取try {// 1.获取队列中的订单信息take() 方法获取 和删除 该队列的 头部如果需要则 等待 直到 元素可用VoucherOrder voucherOrder orderTasks.take();// 2.创建订单handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error(处理订单异常, e);}}}private void handleVoucherOrder(VoucherOrder voucherOrder) {//1.获取用户Long userId voucherOrder.getUserId();// 2.创建锁对象RLock redisLock redissonClient.getLock(lock:order: userId);// 3.尝试获取锁boolean isLock redisLock.lock();// 4.判断是否获得锁成功if (!isLock) {// 获取锁失败直接返回失败或者重试log.error(不允许重复下单);return;}try {//注意由于是spring的事务是放在threadLocal中此时的是多线程事务会失效proxy.createVoucherOrder(voucherOrder);} finally {// 释放锁redisLock.unlock();}}//阻塞队列private BlockingQueueVoucherOrder orderTasks new ArrayBlockingQueue(1024 * 1024);Overridepublic Result seckillVoucher(Long voucherId) {Long userId UserHolder.getUser().getId();long orderId redisIdWorker.nextId(order);// 1.执行lua脚本Long result stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString(), String.valueOf(orderId));int r result.intValue();// 2.判断结果是否为0if (r ! 0) {// 2.1.不为0 代表没有购买资格return Result.fail(r 1 ? 库存不足 : 不能重复下单);}//2.2 为0 有购买资格把下单信息保存到阻塞队列VoucherOrder voucherOrder new VoucherOrder();// 2.3.订单idlong orderId redisIdWorker.nextId(order);voucherOrder.setId(orderId);// 2.4.用户idvoucherOrder.setUserId(userId);// 2.5.代金券idvoucherOrder.setVoucherId(voucherId);// 2.6.放入阻塞队列orderTasks.add(voucherOrder);//3.子线程不能获取代理对象主线程可以 获取了给handler创建订单proxy (IVoucherOrderService)AopContext.currentProxy();//4.返回订单idreturn Result.ok(orderId);}Transactional//把他的voucherOrder创建 挪到 seckillVoucherpublic void createVoucherOrder(VoucherOrder voucherOrder) {Long userId voucherOrder.getUserId();// 5.1.查询订单int count query().eq(user_id, userId).eq(voucher_id, voucherOrder.getVoucherId()).count();// 5.2.判断是否存在if (count 0) {// 用户已经购买过了log.error(用户已经购买过了);return ;}// 6.扣减库存boolean success seckillVoucherService.update().setSql(stock stock - 1) // set stock stock - 1.eq(voucher_id, voucherOrder.getVoucherId()).gt(stock, 0) // where id ? and stock 0.update();if (!success) {// 扣减失败log.error(库存不足);return ;}save(voucherOrder);}代码解释 1、主线程主要在redis中对使用lua脚本对库存、用户id判断并将创建的订单放到队列中 2、开启线程池对队列中的数据后台独立线程异步创建订单
2.5 异步下单总结
秒杀业务的优化思路是什么或者可以问异步下单你做了什么工作
先利用Redis完成库存余量、一人一单判断完成抢单业务再将下单业务放入阻塞队列利用独立线程异步下单提高并发量
3、基于Redis中的sortedSet的排序功能实现点赞排行榜用时间戳作为score筛选出最早点赞的用户
Redis的sorted_set是有序集合在set的基础上增加score属性用来排序
点赞功能Overridepublic Result likeBlog(Long id) {// 1.获取登录用户Long userId UserHolder.getUser().getId();// 2.判断当前登录用户是否已经点赞String key BLOG_LIKED_KEY id;Double score stringRedisTemplate.opsForZSet().score(key, userId.toString());if (score null) {// 3.如果未点赞可以点赞// 3.1.数据库点赞数 1boolean isSuccess update().setSql(liked liked 1).eq(id, id).update();// 3.2.保存用户到Redis的set集合 zadd key value scoreif (isSuccess) {stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());}} else {// 4.如果已点赞取消点赞// 4.1.数据库点赞数 -1boolean isSuccess update().setSql(liked liked - 1).eq(id, id).update();// 4.2.把用户从Redis的set集合移除if (isSuccess) {stringRedisTemplate.opsForZSet().remove(key, userId.toString());}}return Result.ok();}private void isBlogLiked(Blog blog) {//中间没变String key blog:liked: blog.getId();Double score stringRedisTemplate.opsForZSet().score(key, userId.toString());blog.setIsLike(score ! null);}点赞列表查询
Override
public Result queryBlogLikes(Long id) {String key BLOG_LIKED_KEY id;// 1.查询top5的点赞用户 zrange key 0 4SetString top5 stringRedisTemplate.opsForZSet().range(key, 0, 4);if (top5 null || top5.isEmpty()) {//没人点赞return Result.ok(Collections.emptyList());}// 2.解析出其中的用户idListLong ids top5.stream().map(Long::valueOf).collect(Collectors.toList());String idStr StrUtil.join(,, ids);//3.0 未实现排序功能的点赞top5 因为传参的是5,1
// listByIdsin(5,1)是倒着来的 要用 order by field/* ListUserDTO userDTOS userService.listByIds(ids).stream().map(user - BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());/// 3.根据用户id查询用户 WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1)ListUserDTO userDTOS userService.query()//自定义查询.in(id, ids).last(ORDER BY FIELD(id, idStr )).list().stream().map(user - BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());// 4.返回return Result.ok(userDTOS);
}解析 redis数据结构都是key-value结构 1、点赞时使用博客的唯一主键作为zset的key使用用户id作为value。这样每次点赞或者取消点赞可以把相应的用户添加或者移除。需要注意的是我们在加入点赞信息的时候需要加入时间戳为后面的点赞排序做准备。 2、查看实时点赞列表时需要查询top5的点赞用户 zrange key 0 4
4、使用MongoDB记录用户搜索记录使用ElasticSearch优化搜索功能提高用户体验和减轻数据库压力。
此处是将黑马头条中的自媒体模块的搜索功能加入到了这个外卖app中。
4.1 MongoDB记录用户搜索记录
4.1.1 实体类
用户搜索记录对应的集合对应实体类java
package com.heima.search.pojos;import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;import java.io.Serializable;
import java.util.Date;/** p* APP用户搜索信息表* /p* author itheima/
Data
Document(ap_user_search)
public class ApUserSearch implements Serializable {private static final long serialVersionUID 1L;/** 主键/private String id;/** 用户ID/private Integer userId;/** 搜索词/private String keyword;/** 创建时间/private Date createdTime;}4.1.2 搜索微服务集成mongodb
①pom依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-mongodb/artifactId
/dependency②nacos配置
spring:data:mongodb:host: 192.168.200.130port: 27017database: leadnews-history4.1.3 创建Service中新增insert方法并设计实现类。在实现方法中添加注解Async来实现异步调用
Service
Slf4j
public class ApUserSearchServiceImpl implements ApUserSearchService {Autowiredprivate MongoTemplate mongoTemplate;/** 保存用户搜索历史记录* param keyword* param userId/OverrideAsyncpublic void insert(String keyword, Integer userId) {//1.查询当前用户的搜索关键词Query query Query.query(Criteria.where(userId).is(userId).and(keyword).is(keyword));ApUserSearch apUserSearch mongoTemplate.findOne(query, ApUserSearch.class);//2.存在 更新创建时间if(apUserSearch ! null){apUserSearch.setCreatedTime(new Date());mongoTemplate.save(apUserSearch);return;}//3.不存在判断当前历史记录总数量是否超过10apUserSearch new ApUserSearch();apUserSearch.setUserId(userId);apUserSearch.setKeyword(keyword);apUserSearch.setCreatedTime(new Date());Query query1 Query.query(Criteria.where(userId).is(userId));query1.with(Sort.by(Sort.Direction.DESC,createdTime));ListApUserSearch apUserSearchList mongoTemplate.find(query1, ApUserSearch.class);if(apUserSearchList null || apUserSearchList.size() 10){mongoTemplate.save(apUserSearch);}else {ApUserSearch lastUserSearch apUserSearchList.get(apUserSearchList.size() - 1);mongoTemplate.findAndReplace(Query.query(Criteria.where(id).is(lastUserSearch.getId())),apUserSearch);}}
}4.1.4 在文章搜索中加入插入搜索历史的记录
/** es文章分页检索** param dto* return/Overridepublic ResponseResult search(UserSearchDto dto) throws IOException {//1.检查参数if(dto null || StringUtils.isBlank(dto.getSearchWords())){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}ApUser user AppThreadLocalUtil.getUser();//异步调用 保存搜索记录if(user ! null dto.getFromIndex() 0){apUserSearchService.insert(dto.getSearchWords(), user.getId());}
4.1.5 加载搜索记录列表在mongoDb数据中查询前十条按照时间顺序倒叙展示
实现方法 /** 查询搜索历史** return/
Override
public ResponseResult findUserSearch() {//获取当前用户ApUser user AppThreadLocalUtil.getUser();if(user null){return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);}//根据用户查询数据按照时间倒序ListApUserSearch apUserSearches mongoTemplate.find(Query.query(Criteria.where(userId).is(user.getId())).with(Sort.by(Sort.Direction.DESC, createdTime)), ApUserSearch.class);return ResponseResult.okResult(apUserSearches);
}4.2 使用ElasticSearch优化搜索功能
4.2.1微服务参数配置
1、在父工程pom中添加依赖
!–elasticsearch–
dependencygroupIdorg.elasticsearch.client/groupIdartifactIdelasticsearch-rest-high-level-client/artifactIdversion7.4.0/version
/dependency
dependencygroupIdorg.elasticsearch.client/groupIdartifactIdelasticsearch-rest-client/artifactIdversion7.4.0/version
/dependency
dependencygroupIdorg.elasticsearch/groupIdartifactIdelasticsearch/artifactIdversion7.4.0/version
/dependency2、nacos配置中心添加搜索微服务配置ip和端口
spring:autoconfigure:exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
elasticsearch:host: 192.168.200.130port: 92004.2.2业务层实现类
Service
Slf4j
public class ArticleSearchServiceImpl implements ArticleSearchService {Autowiredprivate RestHighLevelClient restHighLevelClient;/** es文章分页检索** param dto* return/Overridepublic ResponseResult search(UserSearchDto dto) throws IOException {//1.检查参数if(dto null || StringUtils.isBlank(dto.getSearchWords())){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}//2.设置查询条件SearchRequest searchRequest new SearchRequest(app_info_article);SearchSourceBuilder searchSourceBuilder new SearchSourceBuilder();//布尔查询BoolQueryBuilder boolQueryBuilder QueryBuilders.boolQuery();//关键字的分词之后查询QueryStringQueryBuilder queryStringQueryBuilder QueryBuilders.queryStringQuery(dto.getSearchWords()).field(title).field(content).defaultOperator(Operator.OR);boolQueryBuilder.must(queryStringQueryBuilder);//查询小于mindate的数据RangeQueryBuilder rangeQueryBuilder QueryBuilders.rangeQuery(publishTime).lt(dto.getMinBehotTime().getTime());boolQueryBuilder.filter(rangeQueryBuilder);//分页查询searchSourceBuilder.from(0);searchSourceBuilder.size(dto.getPageSize());//按照发布时间倒序查询searchSourceBuilder.sort(publishTime, SortOrder.DESC);//设置高亮 titleHighlightBuilder highlightBuilder new HighlightBuilder();highlightBuilder.field(title);highlightBuilder.preTags(font stylecolor: red; font-size: inherit;);highlightBuilder.postTags(/font);searchSourceBuilder.highlighter(highlightBuilder);searchSourceBuilder.query(boolQueryBuilder);searchRequest.source(searchSourceBuilder);SearchResponse searchResponse restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);//3.结果封装返回ListMap list new ArrayList();SearchHit[] hits searchResponse.getHits().getHits();for (SearchHit hit : hits) {String json hit.getSourceAsString();Map map JSON.parseObject(json, Map.class);//处理高亮if(hit.getHighlightFields() ! null hit.getHighlightFields().size() 0){Text[] titles hit.getHighlightFields().get(title).getFragments();String title StringUtils.join(titles);//高亮标题map.put(h_title,title);}else {//原始标题map.put(h_title,map.get(title));}list.add(map);}return ResponseResult.okResult(list);}
}解析 1、搜索的参数关键字、时间、第几页ES中使用must和filter连接多个搜索条件 2、所得到的hit文件中会有单独的一个hightlight属性的值这里面设置了高亮的信息我们将其取出后覆盖之前查询的结果就可以实现高亮显示的效果 3、我们在覆盖是一些细节就是getFragments()来获取查询中的所有结果结果是一个分片集合。
5 使用Kafka完成内部系统的消息通知起到了削峰填谷及解耦的作用。
因为当时我们两个微服务是分开设计的自媒体微服务可以先处理完将信息发送给kafka让app微服务异步去更新信息。 功能 1、我们发送的是文章信息只是topic绑定了app微服务后期我们可以扩展更多的微服务例如大数据之类的 2、起到削峰填谷的效果在这里可能没有提现到。
5.1 导入kafka索引
1.导入kafka依赖 2、在生产者自媒体端的nacos配置中加入生产者配置。在app端的nacos中配置消费者配置
生产者
spring:kafka:bootstrap-servers: 192.168.200.130:9092consumer:group-id: \({spring.application.name}key-deserializer: org.apache.kafka.common.serialization.StringDeserializervalue-deserializer: org.apache.kafka.common.serialization.StringDeserializer消费者
spring:kafka:bootstrap-servers: 192.168.200.130:9092consumer:group-id: \){spring.application.name}key-deserializer: org.apache.kafka.common.serialization.StringDeserializervalue-deserializer: org.apache.kafka.common.serialization.StringDeserializer5.2 监听消息进行接下来的业务逻辑-文章上下架的功能
Component
Slf4j
public class ArtilceIsDownListener {Autowiredprivate ApArticleConfigService apArticleConfigService;KafkaListener(topics WmNewsMessageConstants.WM_NEWS_UP_OR_DOWN_TOPIC)public void onMessage(String message){if(StringUtils.isNotBlank(message)){Map map JSON.parseObject(message, Map.class);apArticleConfigService.updateByMap(map);log.info(article端文章配置修改articleId{},map.get(articleId));}}
}6 基于Feign熔断降级的功能编写降级逻辑防止服务出现雪崩问题。 服务降级是服务自我保护的一种方式或者保护下游服务的一种方式用于确保服务不会受请求突增影响变得不可用确保服务不会崩溃 服务降级虽然会导致请求失败但是不会导致阻塞。
6.1 feign远程调用是什么意思 feign只是定义了抽象方法方便依赖工程可以使用相应方法以及让别的工程实现它做具体的事情。类似于动态代理的意思一样把需要做的事情先抽象出方法来。 可以看到我们在定义了feign模块并使用了自媒体模块依赖它在feign定义了保存接口并由文章模块的类实现。这样的话我们通过自动注入调用feign接口的保存方法相当于实际调用的是文章模块中的保存方法。
具体实现过程
1、feign模块定义接口
feign接口
FeignClient(value leadnews-article,fallback IArticleClientFallback.class)
public interface IArticleClient {PostMapping(/api/v1/article/save)public ResponseResult saveArticle(RequestBody ArticleDto dto);
}2、在继承的模块中调用此方法
Autowiredprivate IArticleClient articleClient;
ResponseResult responseResult articleClient.saveArticle(dto);3、在需要调用的模块中定一个feign接口的实现类风格为正常controller形式
RestController
public class ArticleClient implements IArticleClient {Autowiredprivate ApArticleService apArticleService;PostMapping(/api/v1/article/save)Overridepublic ResponseResult saveArticle(RequestBody ArticleDto dto) {return apArticleService.saveArticle(dto);}
}
4、在severce的实现类中定义具体的函数功能
Overridepublic ResponseResult saveArticle(ArticleDto dto) {//1.检查参数if(dto null){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}6.2 feign降级逻辑的具体编写 实现步骤 1、编写降级逻辑的微服务注意这里的降级逻辑是写在feign模块中自我理解 因为刚才远程调用其实也是把接口实现了我们就可以在feign模块中写另外一个实现类在远程调用不成功的时候就可以运行另外一个实现类当然这也是feign给我们提供的功能具体实现是通过 2、自媒体使用feign远程调用 3、在feign远程接口也就是你在feign中抽象出来的方法上面中指向降级代码接口实现类 4、在自媒体微服务中开启服务降级功能
①在heima-leadnews-feign-api编写降级逻辑
package com.heima.apis.article.fallback;/** feign失败配置* author itheima*/
Component
public class IArticleClientFallback implements IArticleClient {Overridepublic ResponseResult saveArticle(ArticleDto dto) {return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,获取数据失败);}
}在自媒体微服务中添加类扫描降级代码类的包
package com.heima.wemedia.config;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;Configuration
ComponentScan(com.heima.apis.article.fallback)
public class InitConfig {
}②远程接口中指向降级代码
package com.heima.apis.article;import com.heima.apis.article.fallback.IArticleClientFallback;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;FeignClient(value leadnews-article,fallback IArticleClientFallback.class)
public interface IArticleClient {PostMapping(/api/v1/article/save)public ResponseResult saveArticle(RequestBody ArticleDto dto);
}③客户端开启降级heima-leadnews-wemedia
在wemedia的nacos配置中心里添加如下内容开启服务降级也可以指定服务响应的超时的时间
feign:# 开启feign对hystrix熔断降级的支持hystrix:enabled: true# 修改调用超时时间client:config:default:connectTimeout: 2000readTimeout: 20006.3 feign远程调用的底层逻辑
底层采用是动态代理的方式实现 1.feign采用的是基于接口的注解 2.feign整合了ribbon具有负载均衡的能力 3.整合了Hystrix具有熔断的能力 使用: 1.添加pom依赖。 2.启动类添加EnableFeignClients 3.定义一个接口 FeignClient(name“xxx”)指定调用哪个服务
- 上一篇: 做棋牌网站建设php网站开发推荐书籍
- 下一篇: 做企业网站到哪里找塑胶原料东莞网站建设技术支持
相关文章
-
做棋牌网站建设php网站开发推荐书籍
做棋牌网站建设php网站开发推荐书籍
- 技术栈
- 2026年04月18日
-
做棋牌推广网站违法不做仿牌网站空间
做棋牌推广网站违法不做仿牌网站空间
- 技术栈
- 2026年04月18日
-
做棋牌辅助网站厦门集美区网站建设
做棋牌辅助网站厦门集美区网站建设
- 技术栈
- 2026年04月18日
-
做企业网站到哪里找塑胶原料东莞网站建设技术支持
做企业网站到哪里找塑胶原料东莞网站建设技术支持
- 技术栈
- 2026年04月18日
-
做企业网站的费用挂什么科目婚介网站建站
做企业网站的费用挂什么科目婚介网站建站
- 技术栈
- 2026年04月18日
-
做企业网站的要点宁波制作网站的公司
做企业网站的要点宁波制作网站的公司
- 技术栈
- 2026年04月18日
