Merge branch 'master' into feature/REQ-2106

# Conflicts:
#	nanopart-server/src/main/java/cn/axzo/nanopart/NanopartApplication.java
#	pom.xml
This commit is contained in:
chenwenjian 2024-04-10 15:44:36 +08:00
commit c2ba6dde14
28 changed files with 2573 additions and 0 deletions

View File

@ -108,6 +108,33 @@
<artifactId>black-list-service</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.axzo.nanopart</groupId>
<artifactId>op-api</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.axzo.nanopart</groupId>
<artifactId>op-server</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.axzo.im.center</groupId>
<artifactId>im-center-api</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.basics</groupId>
<artifactId>basics-profiles-api</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework.rocketmq</groupId>
<artifactId>axzo-common-rocketmq</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -1,5 +1,6 @@
package cn.axzo.nanopart;
import cn.axzo.nanopart.config.RocketMQEventConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
@ -7,6 +8,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
@Slf4j
@ -16,6 +18,7 @@ import org.springframework.core.env.Environment;
"cn.axzo.nanopart.api"
})
@EnableAspectJAutoProxy()
@Import(RocketMQEventConfiguration.class)
public class NanopartApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(NanopartApplication.class, args);

View File

@ -0,0 +1,138 @@
package cn.axzo.nanopart.config;
import cn.azxo.framework.common.constatns.Constants;
import cn.hutool.core.util.ReflectUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Target.HardCodedTarget;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Objects;
/**
* @author liuyang
* @date 2020/10/24
**/
@Component
@Slf4j
@Profile({"dev", "test", "local", "pre"})
public class FeignConfig implements RequestInterceptor, EnvironmentAware {
private Environment environment;
@Value("${maokaiEnvUrl:http://dev-app.axzo.cn/maokai}")
private String maokaiEnvUrl;
@Value("${apolloEnvUrl:http://dev-app.axzo.cn/apollo}")
private String apolloEnvUrl;
@Value("${mnsEnvUrl:http://dev-app.axzo.cn/mns}")
private String mnsEnvUrl;
@Value("${pudgeEnvUrl:http://dev-app.axzo.cn/pudge}")
private String pudgeEnvUrl;
@Value("${outmanEnvUrl:http://dev-app.axzo.cn/outman}")
private String outmanEnvUrl;
@Value("${thronesEnvUrl:http://dev-app.axzo.cn/thrones}")
private String thronesEnvUrl;
@Value("${workspaceEnvUrl:http://dev-app.axzo.cn/workspace}")
private String workspaceEnvUrl;
@Value("${thirdPartUrl:http://dev-app.axzo.cn/thirdParty}")
private String thirdPartUrl;
@Value("${nanopartUrl:http://dev-app.axzo.cn/nanopart}")
private String nanopartUrl;
@Value("${tyrEnvUrl:http://dev-app.axzo.cn/tyr}")
private String tyrEnvUrl;
@Value("${plutoEnvUrl:http://dev-app.axzo.cn/pluto}")
private String plutoUrl;
@Value("${sennaEnvUrl:http://dev-app.axzo.cn/senna}")
private String sennaEnvUrl;
@Value("${workflowEngineEnvUrl:http://dev-app.axzo.cn/workflow-engine}")
private String workflowEngineEnvUrl;
@Value("${msgCenterEngineEnvUrl:http://dev-app.axzo.cn/msg-center}")
private String msgCenterEngineEnvUrl;
@Value("${imCenterEnvUrl:http://dev-app.axzo.cn/im-center}")
private String imCenterEnvUrl;
@Value("${msgCenterEngineEnvUrl:http://dev-app.axzo.cn/braum}")
private String braumEnvUrl;
@Value("${labourEnvUrl:http://dev-app.axzo.cn/labour}")
private String labourEvnUrl;
@Value("${epicEnvUrl:http://dev-app.axzo.cn/epic}")
private String epicEnvUrl;
@Value("${karmaEnvUrl:http://dev-app.axzo.cn/karma}")
private String karmaEnvUrl;
@Value("${dataCollectionUrl:http://dev-app.axzo.cn/dataCollection}")
private String dataCollectionUrl;
@Value("${attendanceUrl:http://dev-app.axzo.cn/attendance}")
private String attendanceApi;
private static String POD_NAMESPACE;
static {
Map<String, String> env = System.getenv();
if (env != null) {
POD_NAMESPACE = env.get("MY_POD_NAMESPACE");
}
log.info("init FeignConfig, POD_NAMESPACE value is {}", POD_NAMESPACE);
}
@SneakyThrows
@Override
public void apply(RequestTemplate requestTemplate) {
if (POD_NAMESPACE == null) {
HardCodedTarget target = (HardCodedTarget) requestTemplate.feignTarget();
String url = requestTemplate.feignTarget().url();
// 如需修改微服务地址,建议通过外部化参数来调整
url = url.replace("http://maokai:8080", maokaiEnvUrl);
url = url.replace("http://apollo:11000", apolloEnvUrl);
url = url.replace("http://mns:8989", mnsEnvUrl);
url = url.replace("http://pudge:10099", pudgeEnvUrl);
url = url.replace("http://outman:8989", outmanEnvUrl);
url = url.replace("http://thrones", thronesEnvUrl);
url = url.replace("http://workspace:8080", workspaceEnvUrl);
url = url.replace("http://third-party:11000", thirdPartUrl);
url = url.replace("http://nanopart:8080", nanopartUrl);
url = url.replace("http://tyr:8080", tyrEnvUrl);
url = url.replace("http://pluto:8080", plutoUrl);
url = url.replace("http://senna:8080", sennaEnvUrl);
url = url.replace("http://workflow-engine:8080", workflowEngineEnvUrl);
url = url.replace("http://msg-center:8080", msgCenterEngineEnvUrl);
url = url.replace("http://im-center:8080", imCenterEnvUrl);
url = url.replace("http://braum:8080", braumEnvUrl);
url = url.replace("http://labour:8080", labourEvnUrl);
url = url.replace("http://epic:8080", epicEnvUrl);
url = url.replace("http://karma:8080", karmaEnvUrl);
url = url.replace("http://data-collection:21200", dataCollectionUrl);
url = url.replace("http://attendance:8080", attendanceApi);
String profile = environment.getProperty("spring.profiles.active");
if(Objects.equals(profile, "test") && url.contains("dev-app.axzo.cn")) {
url = url.replace("dev-app", "test-api");
}
if(Objects.equals(profile, "pre") && url.contains("dev-app.axzo.cn")) {
url = url.replace("dev-app", "pre-api");
}
if(Objects.equals(profile, "live") && url.contains("dev-app.axzo.cn")) {
url = url.replace("dev-app", "live-api");
}
requestTemplate.target(url);
Field field = ReflectUtil.getField(target.getClass(), "url");
field.setAccessible(true);
field.set(target, url);
}
requestTemplate.header(Constants.CTX_LOG_ID_MDC, MDC.get(Constants.CTX_LOG_ID_MDC));
requestTemplate.header("X-SERVER-NAME", environment.getProperty("spring.application.name"));
}
/**
* Set the {@code Environment} that this component runs in.
*
* @param environment
*/
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}

View File

@ -0,0 +1,90 @@
package cn.axzo.nanopart.config;
import cn.axzo.framework.rocketmq.BaseListener;
import cn.axzo.framework.rocketmq.DefaultEventConsumer;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandlerRepository;
import cn.axzo.framework.rocketmq.EventProducer;
import cn.axzo.framework.rocketmq.RocketMQEventProducer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.function.Consumer;
/**
* @Author: liyong.tian
* @Date: 2023/7/25 14:43
* @Description:
*/
@Slf4j
public class RocketMQEventConfiguration {
@Value("${spring.application.name}")
private String appName;
@Value("${topic}")
private String topic;
@Bean
public RocketMQTemplate ser(){
return new RocketMQTemplate();
}
@Bean
EventProducer eventProducer(RocketMQTemplate rocketMQTemplate) {
return new RocketMQEventProducer(rocketMQTemplate,
"nanopart",
appName,
EventProducer.Context.<RocketMQEventProducer.RocketMQMessageMeta>builder()
.meta(RocketMQEventProducer.RocketMQMessageMeta.builder()
.topic(topic)
.build())
.build(),
null
);
}
@Bean
EventConsumer eventConsumer(EventHandlerRepository eventHandlerRepository) {
Consumer<EventConsumer.EventWrapper> callback = (eventWrapper) -> {
if (eventWrapper.isHandled()) {
// 只收集被App真正消费的消息.
//String topic = (String) eventWrapper.getExt().get(EVENT_TOPIC_KEY);
}
};
return new DefaultEventConsumer(appName, eventHandlerRepository, callback);
}
@Slf4j
@Component
@RocketMQMessageListener(topic = "topic_im_center_${spring.profiles.active}",
consumerGroup = "GID_topic_im_center_${spring.application.name}_${spring.profiles.active}",
consumeMode = ConsumeMode.ORDERLY,
nameServer = "${rocketmq.name-server}"
)
public static class DefaultListener extends BaseListener implements RocketMQListener<MessageExt> {
@Autowired
private EventConsumer eventConsumer;
@Override
public void onMessage(MessageExt message) {
super.onEvent(message, eventConsumer);
}
}
@Bean
EventHandlerRepository eventHandlerRepository() {
return new EventHandlerRepository((ex, logText) -> {
log.warn("MQ, handle warning {}", logText, ex);
});
}
}

