简单的下载中心的设计流程
直接上设计流程:
以上就是步骤了,至于每个步骤怎么实现,那方法就很多了 ,随意,达到目的就行。
至于各种问题,比如队列性能,消息重复或丢失,等等,碰到的再说 再处理吧 ╮(╯_╰)╭ 反正这是超简单的版本 不想了
以下是,我使用的实现方式,可以参考。【仅参考,毕竟大家的需求不同】
MQ 搭建-RocketMQ
这里的队列用了RocketMQ,至于为什么用这个,理由是这个我以前没玩过 ,试试(~ ̄▽ ̄)~
项目用的是spring-cloud-alibaba,所以还是用它的东西试试吧。
参考资料:
- RocketMQ官方:https://rocketmq.apache.org/docs/quick-start/
- springCloud官方:https://spring.io/projects/spring-cloud-alibaba/
- https://blog.csdn.net/m0_46689235/article/details/120945490
- https://blog.51cto.com/zhangxueliang/2984247
Start Name Server
docker pull rocketmqinc/rocketmq:latest
docker run -d -p 9876:9876 -v /u01/logs/rocketmq/namesrv/logs:/root/logs -v /u01/date/rocketmq/namesrv/store:/root/store --name rmqnamesrv rocketmqinc/rocketmq:latest sh mqnamesrv
Start Broker
创建broker配置文件(可能需要切换全新 mac[sudo -s])
mkdir -p /u01/docker/rocketmq/conf //递归创建文件
cd /u01/docker/rocketmq/conf //进入文件位置
vim broker.conf (通过vim编辑文件 没有会自动创建)
brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
# brokerIP1 = 127.0.0.1
brokerIP1 = 192.168.0.198
通过指定broker.conf配置文件来启动容器
docker run -d -p 10911:10911 -p 10909:10909 -v /u01/logs/rocketmq/broker/logs:/root/logs -v /u01/date/rocketmq/broker/store:/root/store -v /u01/docker/rocketmq/conf/broker.conf:/opt/rocketmq-latest/conf/broker.conf --name rmqbroker --link rmqnamesrv:namesrv -e "NAMESRV_ADDR=namesrv:9876" -e "MAX_POSSIBLE_HEAP=200000000" rocketmqinc/rocketmq:latest sh mqbroker -c /opt/rocketmq-latest/conf/broker.conf
start look View
# 下面这个 选一个就可以了 没啥差别
docker pull pangliang/rocketmq-console-ng //拉取镜像
docker run -d -e "JAVA_OPTS=-Drocketmq.namesrv.addr=172.17.192.173:9876 -Dcom.rocketmq.sendMessageWithVIPChannel=false" -p 8082:8080 -t pangliang/rocketmq-console-ng:latest
# 访问:http://192.168.0.198:8082/#/ops
docker pull styletang/rocketmq-console-ng:1.0.0
docker run -e "JAVA_OPTS=-Drocketmq.namesrv.addr=172.16.55.185:9876 -Dcom.rocketmq.sendMessageWithVIPChannel=false" -p 8082:8080 -t styletang/rocketmq-console-ng:1.0.0
小结
MQ就只是个MQ,在这个方案里面就只传递一个消息,异步一下,控制一下处理速度就行,至于别用其他的功能无所谓了。
所以用别的RabbitMQ,kafka 也都是可以了,没差,这里用不到多少功能与性能。
除非系统的量级大上去了。那,时候这个简单的方案也不能用了不是 ╮(╯_╰)╭
MQ使用与简单分发
rocketMQ本身的发送,就没什么要说的了,官方等等 导出都有栗子,这里直接就 贴 这个方案用到的。
参考:
- spring 测试类:https://www.jb51.net/article/233246.htm
- https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/spring-cloud-alibaba-examples/rocketmq-example/readme-zh.md
发送下载消息
@Component
public class DownloadMessageSendUtil {
... 精简已来一些代码了
private static String NAMW_SERVER; // 名称服务地址
private static String PRODUCER_NAME; // 生产者注册名称
private static String LISTENER_TOPIC; //监听 的topic 名称
private static DefaultMQProducer producer = null;
private static DefaultMQProducer init() throws MQClientException {
if (producer == null) {
producer = new
DefaultMQProducer(PRODUCER_NAME);
producer.setNamesrvAddr(NAMW_SERVER);
producer.start();
}
return producer;
}
// 简单发送MQ消息
public static SendResult simpleSend(String message,IDownloadResourceService downloadResourceService) throws Exception {
DefaultMQProducer producer = init();
Message msg = new Message(LISTENER_TOPIC,
"TagA",
(message).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
MsgInfo msgInfoId = JSON.parseObject(message, MsgInfo.class);
SendResult sendResult = producer.send(msg);
downloadResourceService.update(new LambdaUpdateWrapper<DownloadResource>()
.eq(DownloadResource::getId,msgInfoId.getId())
.set(DownloadResource::getMessageId,sendResult.getMsgId()));
System.out.printf("%s%n", sendResult);
return sendResult;
}
// 创建下载中心基础数据对象,这个就是那个文件数据表了,这个只是方便创建数据而已
public static DownloadResource createDownloadResource(int belongType, Agent agent, Long id, String createBy) {
DownloadResource downloadResource = new DownloadResource();
downloadResource.setBelongType(belongType);
downloadResource.setSysId(agent.getSysId());
。。。
return downloadResource;
}
/** 发送消息 对外使用 */
public static AjaxResult sendDownloadMsg(IDownloadResourceService downloadResourceService, DownloadResource downloadResource, String downloadServiceName, Object paramObject) throws Exception {
// 插入下载资源表数据
MsgInfo msgInfo = new MsgInfo();
String paramObjectJson = JSONObject.toJSONString(paramObject);
msgInfo.setDownloadServiceName(downloadServiceName);
msgInfo.setParamJson(paramObjectJson);
downloadResource.setParamJson(JSON.toJSONString(msgInfo));
downloadResourceService.save(downloadResource);
msgInfo.setId(downloadResource.getId());
//消息拦截
AjaxResult ajaxResult = intercept(downloadResourceService, downloadResource);
if (Integer.parseInt(ajaxResult.get("code").toString()) == 200){
// 发送MQ消息
String msgInfoJson = JSONObject.toJSONString(msgInfo);
simpleSend(msgInfoJson,downloadResourceService);
}
return ajaxResult;
}
/** 消息拦截 做一些限制 不嫌麻烦这里可以做各种的限制*/
public static AjaxResult intercept(IDownloadResourceService downloadResourceService, DownloadResource downloadResource){
//下载中心表的存放的参数
List<String> paramsList = new ArrayList<>();
int count = downloadResourceService.count(new LambdaQueryWrapper<DownloadResource>()
.eq(DownloadResource::getAgentId, downloadResource.getAgentId())
.eq(DownloadResource::getSysId, downloadResource.getSysId())
.eq(DownloadResource::getFileType, downloadResource.getFileType())
//未完成和已完成需要进行判断
.eq(DownloadResource::getStatus, 0)
//取时间为今天的数据
.apply("date_format(create_time,'%Y-%m-%d') = {0}", DateUtil.today())
);
//未完成超过十条直接返回失败
if(count>=10){
return AjaxResult.error("当前代理商未完成的下载数量不能超过10条");
}
return AjaxResult.success("导出成功,请至下载中心进行下载至本地");
}
}
监听以及分发【主要】
@Component
public class DownLoadBusinessBase {
... 精简的一些代码了
private static String NAMW_SERVER; // 名称服务地址
private static String PRODUCER_NAME; // 生产者注册名称
private static String LISTENER_TOPIC; //监听 的topic 名称
public void run() throws Exception{
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_NAME);
consumer.setNamesrvAddr(NAMW_SERVER);
consumer.subscribe(LISTENER_TOPIC, "*");
// 现在消息监听处理的线程池配置
consumer.setConsumeThreadMax(1);
consumer.setConsumeThreadMin(1);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@SneakyThrows
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
// 这里就是监听的方法了,下面是具体怎么处理消息的
System.out.println("=================msgs.size:"+msgs.size());
System.out.printf("%s Receive New Messages: %s %n %n", Thread.currentThread().getName(), msgs,context);
Long downloadId = null;
try {
// 这里需要分发的操作 。。。demo 当做一条数据处理
String body = new String(msgs.get(0).getBody());
MsgInfo msgInfo = JSONObject.parseObject(body, MsgInfo.class);
downloadId = msgInfo.getId();
// 判断下载业务是否为待处理 并更新为处理中
DownloadResourceServiceImpl downloadResourceService= SpringUtils.getBean("downloadResourceServiceImpl");
DownloadResource downloadResource = downloadResourceService.getOne(new LambdaQueryWrapper<DownloadResource>().eq(DownloadResource::getId,msgInfo.getId()),false);
if (downloadResource==null || !DownloadResource.Constants.STATUS_WAIT.equals(downloadResource.getStatus()) ){
// 不进行处理
}else{
downloadResourceService.lambdaUpdate().eq(DownloadResource::getId, downloadResource.getId())
.set(DownloadResource::getStatus, DownloadResource.Constants.STATUS_PROCESSING)
.update();
// 以下4行就是简单的分发了,获取到名称,调用spring管理的具体业务处理服务的Bean (~ ̄▽ ̄)~
// 这里要求名称与业务处理名一致
String serviceName = msgInfo.getDownloadServiceName();
serviceName = serviceName.substring(0, 1).toLowerCase() + serviceName.substring(1);
BaseBusiness business= SpringUtils.getBean(serviceName);
business.download(msgInfo.getParamJson(),msgInfo.getId());
}
}catch (Exception e){ // 异常处理
if (downloadId!=null){
DownloadResourceServiceImpl downloadResourceService= SpringUtils.getBean("downloadResourceServiceImpl");
downloadResourceService.lambdaUpdate().eq(DownloadResource::getId, downloadId)
.set(DownloadResource::getStatus, DownloadResource.Constants.STATUS_PROCESSING_FAIL)
.set(DownloadResource::getRemarks,"下载数据接受处理异常:"+e.getMessage())
.update();
}
}finally {
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}
});
consumer.start();
System.out.println("Consumer Started.%n");
System.out.println("~ (~ ̄▽ ̄)~ 。。。下载队列监听中。。。。。。。 ~ (~ ̄▽ ̄)~ ");
}
}
// 这一行代码,需要在项目启动的时候执行,在项目Application.main加上
new DownLoadBusinessBase().run();
业务的定义类处理
// 这个就只是方便前面的获取Bean与调用,所以定义了接口与基础实现
public interface BaseBusinessInterface {
void download(String param,Long downloadId) throws IOException;
}
public class BaseBusiness implements BaseBusinessInterface{
@Override
public void download(String param,Long downloadId) throws IOException {
}
}
@Slf4j
@Service
@Component
public class MerchantBusiness extends BaseBusiness{
// 业务处理中需要用到,因为这里已经是在spring管理中了,所以随便注入,能用的
@Autowired
private IDownloadResourceService downloadResourceService;
@Override
public void download(String param,Long downloadId) throws RuntimeException {
// 这里就可以处理下载查询等等的操作了
// 然后吧处理好的文件压缩保存到文件服务就行了
}
}
其他内容
文件下载
- 这里使用远程文件服务的话,就是直接调用接口就可以了。
- 使用本地的话,可以使用nginx做静态文件资源映射 就可以了。
- 如果这里的业务服务与Nginx服务,不在同一个服务器里面,那做个Linux下的远程文件挂载就可以了。