View File

@ -0,0 +1,69 @@
package cn.axzo.nanopart.config;
import cn.azxo.framework.common.logger.JobLoggerTemplate;
import cn.azxo.framework.common.service.JobParamResolver;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* xxl-job config
*
* @author xuxueli 2017-04-28
*/
@Configuration
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
// @Value("http://dev-xxl-job.axzo.cn/xxl-job-admin")
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appName;
@Value("")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
// @Value("${xxl.job.accessToken}")
@Value("")
private String accessToken;
@Value("")
private String logPath;
@Value("-1")
private int logRetentionDays;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appName);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
@Bean("jobParamResolver")
public JobParamResolver jobParamResolver() {
return new JobParamResolver();
}
@Bean("jobLoggerTemplate")
public JobLoggerTemplate jobLoggerTemplate() {
return new JobLoggerTemplate();
}
}

View File

@ -1,6 +1,7 @@
package cn.axzo.nanopart.config.exception;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.pokonyan.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.util.CollectionUtils;
@ -41,6 +42,12 @@ public class ExceptionAdviceHandler {
return ApiResult.err(e.getMessage());
}
@ExceptionHandler(BusinessException.class)
public ApiResult<Void> businessExceptionHandler(BusinessException e){
log.warn("业务异常", e);
return ApiResult.err(e.getErrorMsg());
}
@ExceptionHandler(BindException.class)
public ApiResult<Void> bindExceptionHandler(BindException e) {
log.warn("业务异常", e);

1
op/README.md Normal file
View File

@ -0,0 +1 @@
# 项目介绍

2
op/RELEASE.md Normal file
View File

@ -0,0 +1,2 @@
# 发布记录

38
op/op-api/pom.xml Normal file
View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>op</artifactId>
<groupId>cn.axzo.nanopart</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>op-api</artifactId>
<packaging>jar</packaging>
<name>op-api</name>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-common-domain</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.basics</groupId>
<artifactId>basics-common</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.basics</groupId>
<artifactId>basics-profiles-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,342 @@
package cn.axzo.op.api;
import cn.axzo.framework.domain.web.result.ApiPageResult;
import cn.axzo.framework.domain.web.result.ApiResult;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.List;
@FeignClient(name = "nanopart", url = "${axzo.service.nanopart:http://nanopart:8080}")
public interface OpMessageConfigApi {
@PostMapping("/api/op-message-config/page")
ApiPageResult<OpMessageConfigResp> page(@RequestBody PageOpMessageConfigReq req);
@PostMapping("/api/op-message-config/create")
ApiResult<OpMessageConfigBaseResp> create(@Valid @RequestBody CreateOpMessageConfigParam req);
@PostMapping("/api/op-message-config/delete")
ApiResult<Void> delete(@Valid @RequestBody DeleteOpMessageConfigParam param);
@PostMapping("/api/op-message-config/update")
ApiResult<OpMessageConfigBaseResp> update(@Valid @RequestBody UpdateOpMessageConfigParam param);
@PostMapping("/api/op-message-config/draft/save")
ApiResult<OpMessageConfigBaseResp> saveDraft(@Valid @RequestBody SaveDraftOpMessageConfigParam req);
@PostMapping("/api/op-message-config/draft/submit")
ApiResult<OpMessageConfigBaseResp> submitDraft(@Valid @RequestBody SaveDraftOpMessageConfigParam req);
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
class UpdateOpMessageConfigParam {
@NotNull(message = "id不能为空")
private Long id;
private String name;
private String sendImAccount;
private JSONObject receiveData;
private ActionEnum action;
private String title;
private String content;
private String contentType;
private String coverImg;
private JSONObject jumpData;
private JSONObject pushData;
private JSONObject ext;
private Date planStartTime;
@NotNull(message = "operatePersonId不能为空")
private Long operatePersonId;
}
@Getter
@AllArgsConstructor
enum ActionEnum {
SUBMIT_DRAFT
}
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
class DeleteOpMessageConfigParam {
@NotNull(message = "id不能为空")
private Long id;
@NotNull(message = "operatePersonId不能为空")
private Long operatePersonId;
}
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
class CreateOpMessageConfigParam {
private String name;
private String sendImAccount;
private JSONObject receiveData;
private Status status;
private String title;
private String content;
private String contentType;
private String coverImg;
private JSONObject jumpData;
private JSONObject pushData;
private JSONObject ext;
private Date planStartTime;
@NotNull(message = "operatePersonId不能为空")
private Long operatePersonId;
}
@SuperBuilder
@Data
@NoArgsConstructor
@AllArgsConstructor
class ListOpMessageConfigReq {
private List<Long> ids;
private Date planStartTimeLE;
private List<String> statuses;
private String nameLike;
private String sendImAccount;
private String sendImAccountLike;
private String contentType;
private List<String> receivePlatforms;
/**
* 返回发送的信息
* IM机器人账号返回头像名字等信息
* 账号有机器人和系统人员信息查询的接口不同
*/
private boolean needSendUserInfo;
/**
* 返回创建人的信息
*/
private boolean needCreatePersonInfo;
/**
* 返回提交审核人的信息
*/
private boolean needSubmitPersonInfo;
}
@SuperBuilder
@Data
@NoArgsConstructor
@AllArgsConstructor
class PageOpMessageConfigReq extends ListOpMessageConfigReq {
Integer page;
Integer pageSize;
/**
* 排序使用示例createTime__DESC
*/
List<String> sort;
}
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
class OpMessageConfigResp extends OpMessageConfigBaseResp {
private SendUserInfo sendUserInfo;
private PersonProfileDto createPersonProfile;
private PersonProfileDto submitPersonProfile;
}
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
class PersonProfileDto {
private String phone;
private String realName;
}
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
class OpMessageConfigBaseResp {
private Long id;
/**
* im-center服务im_message_task的id
*/
private Long imMessageTaskId;
/**
* 消息名字
*/
private String name;
/**
* 发送者的三方平台账号id
*/
private String sendImAccount;
/**
* 消息接收配置
*/
private JSONObject receiveData;
private String status;
private String title;
private String content;
private String contentType;
private String coverImg;
private JSONObject jumpData;
private JSONObject pushData;
private JSONObject ext;
private Date planStartTime;
private Date startedTime;
private Date finishedTime;
private Integer isDelete;
private Date createAt;
private Date updateAt;
private Long createPersonId;
/**
* 删除人id
*/
private Long deletePersonId;
/**
* 更新人id
*/
private Long updatePersonId;
private Date submitTime;
}
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
class SendUserInfo {
/**
* im账号
*/
private String imAccount;
/**
* 昵称
*/
private String nickName;
/**
* 头像
*/
private String headImageUrl;
}
enum Status {
DRAFT,
PENDING,
RUNNING,
COMPLETED,
;
}
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
class SaveDraftOpMessageConfigParam {
private Long id;
private String name;
private String sendImAccount;
private JSONObject receiveData;
private String title;
private String content;
private String contentType;
private String coverImg;
private JSONObject jumpData;
private JSONObject pushData;
private JSONObject ext;
private Date planStartTime;
@NotNull(message = "operatePersonId不能为空")
private Long operatePersonId;
}
}

View File

@ -0,0 +1,9 @@
package cn.axzo.op.api.config;
import cn.axzo.op.api.constant.NanopartConstant;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients(NanopartConstant.BASIC_FEIGN_PACKAGE)
public class NanopartApiAutoConfiguration {
}

View File

@ -0,0 +1,13 @@
package cn.axzo.op.api.constant;
/**
* @author chenwenjian
* @date 2023/8/14 9:38
* @description
* @modifiedBy
* @version: 1.0
*/
public class NanopartConstant {
public static final String BASIC_FEIGN_PACKAGE = "cn.axzo";
}

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.axzo.nanopart.api.config.NanopartApiAutoConfiguration

99
op/op-server/pom.xml Normal file
View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.axzo.nanopart</groupId>
<artifactId>op</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>op-server</artifactId>
<packaging>jar</packaging>
<name>op-server</name>
<dependencies>
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-web-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-consumer-spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-processor-spring-boot-starter</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-mybatisplus-spring-boot-starter</artifactId>
</dependency>
<!-- swagger-yapi -->
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-swagger-yapi-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.basics</groupId>
<artifactId>basics-common</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-logger-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.nanopart</groupId>
<artifactId>op-api</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.axzo.pokonyan</groupId>
<artifactId>pokonyan</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.im.center</groupId>
<artifactId>im-center-api</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.basics</groupId>
<artifactId>basics-profiles-api</artifactId>
</dependency>
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework.rocketmq</groupId>
<artifactId>axzo-common-rocketmq</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.msgcenter</groupId>
<artifactId>msg-center-api</artifactId>
<version>1.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,20 @@
package cn.axzo.nanopart.server.config;
import cn.axzo.pokonyan.exception.ResultCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum BizResultCode implements ResultCode {
OP_MESSAGE_CONFIG_NOT_FOUND("100", "运营消息配置不存在"),
DELETE_OP_MESSAGE_CONFIG_STATUS_ERROR("101", "删除运营消息配置失败,状态异常"),
UPDATE_OP_MESSAGE_CONFIG_STATUS_ERROR("102", "更新运营消息配置失败,状态异常"),
OP_MESSAGE_CONFIG_ID_NOT_NULL("103", "id不能为空"),
OP_MESSAGE_CONFIG_STATUS_ERROR("104", "操作运营消息配置失败,状态异常"),;
private String errorCode;
private String errorMessage;
}

View File

@ -0,0 +1,152 @@
package cn.axzo.nanopart.server.controller;
import cn.axzo.framework.domain.web.result.ApiPageResult;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.nanopart.server.domain.OpMessageConfig;
import cn.axzo.nanopart.server.service.OpMessageConfigService;
import cn.axzo.op.api.OpMessageConfigApi;
import cn.axzo.pokonyan.exception.Aassert;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import static cn.axzo.nanopart.server.config.BizResultCode.OP_MESSAGE_CONFIG_ID_NOT_NULL;
@RestController
public class OpMessageConfigController implements OpMessageConfigApi {
@Autowired
private OpMessageConfigService opMessageConfigService;
@Override
public ApiPageResult<OpMessageConfigResp> page(PageOpMessageConfigReq param) {
OpMessageConfigService.PageOpMessageConfigParam pageOpMessageConfigParam = OpMessageConfigService.PageOpMessageConfigParam.builder().build();
BeanUtils.copyProperties(param, pageOpMessageConfigParam);
Page<OpMessageConfigService.OpMessageConfigDTO> page = opMessageConfigService.page(pageOpMessageConfigParam);
return ApiPageResult.ok(page.convert(record -> {
OpMessageConfigResp opMessageConfigResp = OpMessageConfigResp.builder().build();
BeanUtils.copyProperties(record, opMessageConfigResp);
opMessageConfigResp.setStatus(record.getStatus().name());
if (record.getSendUserInfo() != null) {
SendUserInfo sendUserInfo = SendUserInfo.builder().build();
BeanUtils.copyProperties(record.getSendUserInfo(), sendUserInfo);
opMessageConfigResp.setSendUserInfo(sendUserInfo);
}
if (record.getCreatePersonProfile() != null) {
PersonProfileDto createPersonProfile = PersonProfileDto.builder().build();
BeanUtils.copyProperties(record.getCreatePersonProfile(), createPersonProfile);
opMessageConfigResp.setCreatePersonProfile(createPersonProfile);
}
if (record.getSubmitPersonProfile() != null) {
PersonProfileDto submitPersonProfile = PersonProfileDto.builder().build();
BeanUtils.copyProperties(record.getSubmitPersonProfile(), submitPersonProfile);
opMessageConfigResp.setSubmitPersonProfile(submitPersonProfile);
}
return opMessageConfigResp;
}));
}
@Override
public ApiResult<OpMessageConfigBaseResp> create(CreateOpMessageConfigParam param) {
OpMessageConfigService.CreateOpMessageConfigParam createOpMessageConfigParam = OpMessageConfigService.CreateOpMessageConfigParam.builder().build();
BeanUtils.copyProperties(param, createOpMessageConfigParam);
// 默认状态为待执行创建人就是提交审核人
createOpMessageConfigParam.setSubmitPersonId(param.getOperatePersonId());
createOpMessageConfigParam.setCreatePersonId(param.getOperatePersonId());
createOpMessageConfigParam.setStatus(OpMessageConfig.Status.PENDING);
createOpMessageConfigParam.setPlanStartTime(resolvePlanStartTime(param.getReceiveData(), param.getPlanStartTime()));
OpMessageConfig opMessageConfig = opMessageConfigService.create(createOpMessageConfigParam);
return ApiResult.ok(to(opMessageConfig));
}
private Date resolvePlanStartTime(JSONObject receiveData, Date planStartTime) {
if (planStartTime != null) {
return planStartTime;
}
if (receiveData == null) {
return null;
}
if (receiveData.toJavaObject(OpMessageConfig.ReceiveData.class).isNowStrategy()) {
return new Date();
}
return null;
}
@Override
public ApiResult<Void> delete(DeleteOpMessageConfigParam param) {
OpMessageConfigService.DeleteOpMessageConfigParam deleteOpMessageConfigParam = OpMessageConfigService.DeleteOpMessageConfigParam.builder()
.id(param.getId())
.deletePersonId(param.getOperatePersonId())
.build();
opMessageConfigService.delete(deleteOpMessageConfigParam);
return ApiResult.ok();
}
@Override
public ApiResult<OpMessageConfigBaseResp> update(UpdateOpMessageConfigParam param) {
OpMessageConfigService.UpdateOpMessageConfigParam updateOpMessageConfigParam = OpMessageConfigService.UpdateOpMessageConfigParam.builder().build();
BeanUtils.copyProperties(param, updateOpMessageConfigParam);
updateOpMessageConfigParam.setUpdatePersonId(param.getOperatePersonId());
updateOpMessageConfigParam.setPlanStartTime(resolvePlanStartTime(param.getReceiveData(), param.getPlanStartTime()));
OpMessageConfig opMessageConfig = opMessageConfigService.update(updateOpMessageConfigParam);
return ApiResult.ok(to(opMessageConfig));
}
@Override
public ApiResult<OpMessageConfigBaseResp> saveDraft(SaveDraftOpMessageConfigParam req) {
if (req.getId() == null) {
OpMessageConfigService.CreateOpMessageConfigParam createOpMessageConfigParam = OpMessageConfigService.CreateOpMessageConfigParam.builder().build();
BeanUtils.copyProperties(req, createOpMessageConfigParam);
createOpMessageConfigParam.setStatus(OpMessageConfig.Status.DRAFT);
createOpMessageConfigParam.setCreatePersonId(req.getOperatePersonId());
OpMessageConfig opMessageConfig = opMessageConfigService.create(createOpMessageConfigParam);
return ApiResult.ok(to(opMessageConfig));
}
OpMessageConfigService.UpdateOpMessageConfigParam updateOpMessageConfigParam = OpMessageConfigService.UpdateOpMessageConfigParam.builder().build();
BeanUtils.copyProperties(req, updateOpMessageConfigParam);
updateOpMessageConfigParam.setUpdatePersonId(req.getOperatePersonId());
updateOpMessageConfigParam.setPlanStartTime(resolvePlanStartTime(req.getReceiveData(), req.getPlanStartTime()));
OpMessageConfig opMessageConfig = opMessageConfigService.update(updateOpMessageConfigParam);
return ApiResult.ok(to(opMessageConfig));
}
@Override
public ApiResult<OpMessageConfigBaseResp> submitDraft(SaveDraftOpMessageConfigParam req) {
Aassert.notNull(req.getId(), OP_MESSAGE_CONFIG_ID_NOT_NULL);
OpMessageConfigService.UpdateOpMessageConfigParam updateOpMessageConfigParam = OpMessageConfigService.UpdateOpMessageConfigParam.builder().build();
BeanUtils.copyProperties(req, updateOpMessageConfigParam);
updateOpMessageConfigParam.setAction(OpMessageConfig.ActionEnum.SUBMIT_DRAFT);
updateOpMessageConfigParam.setUpdatePersonId(req.getOperatePersonId());
updateOpMessageConfigParam.setSubmitPersonId(req.getOperatePersonId());
updateOpMessageConfigParam.setSubmitTime(new Date());
updateOpMessageConfigParam.setPlanStartTime(resolvePlanStartTime(req.getReceiveData(), req.getPlanStartTime()));
OpMessageConfig opMessageConfig = opMessageConfigService.update(updateOpMessageConfigParam);
return ApiResult.ok(to(opMessageConfig));
}
public OpMessageConfigBaseResp to(OpMessageConfig opMessageConfig) {
OpMessageConfigBaseResp opMessageConfigResp = OpMessageConfigBaseResp.builder().build();;
BeanUtils.copyProperties(opMessageConfig, opMessageConfigResp);
opMessageConfigResp.setStatus(opMessageConfig.getStatus().name());
return opMessageConfigResp;
}
}

View File

@ -0,0 +1,32 @@
package cn.axzo.nanopart.server.controller;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.nanopart.server.event.outer.PushYouMengMessageHandler;
import cn.axzo.nanopart.server.xxljob.SendMessageJob;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/private")
public class PrivateController {
@Autowired
private SendMessageJob sendMessageJob;
@Autowired
private PushYouMengMessageHandler pushYouMengMessageHandler;
@PostMapping("/send-message/job/run")
public Object runSendMessage(@RequestBody SendMessageJob.SendMessageParam param) throws Exception {
return sendMessageJob.execute(JSONObject.toJSONString(param));
}
@PostMapping("/you-meng/push")
public Object pushYouMeng(@RequestBody Event param) throws Exception {
pushYouMengMessageHandler.onEvent(param, null);
return null;
}
}

View File

@ -0,0 +1,312 @@
package cn.axzo.nanopart.server.domain;
import cn.axzo.nanopart.server.config.BizResultCode;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@Data
@SuperBuilder
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "`op_message_config`", autoResultMap = true)
public class OpMessageConfig {
private static final Set<Status> CAN_DELETE_STATUSES = Sets.newHashSet(
Status.DRAFT,
Status.PENDING
);
@TableId(type = IdType.AUTO)
private Long id;
/**
* im-center服务im_message_task的id
*/
@TableField(value = "im_message_task_id")
private Long imMessageTaskId;
/**
* 消息名字
*/
@TableField(value = "name")
private String name;
/**
* 发送者的三方平台账号id
*/
@TableField(value = "send_im_account")
private String sendImAccount;
/**
* 消息接收配置
*/
@TableField(typeHandler = FastjsonTypeHandler.class)
private JSONObject receiveData;
private Status status;
@TableField(value = "title")
private String title;
@TableField(value = "content")
private String content;
@TableField(value = "content_type")
private String contentType;
@TableField(value = "cover_img")
private String coverImg;
@TableField(typeHandler = FastjsonTypeHandler.class)
private JSONObject jumpData;
@TableField(typeHandler = FastjsonTypeHandler.class)
private JSONObject pushData;
@TableField(typeHandler = FastjsonTypeHandler.class)
private JSONObject ext;
@TableField
private Date planStartTime;
@TableField
private Date startedTime;
@TableField
private Date finishedTime;
@TableField
private Integer isDelete;
@TableField
private Date createAt;
@TableField
private Date submitTime;
@TableField
private Date updateAt;
/**
* 创建人id
*/
@TableField
private Long createPersonId;
/**
* 删除人id
*/
@TableField
private Long deletePersonId;
/**
* 更新人id
*/
@TableField
private Long updatePersonId;
/**
* 提交审核人
*/
@TableField
private Long submitPersonId;
public enum Status {
DRAFT,
PENDING,
RUNNING,
COMPLETED,
;
}
public boolean canDelete() {
return CAN_DELETE_STATUSES.contains(this.getStatus());
}
@Getter
@AllArgsConstructor
public enum ActionEnum {
SUBMIT_DRAFT,
RUN,
COMPLETE
;
private static final Table<Status, ActionEnum, Status> STATUS_FLOWS = HashBasedTable.create();
static {
STATUS_FLOWS.put(Status.DRAFT, SUBMIT_DRAFT, Status.PENDING);
STATUS_FLOWS.put(Status.PENDING, RUN, Status.RUNNING);
STATUS_FLOWS.put(Status.RUNNING, COMPLETE, Status.COMPLETED);
}
public Status getNextStatus(Status oldStatus) {
return Optional.ofNullable(STATUS_FLOWS.get(oldStatus, this)).orElseThrow(BizResultCode.OP_MESSAGE_CONFIG_STATUS_ERROR::toException);
}
}
public ReceiveData resolveReceiveData() {
JSONObject receiveData = Optional.ofNullable(this.getReceiveData()).orElseGet(JSONObject::new);
return JSONObject.toJavaObject(receiveData, ReceiveData.class);
}
public JumpData resolveJumpData() {
JSONObject jumpData = Optional.ofNullable(this.getJumpData()).orElseGet(JSONObject::new);
return JSONObject.toJavaObject(jumpData, JumpData.class);
}
public PushData resolvePushData() {
JSONObject pushData = Optional.ofNullable(this.getPushData()).orElseGet(JSONObject::new);
return JSONObject.toJavaObject(pushData, PushData.class);
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ReceiveData {
private List<Person> persons;
/**
* 推送时间策略now(立即发送)specifyTime(指定时间)
*/
private String strategy;
private String fileName;
private String fileKey;
/**
* 接收的类型all全部员工import(导入名单)
*/
private String type;
/**
* 接收渠道平台CMP(B端: APP(管理端)+ CMS)CM(C端APP(工人端))
*/
private List<String> platforms;
public boolean isNowStrategy() {
return Objects.equals("now", this.getStrategy());
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Person {
private Long ouId;
private Long personId;
}
@Getter
@AllArgsConstructor
public enum ReceiveType {
ALL("all", "全部员工"),
IMPORT("import", "导入名单");
private String code;
private String desc;
public static ReceiveType of(String typeCode) {
if (StringUtils.isBlank(typeCode)) {
return null;
}
return Arrays.stream(values())
.filter(e -> Objects.equals(e.getCode(), typeCode))
.findFirst()
.orElse(null);
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class JumpData {
private boolean switchOn;
private List<Platform> platforms;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Platform {
private JumpPlatform platform;
private String url;
}
@Getter
@AllArgsConstructor
public enum JumpPlatform {
PC,
CM_IOS,
CM_ANDROID,
CMP_IOS,
CMP_ANDROID
;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class PushData {
private boolean switchOn;
/**
* 声音文件
*/
private String voiceFile;
private String voiceFileName;
/**
* 提醒方式voice声音vibrate(震动)
*/
private String ability;
/**
* push类型system系统消息op(运营消息)
*/
private String type;
/**
* 声音类型custom自定义system系统
*/
private String voiceType;
}
}

View File

@ -0,0 +1,31 @@
package cn.axzo.nanopart.server.event.outer;
import cn.axzo.framework.rocketmq.Event;
import lombok.Getter;
/**
* @Classname EventTypeEnum
* @Date 2021/2/7 6:05 下午
* @Created by lilong
*/
@Getter
public enum EventTypeEnum {
MESSAGE_HISTORY_UPDATED("message-history", "message-history-updated", "发送记录修改")
;
EventTypeEnum(String model, String name, String desc) {
this.eventCode = Event.EventCode.builder()
.module(model)
.name(name)
.build();
this.model = model;
this.name = name;
this.desc = desc;
}
private String model;
private String name;
private String desc;
private Event.EventCode eventCode;
}

View File

@ -0,0 +1,184 @@
package cn.axzo.nanopart.server.event.outer;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandler;
import cn.axzo.msg.center.api.MessagePushApi;
import cn.axzo.msg.center.api.request.MsgBody4Guest;
import cn.axzo.nanopart.server.domain.OpMessageConfig;
import cn.axzo.nanopart.server.event.payload.MessageHistoryUpdatedPayload;
import cn.axzo.nanopart.server.service.OpMessageConfigService;
import cn.axzo.nanopart.server.xxljob.SendMessageJob;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.axzo.nanopart.server.event.payload.MessageHistoryUpdatedPayload.PLATFORM_ROUTER_TYPE;
@Slf4j
@Component
public class PushYouMengMessageHandler implements EventHandler, InitializingBean {
@Autowired
private EventConsumer eventConsumer;
@Autowired
private OpMessageConfigService opMessageConfigService;
@Autowired
private MessagePushApi messagePushApi;
/**
* 按照这个顺序解析routerType
*/
private static final List<String> ROUTER_TYPE_SORTED = Lists.newArrayList(
MessageHistoryUpdatedPayload.IOS_PLATFORM,
MessageHistoryUpdatedPayload.ANDROID_PLATFORM,
MessageHistoryUpdatedPayload.WEBVIEW_PLATFORM,
MessageHistoryUpdatedPayload.MINI_PROGRAM_PLATFORM,
MessageHistoryUpdatedPayload.WECHAT_MINI_PROGRAM_PLATFORM);
/**
* 按照这个顺序解析IOS的url
*/
private static final List<String> IOS_URL_SORTED = Lists.newArrayList(
MessageHistoryUpdatedPayload.IOS_PLATFORM,
MessageHistoryUpdatedPayload.WEBVIEW_PLATFORM,
MessageHistoryUpdatedPayload.MINI_PROGRAM_PLATFORM,
MessageHistoryUpdatedPayload.WECHAT_MINI_PROGRAM_PLATFORM);
/**
* 按照这个顺序解析ANDROID的url
*/
private static final List<String> ANDROID_URL_SORTED = Lists.newArrayList(
MessageHistoryUpdatedPayload.ANDROID_PLATFORM,
MessageHistoryUpdatedPayload.WEBVIEW_PLATFORM,
MessageHistoryUpdatedPayload.MINI_PROGRAM_PLATFORM,
MessageHistoryUpdatedPayload.WECHAT_MINI_PROGRAM_PLATFORM);
@Override
public void onEvent(Event event, EventConsumer.Context context) {
log.info("begin push-handler rocketmq event: {}", event);
MessageHistoryUpdatedPayload payload = event.normalizedData(MessageHistoryUpdatedPayload.class);
if (Objects.isNull(payload) || StringUtils.isBlank(payload.getNewMessageHistory().getBizId())) {
return;
}
if (!payload.getNewMessageHistory().getBizId().startsWith(SendMessageJob.BIZ_ID_PREFIX)) {
log.info("push-handler 非op-message的消息");
return;
}
if (!payload.isSuccess()) {
log.info("push-handler is not success");
return;
}
if (payload.getNewMessageHistory().getImMessageTaskId() == null) {
log.info("push-handler imMessageTaskId is null");
return;
}
Optional<OpMessageConfigService.OpMessageConfigDTO> opMessageConfigDTO = opMessageConfigService.page(OpMessageConfigService.PageOpMessageConfigParam.builder()
.imMessageTaskId(payload.getNewMessageHistory().getImMessageTaskId())
.build())
.getRecords()
.stream()
.findFirst();
if (!opMessageConfigDTO.isPresent()) {
log.info("push-handler imMessageTaskId is not found,{}", payload.getNewMessageHistory().getImMessageTaskId());
return;
}
if (opMessageConfigDTO.get().getPushData() == null) {
log.info("push-handler, opMessageConfig:{},未配置push信息", opMessageConfigDTO.get().getId());
return;
}
OpMessageConfig.PushData pushData = opMessageConfigDTO.get().getPushData().toJavaObject(OpMessageConfig.PushData.class);
if (BooleanUtils.isNotTrue(pushData.isSwitchOn())) {
log.info("push-handler, opMessageConfig:{},push开关未打开", opMessageConfigDTO.get().getId());
return;
}
MessageHistoryUpdatedPayload.MessageHistory newMessageHistory = payload.getNewMessageHistory();
MessageHistoryUpdatedPayload.MessageBody messageBody = newMessageHistory.resolveMessageBody();
messagePushApi.sendPushMessage(MsgBody4Guest.builder()
.ty(0)
.f("0")
.appClient(newMessageHistory.getAppType())
.m(messageBody.getMsgContent())
.m3(newMessageHistory.getReceivePersonId())
.m2(resolveM2(messageBody, pushData))
// 为了兼容老版本保证老版本的工人端能收到push所以新的push都alias都是person
.t("not_identity" + newMessageHistory.getReceivePersonId())
.ouId(newMessageHistory.getReceiveOuId())
.pushType(pushData.getType())
.build());
log.info("end push-handler rocketmq event: {}", event);
}
private String resolveM2(MessageHistoryUpdatedPayload.MessageBody messageBody,
OpMessageConfig.PushData pushData) {
JSONObject jsonObject = new JSONObject()
.fluentPut("t", messageBody.getMsgHeader())
.fluentPut("type", 0);
if (StringUtils.isNotBlank(pushData.getVoiceFile())) {
jsonObject.fluentPut("audio", pushData.getVoiceFile());
}
MessageHistoryUpdatedPayload.CardDetailButton cardDetailButton = messageBody.resolveMsgBody().getCardDetailButton();
if (cardDetailButton != null && CollectionUtils.isNotEmpty(cardDetailButton.getActionPaths())) {
resolveRouter(jsonObject, cardDetailButton.getActionPaths());
}
return jsonObject.toJSONString();
}
/**
*
* @param jsonObject
* @param actionPaths
*/
private void resolveRouter(JSONObject jsonObject, List<MessageHistoryUpdatedPayload.ActionPath> actionPaths) {
Map<String, MessageHistoryUpdatedPayload.ActionPath> actionPathMap = actionPaths.stream()
.collect(Collectors.toMap(MessageHistoryUpdatedPayload.ActionPath::getPlatform, Function.identity()));
Optional<MessageHistoryUpdatedPayload.ActionPath> routerType = ROUTER_TYPE_SORTED.stream()
.map(actionPathMap::get)
.filter(Objects::nonNull)
.findFirst();
routerType.ifPresent(actionPath -> jsonObject.fluentPut("rt", PLATFORM_ROUTER_TYPE.get(actionPath.getPlatform())));
Optional<MessageHistoryUpdatedPayload.ActionPath> ios = IOS_URL_SORTED.stream()
.map(actionPathMap::get)
.filter(Objects::nonNull)
.findFirst();
// IOS的跳转URL
ios.ifPresent(actionPath -> jsonObject.fluentPut("ir", actionPath.getUrl()));
Optional<MessageHistoryUpdatedPayload.ActionPath> android = ANDROID_URL_SORTED.stream()
.map(actionPathMap::get)
.filter(Objects::nonNull)
.findFirst();
// ANDROID的跳转URL
android.ifPresent(actionPath -> jsonObject.fluentPut("ar", actionPath.getUrl()));
}
@Override
public void afterPropertiesSet() throws Exception {
eventConsumer.registerHandler(EventTypeEnum.MESSAGE_HISTORY_UPDATED.getEventCode(), this);
}
}

View File

@ -0,0 +1,176 @@
package cn.axzo.nanopart.server.event.payload;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageHistoryUpdatedPayload implements Serializable {
public static final String IOS_PLATFORM = "IOS";
public static final String ANDROID_PLATFORM = "ANDROID";
public static final String WEBVIEW_PLATFORM = "WEBVIEW";
public static final String MINI_PROGRAM_PLATFORM = "MINI_PROGRAM";
public static final String WECHAT_MINI_PROGRAM_PLATFORM = "WECHAT_MINI_PROGRAM";
public static final Map<String, Integer> PLATFORM_ROUTER_TYPE = Maps.newHashMap();
static {
PLATFORM_ROUTER_TYPE.put(IOS_PLATFORM, 2);
PLATFORM_ROUTER_TYPE.put(ANDROID_PLATFORM, 2);
PLATFORM_ROUTER_TYPE.put(WEBVIEW_PLATFORM, 3);
PLATFORM_ROUTER_TYPE.put(MINI_PROGRAM_PLATFORM, 1);
PLATFORM_ROUTER_TYPE.put(WECHAT_MINI_PROGRAM_PLATFORM, 5);
}
private MessageHistory newMessageHistory;
private MessageHistory oldMessageHistory;
public boolean isSuccess() {
return !Objects.equals(newMessageHistory.getStatus(), oldMessageHistory.getStatus())
&& Objects.equals(newMessageHistory.getStatus(), MessageHistory.Status.SUCCEED.name());
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class MessageHistory implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
/**
* 上游业务请求ID
*/
private String bizId;
/**
* 普通用户通过appType包装
* 包装以后进行账户注册
*/
private String messageId;
/**
* 发送者IM账户
*/
private String fromAccount;
/**
* 发送者IM账户
*/
private String toAccount;
/**
* 终端类型
*
* @see AppTypeEnum
*/
private String appType;
/**
* channel 网易云信
*/
private String channel;
private String messageBody;
private String result;
private Long imMessageTaskId;
private String receivePersonId;
private Long receiveOuId;
private String status;
private Integer isDelete;
private Date createAt;
private Date updateAt;
public enum Status {
PENDING,
SUCCEED,
FAILED,
;
}
public MessageBody resolveMessageBody() {
return JSONObject.parseObject(messageBody, MessageBody.class);
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class MessageBody {
/**
* 模板消息:Template聊天消息:Chat通知消息:Notify
*/
private String msgType;
/**
* 消息标题
*/
private String msgHeader;
/**
* 消息内容
*/
private String msgContent;
/**
* 消息通知结构体
*/
private String msgBody;
public MsgBody resolveMsgBody() {
return JSONObject.parseObject(msgBody, MsgBody.class);
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class MsgBody {
private CardDetailButton cardDetailButton;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class CardDetailButton {
private List<ActionPath> actionPaths;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ActionPath {
private String platform;
private String url;
}
}

View File

@ -0,0 +1,9 @@
package cn.axzo.nanopart.server.mapper;
import cn.axzo.nanopart.server.domain.OpMessageConfig;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface OpMessageConfigMapper extends BaseMapper<OpMessageConfig> {
}

View File

@ -0,0 +1,281 @@
package cn.axzo.nanopart.server.service;
import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
import cn.axzo.im.center.api.feign.RobotInfoApi;
import cn.axzo.nanopart.server.domain.OpMessageConfig;
import cn.axzo.pokonyan.dao.mysql.QueryWrapperHelper;
import cn.axzo.pokonyan.dao.page.IPageParam;
import cn.axzo.pokonyan.dao.wrapper.CriteriaField;
import cn.axzo.pokonyan.dao.wrapper.Operator;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public interface OpMessageConfigService extends IService<OpMessageConfig> {
Page<OpMessageConfigDTO> page(PageOpMessageConfigParam param);
OpMessageConfig create(CreateOpMessageConfigParam param);
OpMessageConfig update(UpdateOpMessageConfigParam param);
void delete(DeleteOpMessageConfigParam param);
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
class DeleteOpMessageConfigParam {
private Long id;
private Long deletePersonId;
}
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
class UpdateOpMessageConfigParam {
private Long id;
private String name;
private String sendImAccount;
private JSONObject receiveData;
private String title;
private String content;
private String contentType;
private String coverImg;
private JSONObject jumpData;
private JSONObject pushData;
private JSONObject ext;
private Date planStartTime;
private Long updatePersonId;
private Long submitPersonId;
private OpMessageConfig.ActionEnum action;
private Date submitTime;
private Long imMessageTaskId;
private Date startedTime;
private Date finishedTime;
public OpMessageConfig to() {
OpMessageConfig opMessageConfig = OpMessageConfig.builder().build();
BeanUtils.copyProperties(this, opMessageConfig);
return opMessageConfig;
}
}
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
class CreateOpMessageConfigParam {
private String name;
private String sendImAccount;
private JSONObject receiveData;
private OpMessageConfig.Status status;
private String title;
private String content;
private String contentType;
private String coverImg;
private JSONObject jumpData;
private JSONObject pushData;
private JSONObject ext;
private Date planStartTime;
private Long createPersonId;
private Long submitPersonId;
public OpMessageConfig to() {
OpMessageConfig opMessageConfig = OpMessageConfig.builder().build();
BeanUtils.copyProperties(this, opMessageConfig);
opMessageConfig.setSubmitTime(new Date());
return opMessageConfig;
}
}
@SuperBuilder
@Data
@NoArgsConstructor
@AllArgsConstructor
class ListOpMessageConfigParam {
@CriteriaField(field = "id", operator = Operator.IN)
private List<Long> ids;
@CriteriaField(field = "name", operator = Operator.LIKE)
private String nameLike;
@CriteriaField(field = "sendImAccount", operator = Operator.EQ)
private String sendImAccount;
@CriteriaField(field = "sendImAccount", operator = Operator.LIKE)
private String sendImAccountLike;
@CriteriaField(field = "planStartTime", operator = Operator.LE)
private Date planStartTimeLE;
@CriteriaField(field = "status", operator = Operator.IN)
private List<String> statuses;
@CriteriaField(field = "contentType", operator = Operator.EQ)
private String contentType;
@CriteriaField(field = "imMessageTaskId", operator = Operator.EQ)
private Long imMessageTaskId;
@CriteriaField(ignore = true)
private List<String> receivePlatforms;;
/**
* 返回发送的信息
* IM机器人账号返回头像名字等信息
* 账号有机器人和系统人员信息查询的接口不同
*/
@CriteriaField(ignore = true)
private boolean needSendUserInfo;
/**
* 返回创建人的信息
*/
@CriteriaField(ignore = true)
private boolean needCreatePersonInfo;
/**
* 返回提交审核人的信息
*/
@CriteriaField(ignore = true)
private boolean needSubmitPersonInfo;
}
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
class SendUserInfo {
/**
* im账号
*/
private String imAccount;
/**
* 昵称
*/
private String nickName;
/**
* 头像
*/
private String headImageUrl;
public static SendUserInfo from(RobotInfoApi.RobotInfoDTO robotInfoDTO) {
return SendUserInfo.builder()
.imAccount(robotInfoDTO.getImAccount())
.nickName(robotInfoDTO.getNickName())
.headImageUrl(robotInfoDTO.getHeadImageUrl())
.build();
}
}
@SuperBuilder
@Data
@NoArgsConstructor
@AllArgsConstructor
class PageOpMessageConfigParam extends ListOpMessageConfigParam implements IPageParam {
@CriteriaField(ignore = true)
Integer page;
@CriteriaField(ignore = true)
Integer pageSize;
/**
* 排序使用示例createTime__DESC
*/
@CriteriaField(ignore = true)
List<String> sort;
public QueryWrapper<OpMessageConfig> toQueryWrapper() {
QueryWrapper<OpMessageConfig> wrapper = QueryWrapperHelper.fromBean(this, OpMessageConfig.class);
wrapper.eq("is_delete", 0);
if (!CollectionUtils.isEmpty(this.getReceivePlatforms())) {
String sql = this.getReceivePlatforms().stream()
.map(e -> "json_contains(receive_data->'$.platforms', '\"" + e + "\"')")
.collect(Collectors.joining(" or "));
wrapper.and(e -> e.apply(sql));
}
return wrapper;
}
}
@SuperBuilder
@Data
@NoArgsConstructor
@AllArgsConstructor
class OpMessageConfigDTO extends OpMessageConfig {
private SendUserInfo sendUserInfo;
private PersonProfileDto createPersonProfile;
private PersonProfileDto submitPersonProfile;
public static OpMessageConfigDTO from(OpMessageConfig opMessageConfig,
Map<String, SendUserInfo> sendUserInfos,
Map<Long, PersonProfileDto> createPersonProfiles,
Map<Long, PersonProfileDto> submitPersonProfiles) {
OpMessageConfigDTO opMessageConfigDTO = OpMessageConfigDTO.builder().build();
BeanUtils.copyProperties(opMessageConfig, opMessageConfigDTO);
opMessageConfigDTO.setSendUserInfo(sendUserInfos.get(opMessageConfigDTO.getSendImAccount()));
opMessageConfigDTO.setCreatePersonProfile(createPersonProfiles.get(opMessageConfigDTO.getCreatePersonId()));
opMessageConfigDTO.setSubmitPersonProfile(submitPersonProfiles.get(opMessageConfigDTO.getSubmitPersonId()));
return opMessageConfigDTO;
}
}
}

View File

@ -0,0 +1,169 @@
package cn.axzo.nanopart.server.service.impl;
import cn.axzo.basics.profiles.api.UserProfileServiceApi;
import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
import cn.axzo.im.center.api.feign.RobotInfoApi;
import cn.axzo.nanopart.server.domain.OpMessageConfig;
import cn.axzo.nanopart.server.mapper.OpMessageConfigMapper;
import cn.axzo.nanopart.server.service.OpMessageConfigService;
import cn.axzo.pokonyan.dao.converter.PageConverter;
import cn.axzo.pokonyan.exception.Aassert;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.axzo.nanopart.server.config.BizResultCode.DELETE_OP_MESSAGE_CONFIG_STATUS_ERROR;
import static cn.axzo.nanopart.server.config.BizResultCode.OP_MESSAGE_CONFIG_NOT_FOUND;
@Slf4j
@Service
public class OpMessageConfigServiceImpl extends ServiceImpl<OpMessageConfigMapper, OpMessageConfig>
implements OpMessageConfigService {
private static final Long DEFAULT_ZERO_PERSON_ID = 0L;
@Autowired
private RobotInfoApi robotInfoApi;
@Autowired
private UserProfileServiceApi userProfileServiceApi;
@Override
public Page<OpMessageConfigDTO> page(PageOpMessageConfigParam param) {
QueryWrapper<OpMessageConfig> wrapper = param.toQueryWrapper();
Page<OpMessageConfig> page = this.page(PageConverter.convertToMybatis(param, OpMessageConfig.class), wrapper);
Map<String, SendUserInfo> sendUserInfos = listSendUserInfo(param, page.getRecords());
Map<Long, PersonProfileDto> createPersonProfiles = listCreatePersonProfile(param, page.getRecords());
Map<Long, PersonProfileDto> submitPersonProfiles = listSubmitPersonProfile(param, page.getRecords());
return PageConverter.convert(page, (record) -> OpMessageConfigDTO.from(record,
sendUserInfos,
createPersonProfiles,
submitPersonProfiles));
}
@Override
public OpMessageConfig create(CreateOpMessageConfigParam param) {
OpMessageConfig opMessageConfig = param.to();
this.save(opMessageConfig);
return this.getById(opMessageConfig.getId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public OpMessageConfig update(UpdateOpMessageConfigParam param) {
OpMessageConfig oldOpMessageConfig = getAndLock(param.getId());
OpMessageConfig opMessageConfig = param.to();
if (param.getAction() != null) {
opMessageConfig.setStatus(param.getAction().getNextStatus(oldOpMessageConfig.getStatus()));
}
this.updateById(opMessageConfig);
return this.getById(param.getId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(DeleteOpMessageConfigParam param) {
OpMessageConfig oldOpMessageConfig = getAndLock(param.getId());
Aassert.check(oldOpMessageConfig.canDelete(), DELETE_OP_MESSAGE_CONFIG_STATUS_ERROR);
// 因为updateById处理的时候有拦截器过滤掉了isDelete的更新
this.lambdaUpdate()
.set(OpMessageConfig::getIsDelete, 1)
.set(OpMessageConfig::getDeletePersonId, param.getDeletePersonId())
.eq(OpMessageConfig::getId, param.getId())
.update();
}
private OpMessageConfig getAndLock(Long id) {
OpMessageConfig oldOpMessageConfig = this.lambdaQuery()
.eq(OpMessageConfig::getId, id)
.last("for update")
.one();
Aassert.notNull(oldOpMessageConfig, OP_MESSAGE_CONFIG_NOT_FOUND);
return oldOpMessageConfig;
}
private Map<String, SendUserInfo> listSendUserInfo(PageOpMessageConfigParam param,
List<OpMessageConfig> opMessageConfigs) {
if (CollectionUtils.isEmpty(opMessageConfigs) || BooleanUtils.isNotTrue(param.isNeedSendUserInfo())) {
return Collections.emptyMap();
}
List<String> sendImAccounts = opMessageConfigs.stream()
.map(OpMessageConfig::getSendImAccount)
.filter(StringUtils::isNotBlank)
.distinct()
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(sendImAccounts)) {
return Collections.emptyMap();
}
// 现在发送者只有机器人所以发送者的头像信息从robotInfoApi中获取
return robotInfoApi.page(RobotInfoApi.PageRobotInfoParam.builder()
.imAccounts(sendImAccounts)
.build())
.getData()
.getList()
.stream()
.map(SendUserInfo::from)
.collect(Collectors.toMap(SendUserInfo::getImAccount, Function.identity()));
}
private Map<Long, PersonProfileDto> listCreatePersonProfile(PageOpMessageConfigParam param,
List<OpMessageConfig> opMessageConfigs) {
if (CollectionUtils.isEmpty(opMessageConfigs) || BooleanUtils.isNotTrue(param.isNeedCreatePersonInfo())) {
return Collections.emptyMap();
}
List<Long> createPersonIds = opMessageConfigs.stream()
.map(OpMessageConfig::getCreatePersonId)
.distinct()
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(createPersonIds)) {
return Collections.emptyMap();
}
return userProfileServiceApi.postPersonProfiles(createPersonIds).getData()
.stream()
.collect(Collectors.toMap(PersonProfileDto::getId, Function.identity()));
}
private Map<Long, PersonProfileDto> listSubmitPersonProfile(PageOpMessageConfigParam param,
List<OpMessageConfig> opMessageConfigs) {
if (CollectionUtils.isEmpty(opMessageConfigs) || BooleanUtils.isNotTrue(param.isNeedSubmitPersonInfo())) {
return Collections.emptyMap();
}
List<Long> submitPersonIds = opMessageConfigs.stream()
.map(OpMessageConfig::getSubmitPersonId)
.filter(id -> !Objects.equals(DEFAULT_ZERO_PERSON_ID, id))
.distinct()
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(submitPersonIds)) {
return Collections.emptyMap();
}
return userProfileServiceApi.postPersonProfiles(submitPersonIds).getData()
.stream()
.collect(Collectors.toMap(PersonProfileDto::getId, Function.identity()));
}
}

View File

@ -0,0 +1,234 @@
package cn.axzo.nanopart.server.xxljob;
import cn.axzo.framework.domain.web.result.ApiListResult;
import cn.axzo.im.center.api.feign.MessageApi;
import cn.axzo.im.center.api.vo.req.AsyncSendMessageParam;
import cn.axzo.im.center.api.vo.resp.MessageTaskResp;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.maokai.api.client.CooperateShipQueryApi;
import cn.axzo.maokai.api.vo.request.CooperateShipQueryReq;
import cn.axzo.maokai.api.vo.response.CooperateShipResp;
import cn.axzo.nanopart.server.domain.OpMessageConfig;
import cn.axzo.nanopart.server.service.OpMessageConfigService;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
@Component
@RequiredArgsConstructor
public class SendMessageJob extends IJobHandler {
public static final String BIZ_ID_PREFIX = "op-message";
@Autowired
private OpMessageConfigService opMessageConfigService;
@Autowired
private MessageApi messageApi;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private CooperateShipQueryApi cooperateShipQueryApi;
private static final Integer DEFAULT_PAGE_SIZE = 10;
@Override
@XxlJob("sendMessageJob")
public ReturnT<String> execute(String s) throws Exception {
log.info("start sendMessageJob,s:{}", s);
SendMessageParam sendMessageParam = Optional.ofNullable(s)
.map(e -> JSONObject.parseObject(e, SendMessageParam.class))
.orElseGet(() -> SendMessageParam.builder().build());
Integer pageNumber = 1;
Date now = new Date();
SendMessageJob proxySendMessageJob = applicationContext.getBean(this.getClass());
while (true) {
OpMessageConfigService.PageOpMessageConfigParam req = OpMessageConfigService.PageOpMessageConfigParam.builder()
.ids(sendMessageParam.getIds())
.planStartTimeLE(now)
.statuses(Lists.newArrayList(OpMessageConfig.Status.PENDING.name()))
.page(pageNumber)
.pageSize(DEFAULT_PAGE_SIZE)
.build();
Page<OpMessageConfigService.OpMessageConfigDTO> page = opMessageConfigService.page(req);
if (CollectionUtils.isNotEmpty(page.getRecords())) {
// 一个一个更新数据量不大一个失败不影响其他的运营消息
page.getRecords().forEach(e -> {
try {
proxySendMessageJob.sendMessage(e);
} catch (Exception ex) {
log.error("job 执行失败, job{}, ex", e.getId(), ex);
}
});
}
if (!page.hasNext()) {
break;
}
}
log.info("end sendMessageJob");
return ReturnT.SUCCESS;
}
@Transactional(rollbackFor = Exception.class)
public void sendMessage(OpMessageConfigService.OpMessageConfigDTO opMessageConfig) {
opMessageConfigService.update(OpMessageConfigService.UpdateOpMessageConfigParam.builder()
.id(opMessageConfig.getId())
.action(OpMessageConfig.ActionEnum.RUN)
.startedTime(new Date())
.build());
OpMessageConfig.ReceiveData receiveData = opMessageConfig.resolveReceiveData();
OpMessageConfig.ReceiveType receiveType = OpMessageConfig.ReceiveType.of(receiveData.getType());
boolean isAllPerson = Objects.equals(OpMessageConfig.ReceiveType.ALL, receiveType);
List<AppTypeEnum> appTypes = BooleanUtils.isTrue(isAllPerson) ? receiveData.getPlatforms().stream().map(AppTypeEnum::valueOf).collect(Collectors.toList()) : null;
List<AsyncSendMessageParam.ReceivePerson> receivePersons = resolveReceivePerson(receiveData);
// 接收方式是import且解析后没有接收人则直接完成任务
// 存在只配置了管理端但是import的人员没导入ouId导致接收人为空
if (CollectionUtils.isEmpty(receivePersons) && Objects.equals(OpMessageConfig.ReceiveType.IMPORT, receiveType)) {
opMessageConfigService.update(OpMessageConfigService.UpdateOpMessageConfigParam.builder()
.id(opMessageConfig.getId())
// 消息是异步执行的准确的完成时间需要监听messageTask的完成时间才准确现在mq配置较低消息服务没有发送mq
.finishedTime(new Date())
.action(OpMessageConfig.ActionEnum.COMPLETE)
.build());
} else {
MessageTaskResp messageTask = messageApi.sendMessageAsync(AsyncSendMessageParam.builder()
.bizId(String.format("%s:%s", BIZ_ID_PREFIX, opMessageConfig.getId()))
.sendImAccount(opMessageConfig.getSendImAccount())
.receivePersons(receivePersons)
.allPerson(isAllPerson)
.appTypes(appTypes)
.msgHeader(opMessageConfig.getTitle())
.msgContent(opMessageConfig.getContent())
.jumpData(resolveJumpData(opMessageConfig))
.cardBannerUrl(opMessageConfig.getCoverImg())
.build()).getData();
opMessageConfigService.update(OpMessageConfigService.UpdateOpMessageConfigParam.builder()
.id(opMessageConfig.getId())
.imMessageTaskId(messageTask.getId())
// 消息是异步执行的准确的完成时间需要监听messageTask的完成时间才准确现在mq配置较低消息服务没有发送mq
.finishedTime(new Date())
.action(OpMessageConfig.ActionEnum.COMPLETE)
.build());
}
}
private List<AsyncSendMessageParam.ReceivePerson> resolveReceivePerson(OpMessageConfig.ReceiveData receiveData) {
OpMessageConfig.ReceiveType receiveType = OpMessageConfig.ReceiveType.of(receiveData.getType());
boolean isAllPerson = Objects.equals(OpMessageConfig.ReceiveType.ALL, receiveType);
if (BooleanUtils.isTrue(isAllPerson)) {
return Collections.emptyList();
}
List<AsyncSendMessageParam.ReceivePerson> cmReceivePersons = listCmReceivePersons(receiveData);
List<AsyncSendMessageParam.ReceivePerson> cmpReceivePersons = listCmPReceivePersons(receiveData);
cmReceivePersons.addAll(cmpReceivePersons);
return cmReceivePersons;
}
private List<AsyncSendMessageParam.ReceivePerson> listCmReceivePersons(OpMessageConfig.ReceiveData receiveData) {
boolean isCm = receiveData.getPlatforms().stream()
.anyMatch(platform -> AppTypeEnum.valueOf(platform) == AppTypeEnum.CM);
if (BooleanUtils.isNotTrue(isCm)) {
return Lists.newArrayList();
}
return receiveData.getPersons().stream()
.map(OpMessageConfig.Person::getPersonId)
.distinct()
.map(personId -> AsyncSendMessageParam.ReceivePerson.builder()
.personId(personId.toString())
.appType(AppTypeEnum.CM)
.build())
.collect(Collectors.toList());
}
private List<AsyncSendMessageParam.ReceivePerson> listCmPReceivePersons(OpMessageConfig.ReceiveData receiveData) {
boolean isCmp = receiveData.getPlatforms().stream()
.anyMatch(platform -> AppTypeEnum.valueOf(platform) == AppTypeEnum.CMP);
if (BooleanUtils.isNotTrue(isCmp)) {
return Lists.newArrayList();
}
Map<Long, Long> workspaceIds = listWorkspaceIds(receiveData);
return receiveData.getPersons().stream()
.filter(person -> person.getOuId() != null)
.map(person -> AsyncSendMessageParam.ReceivePerson.builder()
.personId(person.getPersonId().toString())
.ouId(person.getOuId())
.appType(AppTypeEnum.CMP)
.workspaceId(workspaceIds.get(person.getOuId()))
.build())
.collect(Collectors.toList());
}
private Map<Long, Long> listWorkspaceIds(OpMessageConfig.ReceiveData receiveData) {
List<Long> ouIds = receiveData.getPersons().stream()
.map(OpMessageConfig.Person::getOuId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(ouIds)) {
return Collections.emptyMap();
}
log.info("list workspaceId,{}", ouIds);
ApiListResult<CooperateShipResp> cooperateShipRespApiListResult = cooperateShipQueryApi.genericQuery(CooperateShipQueryReq.builder()
.ouIdList(ouIds)
.workspaceType(1)
.build());
log.info("list workspaceId result,{}", cooperateShipRespApiListResult);
return cooperateShipRespApiListResult.getData()
.stream()
.collect(Collectors.toMap(CooperateShipResp::getOrganizationalUnitId, CooperateShipResp::getWorkspaceId, (f, s) -> f));
}
private List<cn.axzo.im.center.api.vo.req.SendMessageParam.JumpData> resolveJumpData(OpMessageConfigService.OpMessageConfigDTO opMessageConfig) {
return opMessageConfig.resolveJumpData().getPlatforms().stream()
.map(platform -> cn.axzo.im.center.api.vo.req.SendMessageParam.JumpData.builder()
.platform(cn.axzo.im.center.api.vo.req.SendMessageParam.JumpPlatform.valueOf(platform.getPlatform().name()))
.url(platform.getUrl())
.build())
.collect(Collectors.toList());
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class SendMessageParam {
private List<Long> ids;
}
}

View File

@ -0,0 +1,30 @@
CREATE TABLE IF NOT EXISTS op_message_config
(
id bigint auto_increment comment '主键',
im_message_task_id bigint not null default 0 comment 'im-center服务im_message_task的id',
`name` varchar(50) not null default '' comment '消息名字',
send_im_account varchar(100) not null default '' comment '发送者的三方平台账号id',
receive_data json null comment '消息接收配置',
status varchar(32) not null default 'DRAFT' comment '消息状态DRAFT、PENDING、RUNNING、COMPLETED',
title varchar(128) not null default '' comment '消息标题',
content varchar(512) not null default '' comment '消息内容',
content_type varchar(32) not null default '' comment '消息形式IMAGE_TEXT_SAMPLE',
cover_img varchar(512) not null default '' comment '消息封面大图',
jump_data json comment '跳转配置',
push_data json comment 'push配置',
ext varchar(1024) not null default '{}' COMMENT '其它额外信息',
plan_start_time DATETIME(3) null comment '任务计划开始时间,时间大于改时间会对未完成的任务进行执行操作',
started_time DATETIME(3) null comment '实际开始时间',
finished_time DATETIME(3) null comment '实际完成时间',
submit_time DATETIME(3) null comment '提交审核时间',
is_delete tinyint default 0 not null comment '未删除0,删除1',
create_at datetime default CURRENT_TIMESTAMP not null comment '创建时间',
create_person_id bigint not null comment '创建人id',
submit_person_id bigint not null default 0 comment '提交审核人id可能跟crate_person_id不一样比如先保存草稿',
delete_person_id bigint not null default 0 comment '删除人id',
update_person_id bigint not null default 0 comment '更新人id',
update_at datetime default CURRENT_TIMESTAMP not null comment '更新时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 comment '运营消息配置';

102
op/pom.xml Normal file
View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.axzo.nanopart</groupId>
<artifactId>nanopart</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>cn.axzo.nanopart</groupId>
<artifactId>op</artifactId>
<packaging>pom</packaging>
<name>op</name>
<properties>
<axzo-bom.version>2.0.0-SNAPSHOT</axzo-bom.version>
<axzo-dependencies.version>2.0.0-SNAPSHOT</axzo-dependencies.version>
<lombok.version>1.18.22</lombok.version>
<mapstruct.version>1.4.2.Final</mapstruct.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- 导入axzo通用api依赖 -->
<dependency>
<groupId>cn.axzo.infra</groupId>
<artifactId>axzo-bom</artifactId>
<version>${axzo-bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>cn.axzo.infra</groupId>
<artifactId>axzo-dependencies</artifactId>
<version>${axzo-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- for test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- maven-compiler-plugin 插件,解决 Lombok + MapStruct 组合 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>axzo</id>
<name>axzo repository</name>
<url>https://nexus.axzo.cn/repository/axzo/</url>
</repository>
</repositories>
<modules>
<module>op-server</module>
<module>op-api</module>
</modules>
</project>

View File

@ -31,6 +31,7 @@
<module>nanopart-server</module>
<module>config</module>
<module>job</module>
<module>op</module>
<module>banner</module>
</modules>