params) {
+ if (Objects.isNull(params.get("jobName"))) {
+ return R.failed("jobName不能为空");
+ }
+ elasticJobHandler.removeJob(params.get("jobName").toString());
+ return R.ok("请求成功");
+ }
+
+
+}
diff --git a/admin/src/main/java/com/baiye/schedule/entity/JobDynamicTask.java b/admin/src/main/java/com/baiye/schedule/entity/JobDynamicTask.java
new file mode 100644
index 0000000..c7b62c3
--- /dev/null
+++ b/admin/src/main/java/com/baiye/schedule/entity/JobDynamicTask.java
@@ -0,0 +1,46 @@
+package com.baiye.schedule.entity;
+
+import com.baiye.entity.BaseEntity;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * @author Enzo
+ * @date : 2024/5/28
+ */
+@Getter
+@Setter
+@TableName(value = "tb_job_dynamic_task")
+@Schema(title = "定时任务")
+public class JobDynamicTask extends BaseEntity implements Serializable {
+ /**
+ * ID
+ */
+ @TableId
+ @Schema(title = "ID")
+ private Long id;
+
+ @Schema(title = "is_delete")
+ private Integer isDelete;
+
+ @Schema(title = "job_name")
+ private String jobName;
+
+ @Schema(title = "cron")
+ private String cron;
+
+ @Schema(title = "description")
+ private String description;
+
+ @Schema(title = "parameters")
+ private String parameters;
+
+ @Schema(title = "status")
+ private Integer status;
+}
diff --git a/admin/src/main/java/com/baiye/schedule/handler/DynamicJob.java b/admin/src/main/java/com/baiye/schedule/handler/DynamicJob.java
new file mode 100644
index 0000000..2fac3c1
--- /dev/null
+++ b/admin/src/main/java/com/baiye/schedule/handler/DynamicJob.java
@@ -0,0 +1,34 @@
+package com.baiye.schedule.handler;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import com.dangdang.ddframe.job.api.ShardingContext;
+import com.dangdang.ddframe.job.api.simple.SimpleJob;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Date;
+
+/**
+ * @author Enzo
+ * @date : 2024/5/28
+ */
+@Slf4j
+public class DynamicJob implements SimpleJob {
+
+ /**
+ * 业务执行逻辑
+ *
+ * @param shardingContext
+ */
+ @Override
+ public void execute(ShardingContext shardingContext) {
+ log.info("{}动态定时任务执行逻辑start...", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));
+ String jobName = shardingContext.getJobName();
+ String jobParameter = shardingContext.getJobParameter();
+ log.info("---------DynamicJob---------动态定时任务正在执行:jobName = {}, jobParameter = {}", jobName, jobParameter);
+
+ //根据参数调用不同的业务接口处理,请远程调用业务模块处理,避免本服务与业务依赖过重...
+
+ log.info("{}动态定时任务执行逻辑end...", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));
+ }
+}
diff --git a/admin/src/main/java/com/baiye/schedule/handler/ScanDynamicJobHandler.java b/admin/src/main/java/com/baiye/schedule/handler/ScanDynamicJobHandler.java
new file mode 100644
index 0000000..8327381
--- /dev/null
+++ b/admin/src/main/java/com/baiye/schedule/handler/ScanDynamicJobHandler.java
@@ -0,0 +1,41 @@
+package com.baiye.schedule.handler;
+
+import com.baiye.common.job.handler.ElasticJobHandler;
+import com.baiye.schedule.entity.JobDynamicTask;
+import com.baiye.schedule.service.ElasticJobService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * @author Enzo
+ * @date : 2024/5/28
+ */
+@Slf4j
+@Service
+public class ScanDynamicJobHandler {
+ @Resource
+ private ElasticJobHandler elasticJobHandler;
+ @Resource
+ private ElasticJobService elasticJobService;
+
+ /**
+ * 扫描动态任务列表,并添加任务
+ *
+ * 循环执行的动态任务,本服务重启的时候,需要重新加载任务
+ *
+ * @author songfayuan
+ * @date 2021/4/26 9:15 下午
+ */
+ public void scanAddJob() {
+ // 这里为从MySQL数据库读取job_dynamic_task表的数据,微服务项目中建议使用feign从业务服务获取,避免本服务过度依赖业务的问题,然后业务服务新增动态任务也通过feign调取本服务JobOperateController实现,从而相对独立本服务模块
+ List jobDynamicTaskList = this.elasticJobService.getAllList();
+ log.info("扫描动态任务列表,并添加任务:本次共扫描到{}条任务。", jobDynamicTaskList.size());
+ for (JobDynamicTask jobDynamicTask : jobDynamicTaskList) {
+ // 创建任务
+ elasticJobHandler.addJob(jobDynamicTask.getJobName(), jobDynamicTask.getCron(), 1, new DynamicJob(), jobDynamicTask.getParameters(), jobDynamicTask.getDescription());
+ }
+ }
+}
diff --git a/admin/src/main/java/com/baiye/schedule/mapper/JobDynamicTaskMapper.java b/admin/src/main/java/com/baiye/schedule/mapper/JobDynamicTaskMapper.java
new file mode 100644
index 0000000..dadcb03
--- /dev/null
+++ b/admin/src/main/java/com/baiye/schedule/mapper/JobDynamicTaskMapper.java
@@ -0,0 +1,14 @@
+package com.baiye.schedule.mapper;
+
+import com.baiye.extend.mybatis.plus.mapper.ExtendMapper;
+import com.baiye.schedule.entity.JobDynamicTask;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author Enzo
+ * @date : 2024/5/28
+ */
+@Mapper
+public interface JobDynamicTaskMapper extends ExtendMapper {
+
+}
diff --git a/admin/src/main/java/com/baiye/schedule/service/ElasticJobService.java b/admin/src/main/java/com/baiye/schedule/service/ElasticJobService.java
new file mode 100644
index 0000000..46e4644
--- /dev/null
+++ b/admin/src/main/java/com/baiye/schedule/service/ElasticJobService.java
@@ -0,0 +1,19 @@
+package com.baiye.schedule.service;
+
+import com.baiye.extend.mybatis.plus.service.ExtendService;
+import com.baiye.schedule.entity.JobDynamicTask;
+
+import java.util.List;
+
+/**
+ * @author Enzo
+ * @date : 2024/5/28
+ */
+public interface ElasticJobService extends ExtendService {
+
+ /**
+ * 获取所有正在运行的
+ * @return
+ */
+ List getAllList();
+}
diff --git a/admin/src/main/java/com/baiye/schedule/service/impl/ElasticJobServiceImpl.java b/admin/src/main/java/com/baiye/schedule/service/impl/ElasticJobServiceImpl.java
new file mode 100644
index 0000000..737671e
--- /dev/null
+++ b/admin/src/main/java/com/baiye/schedule/service/impl/ElasticJobServiceImpl.java
@@ -0,0 +1,28 @@
+package com.baiye.schedule.service.impl;
+
+import com.baiye.schedule.mapper.JobDynamicTaskMapper;
+import com.baiye.constant.DefaultNumberConstants;
+import com.baiye.extend.mybatis.plus.service.impl.ExtendServiceImpl;
+import com.baiye.extend.mybatis.plus.toolkit.WrappersX;
+import com.baiye.schedule.entity.JobDynamicTask;
+import com.baiye.schedule.service.ElasticJobService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author Enzo
+ * @date : 2024/5/28
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ElasticJobServiceImpl extends ExtendServiceImpl implements ElasticJobService {
+ @Override
+ public List getAllList() {
+ return baseMapper.selectList(WrappersX.lambdaQueryX(JobDynamicTask.class).eq(JobDynamicTask::getStatus, DefaultNumberConstants.ONE_NUMBER));
+ }
+
+}
diff --git a/admin/src/main/resources/application-dev.yml b/admin/src/main/resources/application-dev.yml
new file mode 100644
index 0000000..ecceb42
--- /dev/null
+++ b/admin/src/main/resources/application-dev.yml
@@ -0,0 +1,48 @@
+spring:
+ datasource:
+ url: jdbc:mysql://39.100.77.21:3306/marketing-scrm?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
+ username: root
+ password: y7z7noq2
+ redis:
+ host: 39.100.77.21
+ password: sC33HXphkHBRj4Jb
+ port: 6379
+ timeout: 5000
+ database: 1
+
+
+alipay:
+ protocol: https
+ # 不需要加/gateway.do,这是新旧SDK的区别,切记
+ # gatewayHost: openapi.alipaydev.com
+ gatewayHost: openapi.alipay.com
+ signType: RSA2
+ # 填APPID
+ appId: 2021003125644167
+ # 填应用私钥,注意是应用私钥,不要填成公钥了_(:з」∠)_
+ merchantPrivateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCNIuelR5MHMY3vtl2KEgWUbhHMHuBwxHMVONuUYKlt4grOGJtaPKitgh9MxrvaxmAvH0LilQQKd5T07os9jC2XpNmH0BA6rNVNgBwjWNllqKo+jT8OiKvog9uBuZYfHdkLRhjObrTkSo97jO5Y32591GZHG+cQ/5bgMC0KXlQMOv5piLqUfKYDJ5pWBdS5gqcCkUABZsrW+C8nD7lvOuVglN1AGQiXQzj+Iu3K5xcUg+d3q0nWXhKPpotSolFGSakLEqUja/MnsILVHVw8MYMuq6LxzQ7DQ5fCzMPMIF5kYTynsAV5SoE90ilj1vmmI4aa7dc+9OmG/vXyFuoOEK1bAgMBAAECggEAH0K/9EfqNQmw2ouWJGLhgYLvxjqAk/mvU+AIItFWNdR/eC7TGiWdZvEPZb4PFeIio81Uz0MaZgceozHC/Zry7kfBNufK8HQfus7JbLrdTDsTmk9GzD1RdmreT9l/etztmiWokPDMeFRbe443rM+wdYZ6MP3pLEawcG+7SjSigSKrcpltAJ49NhHyfJxnnCye4mM8PevVGXe4nGv/vKJbAfzl5V8MsSY+SVktj6jOUTSa6nypISvQElyv8jeNH/bTDjg0DOHzL4tUHaVbTqHhtqbNo3n9VuKz4shVAp3I+9PpcTQTueTsM9L0Y+j84bV3VZlTIGGzDUD4qWDhUKPJ4QKBgQDQFNB2vLDJLeGfn+SnCncnr/wQo1xJez+deBqJXoDhDWDuZVcWNaEOWgfDronm8dIedWMYHD//WN0GtA5foAUdlaNq/jeKdJFgzJ/hoOtOveHY1OWqJHiJ/YMRBMuLn/E4Jit7q215JU6Jur1T+70HxrUFTQfY8GWdMvSDFHCKDwKBgQCto24hqgcEDKoR3/sm5sUKqYZTHLeQbniM0D5EcOkBNw7UXGYQSbz7mJ34XQKTfrOL71A+LkjxdMfs5q56yxeOQxR4EBO/Fpz8xPRlud5uIp4eTQVJ8I01meZyvojkF6mO5C4tbcXJOpnpU8ohoeTRu7th0oUYObGUYmn0qzuj9QKBgDCOOtsKSwKXF0hFanjkQ0vakCpdxIJNJVoclayqhc5+bbkTos/G8e9EaP1rtDhVA6Ah6l7M8M4oMWOIDraXw7nUmk60RcekTexVs5VWFLLKMnKDs5gRbKNeqgAFq23Ig+SDW7A/H4uefgY7skRvwPuYjdNP113zMvMM2evgkCZXAoGAP5przvz/EOaqrV2EG93QM3WhdHRCcS9mDP6CsINDdmR6lCM8Z577EJX4128KcIiqsAl7NSuzIG8MhKSDKQuXl07PAqOw+AAKhTSH6XNKHMGldaf01f69WvMCzOkqL5LTUzoWCCH7nxhOJH/CvMsWjBTeMJjyk8seVyIteaf3crkCgYEAmY8OC+Vdo/yop+ooedCnvBC7GVMfcqR6+rIQ+ZnKom7jhByIN6w7eEVAnmNa9dCv/pig7HaYbitWmCNf9tbqFfcdY6e5Svele3amgDM9iqtdJ9Rf1PpDPZw+gmjRIxGs6W8wnl1Sqvpk8pFUA/2xlT6sXpfxra9N09LS0ttU+cU=
+ # 填支付宝公钥,注意不是生成的应用公钥
+ aliPayPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyvegkGCrz1i5/K7VzQxvoZm4G73pSPNu9R5ET8YonIuDVoHkHaYvQQizLdRkTyBsYIik8FXsSDmVsw5MLMQ7OAlZ97nQBkz5TxOX6Px766nUpSVoWXoAz6cpIlWnwgir4t1ph88Ph56l+qmqW3gUj/U6MIuzjxBJlijTMHl96its95Nd4cEFx4j+sFuRYob6D0kcemC7xEFuty7bdupda51Z56GYI1YjuUTryTlFOHZbOSThc2ZMzNC1gPG25bn2Lx6sDuPByk4KW4rQ2v7mSfeUuZZRdjtVSC0WV2M2Cv5L8eLFvZRgNYnXrUJYhnRpT+OBAvJZXaWU2nv/bNe/UQIDAQAB
+ # 回调地址
+ notifyUrl: http://39.100.77.21:8088/pay/aliPay/pay-notify
+ # 支付宝成功支付跳转页面
+ returnUrl: https://count.byffp.top/statePage/success
+ # 可设置AES密钥,调用AES加解密相关接口时需要(可选)
+ encryptKey:
+
+
+springdoc:
+ swagger-ui:
+ urls:
+ - { name: 'admin', url: '/v3/api-docs' }
+ - { name: 'api', url: 'http://ballcat-api/v3/api-docs' }
+
+#mybatis plus 设置
+mybatis-plus:
+ configuration:
+# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志
+ log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl #关闭sql日志
+
+wechat:
+ gatewayHost: https://fission-server.scrm-ai.com
+ appKey: SFkWRAued71GvCeClj8efDhAG6bJ2rzT
diff --git a/admin/src/main/resources/application-prod.yml b/admin/src/main/resources/application-prod.yml
new file mode 100644
index 0000000..dbfe044
--- /dev/null
+++ b/admin/src/main/resources/application-prod.yml
@@ -0,0 +1,39 @@
+spring:
+ datasource:
+ url: jdbc:mysql://localhost:3306/marketing-scrm?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
+ username: root
+ password: CK8KyUngFSY
+ redis:
+ host: localhost
+ password:
+ port: 6379
+
+
+
+# 生产环境关闭文档
+ballcat:
+ openapi:
+ enabled: false
+
+alipay:
+ protocol: https
+ # 不需要加/gateway.do,这是新旧SDK的区别,切记
+ # gatewayHost: openapi.alipaydev.com
+ gatewayHost: openapi.alipay.com
+ signType: RSA2
+ # 填APPID
+ appId: 2021003125644167
+ # 填应用私钥,注意是应用私钥,不要填成公钥了_(:з」∠)_
+ merchantPrivateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCNIuelR5MHMY3vtl2KEgWUbhHMHuBwxHMVONuUYKlt4grOGJtaPKitgh9MxrvaxmAvH0LilQQKd5T07os9jC2XpNmH0BA6rNVNgBwjWNllqKo+jT8OiKvog9uBuZYfHdkLRhjObrTkSo97jO5Y32591GZHG+cQ/5bgMC0KXlQMOv5piLqUfKYDJ5pWBdS5gqcCkUABZsrW+C8nD7lvOuVglN1AGQiXQzj+Iu3K5xcUg+d3q0nWXhKPpotSolFGSakLEqUja/MnsILVHVw8MYMuq6LxzQ7DQ5fCzMPMIF5kYTynsAV5SoE90ilj1vmmI4aa7dc+9OmG/vXyFuoOEK1bAgMBAAECggEAH0K/9EfqNQmw2ouWJGLhgYLvxjqAk/mvU+AIItFWNdR/eC7TGiWdZvEPZb4PFeIio81Uz0MaZgceozHC/Zry7kfBNufK8HQfus7JbLrdTDsTmk9GzD1RdmreT9l/etztmiWokPDMeFRbe443rM+wdYZ6MP3pLEawcG+7SjSigSKrcpltAJ49NhHyfJxnnCye4mM8PevVGXe4nGv/vKJbAfzl5V8MsSY+SVktj6jOUTSa6nypISvQElyv8jeNH/bTDjg0DOHzL4tUHaVbTqHhtqbNo3n9VuKz4shVAp3I+9PpcTQTueTsM9L0Y+j84bV3VZlTIGGzDUD4qWDhUKPJ4QKBgQDQFNB2vLDJLeGfn+SnCncnr/wQo1xJez+deBqJXoDhDWDuZVcWNaEOWgfDronm8dIedWMYHD//WN0GtA5foAUdlaNq/jeKdJFgzJ/hoOtOveHY1OWqJHiJ/YMRBMuLn/E4Jit7q215JU6Jur1T+70HxrUFTQfY8GWdMvSDFHCKDwKBgQCto24hqgcEDKoR3/sm5sUKqYZTHLeQbniM0D5EcOkBNw7UXGYQSbz7mJ34XQKTfrOL71A+LkjxdMfs5q56yxeOQxR4EBO/Fpz8xPRlud5uIp4eTQVJ8I01meZyvojkF6mO5C4tbcXJOpnpU8ohoeTRu7th0oUYObGUYmn0qzuj9QKBgDCOOtsKSwKXF0hFanjkQ0vakCpdxIJNJVoclayqhc5+bbkTos/G8e9EaP1rtDhVA6Ah6l7M8M4oMWOIDraXw7nUmk60RcekTexVs5VWFLLKMnKDs5gRbKNeqgAFq23Ig+SDW7A/H4uefgY7skRvwPuYjdNP113zMvMM2evgkCZXAoGAP5przvz/EOaqrV2EG93QM3WhdHRCcS9mDP6CsINDdmR6lCM8Z577EJX4128KcIiqsAl7NSuzIG8MhKSDKQuXl07PAqOw+AAKhTSH6XNKHMGldaf01f69WvMCzOkqL5LTUzoWCCH7nxhOJH/CvMsWjBTeMJjyk8seVyIteaf3crkCgYEAmY8OC+Vdo/yop+ooedCnvBC7GVMfcqR6+rIQ+ZnKom7jhByIN6w7eEVAnmNa9dCv/pig7HaYbitWmCNf9tbqFfcdY6e5Svele3amgDM9iqtdJ9Rf1PpDPZw+gmjRIxGs6W8wnl1Sqvpk8pFUA/2xlT6sXpfxra9N09LS0ttU+cU=
+ # 填支付宝公钥,注意不是生成的应用公钥
+ aliPayPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyvegkGCrz1i5/K7VzQxvoZm4G73pSPNu9R5ET8YonIuDVoHkHaYvQQizLdRkTyBsYIik8FXsSDmVsw5MLMQ7OAlZ97nQBkz5TxOX6Px766nUpSVoWXoAz6cpIlWnwgir4t1ph88Ph56l+qmqW3gUj/U6MIuzjxBJlijTMHl96its95Nd4cEFx4j+sFuRYob6D0kcemC7xEFuty7bdupda51Z56GYI1YjuUTryTlFOHZbOSThc2ZMzNC1gPG25bn2Lx6sDuPByk4KW4rQ2v7mSfeUuZZRdjtVSC0WV2M2Cv5L8eLFvZRgNYnXrUJYhnRpT+OBAvJZXaWU2nv/bNe/UQIDAQAB
+ # 回调地址
+ notifyUrl: https://count.byffp.top/api/pay/aliPay/pay-notify
+ # 支付宝成功支付跳转页面
+ returnUrl: https://count.byffp.top/statePage/success
+ # 可设置AES密钥,调用AES加解密相关接口时需要(可选)
+ encryptKey:
+
+wechat:
+ gatewayHost: https://fission-server.scrm-ai.com
+ appKey: uNE5DXdjwQa1iE8cBVZJbuvGfr5t9R73
diff --git a/admin/src/main/resources/application-test.yml b/admin/src/main/resources/application-test.yml
new file mode 100644
index 0000000..04e7fd4
--- /dev/null
+++ b/admin/src/main/resources/application-test.yml
@@ -0,0 +1,26 @@
+spring:
+ datasource:
+ url: jdbc:mysql://39.100.77.21:3306/ad_distribute?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
+ username: root
+ password: y7z7noq2
+ redis:
+ host: 39.100.77.21
+ password: sC33HXphkHBRj4Jb
+ port: 6379
+ timeout: 5000
+ database: 7
+
+
+
+#mybatis plus 设置
+mybatis-plus:
+ configuration:
+ #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志
+ log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl #关闭sql日志
+
+springdoc:
+ swagger-ui:
+ urls:
+ - { name: 'admin', url: '/v3/api-docs' }
+ - { name: 'api', url: 'http://ballcat-api/v3/api-docs' }
+
diff --git a/admin/src/main/resources/application.yml b/admin/src/main/resources/application.yml
new file mode 100644
index 0000000..81dd8c3
--- /dev/null
+++ b/admin/src/main/resources/application.yml
@@ -0,0 +1,106 @@
+server:
+ port: 8099
+
+spring:
+ application:
+ name: @artifactId@
+ profiles:
+ active: dev
+ jackson:
+ date-format: yyyy-MM-dd HH:mm:ss
+ time-zone: GMT+8
+
+
+# 天爱图形验证码
+captcha:
+ secondary:
+ enabled: true
+
+# mybatis-plus相关配置
+mybatis-plus:
+ mapper-locations: classpath*:/mapper/**/*Mapper.xml
+ global-config:
+ banner: false
+ db-config:
+ id-type: auto
+ insert-strategy: not_empty
+ update-strategy: not_empty
+ logic-delete-value: "NOW()" # 逻辑已删除值(使用当前时间标识)
+ logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
+
+
+# BallCat 相关配置
+ballcat:
+ security:
+ # 前端传输密码的 AES 加密密钥
+ password-secret-key: '==market-scrm==='
+ oauth2:
+ authorizationserver:
+ # 登陆验证码是否开启
+ login-captcha-enabled: true
+ # 内嵌的表单登陆页是否开启
+ login-page-enabled: false
+ resourceserver:
+ ## 忽略鉴权的 url 列表
+ ignore-urls:
+ - /public/**
+ - /actuator/**
+ - /doc.html
+ - /v2/api-docs/**
+ - /v3/api-docs/**
+ - /swagger-resources/**
+ - /swagger-ui/**
+ - /webjars/**
+ - /bycdao-ui/**
+ - /favicon.ico
+ - /captcha/**
+ - /outside/**
+ - /wechat/global/setting
+ - /pay/aliPay/pay-notify
+ # 项目 redis 缓存的 key 前缀
+ redis:
+ key-prefix: 'marketing:'
+
+springdoc:
+ # 开启 oauth2 端点显示
+ show-oauth2-endpoints: true
+ swagger-ui:
+ oauth:
+ client-id: test
+ client-secret: test
+ display-request-duration: true
+ disable-swagger-default-url: true
+ persist-authorization: true
+
+# 文件存储路径
+file:
+ mac:
+ path: ~/file/
+ avatar: ~/avatar/
+ clueFilePath: ~/cluefile/
+ systemSeparator: /
+ linux:
+ path: /home/marketing-scrm/file/
+ avatar: /home/marketing-scrm/avatar/
+ clueFilePath: /home/marketing-scrm/cluefile/
+ systemSeparator: /
+ windows:
+ path: C:\marketing-scrm\file\
+ avatar: C:\marketing-scrm\avatar\
+ clueFilePath: C:\marketing-scrm\cluefile\
+ systemSeparator: \
+ # 文件大小 /M
+ maxSize: 300
+ avatarMaxSize: 5
+
+
+elasticjob:
+ zookeeper:
+ namespace: springboot-elasticjob
+ server-list: localhost:2181
+
+snowflake:
+ workerId: 10
+ datacenterId: 10
+
+
diff --git a/admin/src/main/resources/file/template.xlsx b/admin/src/main/resources/file/template.xlsx
new file mode 100644
index 0000000..2847f8f
Binary files /dev/null and b/admin/src/main/resources/file/template.xlsx differ
diff --git a/admin/src/main/resources/logback-spring.xml b/admin/src/main/resources/logback-spring.xml
new file mode 100644
index 0000000..77340d5
--- /dev/null
+++ b/admin/src/main/resources/logback-spring.xml
@@ -0,0 +1,88 @@
+
+
+ elAdmin
+
+
+
+
+
+
+
+
+
+
+ %highlight([%-5level]) %cyan(%d{yyyy-MM-dd#HH:mm:ss.SSS}) %yellow([Thread:%thread]) %magenta([Logger:%logger]) -> %msg%n
+ utf-8
+
+
+
+
+
+ ${LOG_DIR}/log.log
+
+
+
+ ${LOG_DIR}/history/%d{yyyy-MM-dd}.gz
+ 30
+
+
+ true
+
+
+ ${LOG_PATTERN}
+ utf-8
+
+
+
+
+ INFO
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/admin/src/main/resources/mapper/LoginEquipmentMapper.xml b/admin/src/main/resources/mapper/LoginEquipmentMapper.xml
new file mode 100644
index 0000000..840935b
--- /dev/null
+++ b/admin/src/main/resources/mapper/LoginEquipmentMapper.xml
@@ -0,0 +1,80 @@
+
+
+
+
+ wa.username,
+ wa.nickname,
+ wa.city_info,
+ wa.login_type,
+ wa.header_url,
+ wa.wx_id,
+ wa.sex,
+ wa.status,
+ wa.we_chat_no,
+ wa.remark,
+ wa.create_time,
+ wa.update_time,
+ wa.user_id,
+ le.id,
+ le.pit,
+ le.robot_id,
+ le.device_number,
+ le.expiration_time
+
+
+
+
+
+
+
+
+
diff --git a/admin/src/main/resources/mapper/WeChatFriendMapper.xml b/admin/src/main/resources/mapper/WeChatFriendMapper.xml
new file mode 100644
index 0000000..414333d
--- /dev/null
+++ b/admin/src/main/resources/mapper/WeChatFriendMapper.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
diff --git a/marketing-scrm-admin/.flattened-pom.xml b/marketing-scrm-admin/.flattened-pom.xml
new file mode 100644
index 0000000..6b2ddb9
--- /dev/null
+++ b/marketing-scrm-admin/.flattened-pom.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+
+ com.baiye
+ marketing-scrm
+ 1.0.0
+
+ com.baiye
+ marketing-scrm-admin
+ 1.0.0
+ pom
+
+ admin-core
+ admin-websocket
+
+
diff --git a/marketing-scrm-admin/admin-core/.flattened-pom.xml b/marketing-scrm-admin/admin-core/.flattened-pom.xml
new file mode 100644
index 0000000..585a7aa
--- /dev/null
+++ b/marketing-scrm-admin/admin-core/.flattened-pom.xml
@@ -0,0 +1,50 @@
+
+
+ 4.0.0
+
+ com.baiye
+ marketing-scrm-admin
+ 1.0.0
+
+ com.baiye
+ admin-core
+ 1.0.0
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+
+
+ com.baiye
+ common-desensitize
+
+
+ com.baiye
+ common-model
+
+
+ com.baiye
+ security-oauth2-authorization-server
+ compile
+
+
+ com.baiye
+ security-oauth2-resource-server
+
+
+ com.baiye
+ system-controller
+
+
+ com.baiye
+ marketing-scrm-starter-web
+ ${revision}
+
+
+ com.baiye
+ marketing-scrm-notify-controller
+ ${revision}
+
+
+
diff --git a/marketing-scrm-admin/admin-core/pom.xml b/marketing-scrm-admin/admin-core/pom.xml
new file mode 100644
index 0000000..bef4e74
--- /dev/null
+++ b/marketing-scrm-admin/admin-core/pom.xml
@@ -0,0 +1,55 @@
+
+
+
+ marketing-scrm-admin
+ com.baiye
+ ${revision}
+
+ 4.0.0
+ admin-core
+
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+
+
+
+ com.baiye
+ common-desensitize
+
+
+ com.baiye
+ common-model
+
+
+
+ com.baiye
+ security-oauth2-authorization-server
+ compile
+
+
+ com.baiye
+ security-oauth2-resource-server
+
+
+ com.baiye
+ system-controller
+
+
+
+ com.baiye
+ marketing-scrm-starter-web
+ ${revision}
+
+
+
+
+ com.baiye
+ marketing-scrm-notify-controller
+ ${revision}
+
+
+
diff --git a/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/UpmsAutoConfiguration.java b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/UpmsAutoConfiguration.java
new file mode 100644
index 0000000..ad346cc
--- /dev/null
+++ b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/UpmsAutoConfiguration.java
@@ -0,0 +1,106 @@
+package com.baiye.upms;
+
+//import com.hccake.ballcat.admin.upms.log.LogConfiguration;
+import com.baiye.system.authentication.BallcatOAuth2TokenResponseEnhancer;
+import com.baiye.system.authentication.DefaultUserInfoCoordinatorImpl;
+import com.baiye.system.authentication.SysUserDetailsServiceImpl;
+import com.baiye.system.authentication.UserInfoCoordinator;
+import com.baiye.system.properties.SystemProperties;
+import com.baiye.system.service.SysUserService;
+import org.ballcat.security.properties.SecurityProperties;
+import org.ballcat.springsecurity.oauth2.server.authorization.web.authentication.OAuth2TokenResponseEnhancer;
+import org.ballcat.springsecurity.oauth2.server.resource.introspection.SpringAuthorizationServerSharedStoredOpaqueTokenIntrospector;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
+
+/**
+ * @author Hccake 2020/5/25 21:01
+ */
+@EnableAsync
+@AutoConfiguration
+@MapperScan("com.baiye.**.mapper")
+// @ComponentScan({ "com.hccake.ballcat.admin.upms", "com.hccake.ballcat.system",
+// "com.hccake.ballcat.log",
+// "com.hccake.ballcat.file", "com.hccake.ballcat.notify" })
+@ComponentScan({ "com.baiye.upms", "com.baiye.system" })
+@EnableConfigurationProperties({ SystemProperties.class, SecurityProperties.class })
+public class UpmsAutoConfiguration {
+
+ /**
+ * 用户详情处理类
+ *
+ * @author hccake
+ */
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass(SysUserService.class)
+ @ConditionalOnMissingBean(UserDetailsService.class)
+ static class UserDetailsServiceConfiguration {
+
+ /**
+ * 用户详情处理类
+ * @return SysUserDetailsServiceImpl
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public UserDetailsService userDetailsService(SysUserService sysUserService,
+ UserInfoCoordinator userInfoCoordinator) {
+ return new SysUserDetailsServiceImpl(sysUserService, userInfoCoordinator);
+ }
+
+ /**
+ * 用户信息协调者
+ * @return UserInfoCoordinator
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public UserInfoCoordinator userInfoCoordinator() {
+ return new DefaultUserInfoCoordinatorImpl();
+ }
+
+ }
+
+ /**
+ * 新版本 spring-security-oauth2-authorization-server 使用配置类
+ */
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass(OAuth2Authorization.class)
+ static class SpringOAuth2AuthorizationServerConfiguration {
+
+ /**
+ * token 端点响应增强,追加一些自定义信息
+ * @return TokenEnhancer Token增强处理器
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public OAuth2TokenResponseEnhancer oAuth2TokenResponseEnhancer() {
+ return new BallcatOAuth2TokenResponseEnhancer();
+ }
+
+ /**
+ * 当资源服务器和授权服务器的 token 共享存储时,直接使用 OAuth2AuthorizationService 读取 token 信息
+ * @return SpringAuthorizationServerSharedStoredOpaqueTokenIntrospector
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ @ConditionalOnProperty(prefix = "ballcat.security.oauth2.resourceserver", name = "shared-stored-token",
+ havingValue = "true", matchIfMissing = true)
+ public OpaqueTokenIntrospector sharedStoredOpaqueTokenIntrospector(
+ OAuth2AuthorizationService authorizationService) {
+ return new SpringAuthorizationServerSharedStoredOpaqueTokenIntrospector(authorizationService);
+ }
+
+ }
+
+}
diff --git a/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/config/mybatis/FillMetaObjectHandle.java b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/config/mybatis/FillMetaObjectHandle.java
new file mode 100644
index 0000000..b8549e9
--- /dev/null
+++ b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/config/mybatis/FillMetaObjectHandle.java
@@ -0,0 +1,42 @@
+package com.baiye.upms.config.mybatis;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.baiye.constant.GlobalConstants;
+import com.baiye.security.userdetails.User;
+import com.baiye.security.util.SecurityUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.reflection.MetaObject;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author Hccake 2019/7/26 14:41
+ */
+@Slf4j
+public class FillMetaObjectHandle implements MetaObjectHandler {
+
+ @Override
+ public void insertFill(MetaObject metaObject) {
+ // 逻辑删除标识
+ this.strictInsertFill(metaObject, "deleted", Long.class, GlobalConstants.NOT_DELETED_FLAG);
+ // 创建时间
+ this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
+ // 创建人
+ User user = SecurityUtils.getUser();
+ if (user != null) {
+ this.strictInsertFill(metaObject, "createBy", Long.class, user.getUserId());
+ }
+ }
+
+ @Override
+ public void updateFill(MetaObject metaObject) {
+ // 修改时间
+ this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
+ // 修改人
+ User user = SecurityUtils.getUser();
+ if (user != null) {
+ this.strictUpdateFill(metaObject, "updateBy", Long.class, user.getUserId());
+ }
+ }
+
+}
diff --git a/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/config/mybatis/MybatisPlusConfig.java b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/config/mybatis/MybatisPlusConfig.java
new file mode 100644
index 0000000..e982996
--- /dev/null
+++ b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/config/mybatis/MybatisPlusConfig.java
@@ -0,0 +1,62 @@
+package com.baiye.upms.config.mybatis;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.baomidou.mybatisplus.core.injector.AbstractMethod;
+import com.baomidou.mybatisplus.core.injector.ISqlInjector;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import com.baiye.extend.mybatis.plus.injector.CustomSqlInjector;
+import com.baiye.extend.mybatis.plus.methods.InsertBatchSomeColumnByCollection;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author hccake
+ * @date 2020/04/19 默认配置MybatisPlus分页插件,通过conditional注解达到覆盖效用
+ */
+@Configuration
+public class MybatisPlusConfig {
+
+ /**
+ * MybatisPlusInterceptor 插件,默认提供分页插件
+ * 如需其他MP内置插件,则需自定义该Bean
+ * @return MybatisPlusInterceptor
+ */
+ @Bean
+ @ConditionalOnMissingBean(MybatisPlusInterceptor.class)
+ public MybatisPlusInterceptor mybatisPlusInterceptor() {
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+ return interceptor;
+ }
+
+ /**
+ * 自动填充处理类
+ * @return FillMetaObjectHandle
+ */
+ @Bean
+ @ConditionalOnMissingBean(MetaObjectHandler.class)
+ public MetaObjectHandler fillMetaObjectHandle() {
+ return new FillMetaObjectHandle();
+ }
+
+ /**
+ * 自定义批量插入方法注入
+ * @return ISqlInjector
+ */
+ @Bean
+ @ConditionalOnMissingBean(ISqlInjector.class)
+ public ISqlInjector customSqlInjector() {
+ List list = new ArrayList<>();
+ // 对于只在更新时进行填充的字段不做插入处理
+ list.add(new InsertBatchSomeColumnByCollection(t -> t.getFieldFill() != FieldFill.UPDATE));
+ return new CustomSqlInjector(list);
+ }
+
+}
diff --git a/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/config/task/MdcTaskDecorator.java b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/config/task/MdcTaskDecorator.java
new file mode 100644
index 0000000..d32fa76
--- /dev/null
+++ b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/config/task/MdcTaskDecorator.java
@@ -0,0 +1,34 @@
+package com.baiye.upms.config.task;
+
+import cn.hutool.core.map.MapUtil;
+import org.slf4j.MDC;
+import org.springframework.core.task.TaskDecorator;
+
+import java.util.Map;
+
+/**
+ * 使async异步任务支持traceId
+ *
+ * @author huyuanzhi 2021-11-06 23:14:27
+ */
+public class MdcTaskDecorator implements TaskDecorator {
+
+ @Override
+ public Runnable decorate(Runnable runnable) {
+ final Map copyOfContextMap = MDC.getCopyOfContextMap();
+ return () -> {
+ if (MapUtil.isNotEmpty(copyOfContextMap)) {
+ // 现在:@Async线程上下文! 恢复Web线程上下文的MDC数据
+ MDC.setContextMap(copyOfContextMap);
+ }
+
+ try {
+ runnable.run();
+ }
+ finally {
+ MDC.clear();
+ }
+ };
+ }
+
+}
diff --git a/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/config/task/TaskExecutionConfiguration.java b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/config/task/TaskExecutionConfiguration.java
new file mode 100644
index 0000000..fa3c9c6
--- /dev/null
+++ b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/config/task/TaskExecutionConfiguration.java
@@ -0,0 +1,35 @@
+package com.baiye.upms.config.task;
+
+import org.springframework.boot.task.TaskExecutorCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 线程池构建,原本的线程池的拒绝策略为直接抛出异常,不太友好
+ *
+ * @see org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
+ * @author hccake
+ */
+@Configuration(proxyBeanMethods = false)
+public class TaskExecutionConfiguration {
+
+ /**
+ * 修改 springboot 默认配置的 taskExecutor 的拒绝策略为使用当前线程执行
+ * @return TaskExecutorCustomizer
+ */
+ @Bean
+ public TaskExecutorCustomizer taskExecutorCustomizer() {
+ // AbortPolicy: 直接抛出java.util.concurrent.RejectedExecutionException异常
+ // CallerRunsPolicy: 主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度
+ // DiscardOldestPolicy: 抛弃旧的任务、暂不支持;会导致被丢弃的任务无法再次被执行
+ // DiscardPolicy: 抛弃当前任务、暂不支持;会导致被丢弃的任务无法再次被执行
+ // 这里使用主线程直接执行该任务
+ return (taskExecutor -> {
+ taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+ taskExecutor.setTaskDecorator(new MdcTaskDecorator());
+ });
+ }
+
+}
diff --git a/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/log/LogConfiguration.java b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/log/LogConfiguration.java
new file mode 100644
index 0000000..806f551
--- /dev/null
+++ b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/log/LogConfiguration.java
@@ -0,0 +1,78 @@
+// package com.hccake.ballcat.admin.upms.log;
+//
+// import com.hccake.ballcat.common.log.access.handler.AccessLogHandler;
+// import com.hccake.ballcat.common.log.operation.handler.OperationLogHandler;
+// import com.hccake.ballcat.log.handler.CustomAccessLogHandler;
+// import com.hccake.ballcat.log.handler.CustomOperationLogHandler;
+// import com.hccake.ballcat.log.model.entity.AccessLog;
+// import com.hccake.ballcat.log.model.entity.OperationLog;
+// import com.hccake.ballcat.log.service.AccessLogService;
+// import com.hccake.ballcat.log.service.LoginLogService;
+// import com.hccake.ballcat.log.service.OperationLogService;
+// import com.hccake.ballcat.log.thread.AccessLogSaveThread;
+// import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+// import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+// import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+// import org.springframework.context.annotation.Bean;
+// import org.springframework.context.annotation.Configuration;
+// import
+// org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
+// import
+// org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
+//
+/// **
+// * @author hccake
+// */
+// @Configuration(proxyBeanMethods = false)
+// @ConditionalOnClass(LoginLogService.class)
+// public class LogConfiguration {
+//
+// /**
+// * 访问日志保存
+// * @param accessLogService 访问日志Service
+// * @return CustomAccessLogHandler
+// */
+// @Bean
+// @ConditionalOnBean(AccessLogService.class)
+// @ConditionalOnMissingBean(AccessLogHandler.class)
+// public AccessLogHandler customAccessLogHandler(AccessLogService
+// accessLogService) {
+// return new CustomAccessLogHandler(new AccessLogSaveThread(accessLogService));
+// }
+//
+// /**
+// * 操作日志处理器
+// * @param operationLogService 操作日志Service
+// * @return CustomOperationLogHandler
+// */
+// @Bean
+// @ConditionalOnBean(OperationLogService.class)
+// @ConditionalOnMissingBean(OperationLogHandler.class)
+// public OperationLogHandler customOperationLogHandler(OperationLogService
+// operationLogService) {
+// return new CustomOperationLogHandler(operationLogService);
+// }
+//
+// @ConditionalOnClass(OAuth2AuthorizationServerConfigurer.class)
+// @ConditionalOnBean(LoginLogService.class)
+// @ConditionalOnMissingBean(LoginLogHandler.class)
+// @Configuration(proxyBeanMethods = false)
+// static class SpringAuthorizationServerLoginLogConfiguration {
+//
+// /**
+// * Spring Authorization Server 的登录日志处理,监听登录事件记录登录登出
+// * @param loginLogService 操作日志Service
+// * @param authorizationServerSettings 授权服务器设置
+// * @return SpringAuthorizationServerLoginLogHandler
+// */
+// @Bean
+// public LoginLogHandler springAuthorizationServerLoginLogHandler(LoginLogService
+// loginLogService,
+// AuthorizationServerSettings authorizationServerSettings) {
+// return new SpringAuthorizationServerLoginLogHandler(loginLogService,
+// authorizationServerSettings);
+// }
+//
+// }
+//
+// }
\ No newline at end of file
diff --git a/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/log/LoginLogHandler.java b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/log/LoginLogHandler.java
new file mode 100644
index 0000000..646b19c
--- /dev/null
+++ b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/log/LoginLogHandler.java
@@ -0,0 +1,8 @@
+package com.baiye.upms.log;
+
+/**
+ * @author hccake
+ */
+public interface LoginLogHandler {
+
+}
diff --git a/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/log/SpringAuthorizationServerLoginLogHandler.java b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/log/SpringAuthorizationServerLoginLogHandler.java
new file mode 100644
index 0000000..dd3cd6e
--- /dev/null
+++ b/marketing-scrm-admin/admin-core/src/main/java/com/baiye/upms/log/SpringAuthorizationServerLoginLogHandler.java
@@ -0,0 +1,140 @@
+// package com.hccake.ballcat.admin.upms.log;
+//
+// import com.hccake.ballcat.common.core.util.WebUtils;
+//// import com.hccake.ballcat.common.log.operation.enums.LogStatusEnum;
+// import com.hccake.ballcat.common.security.util.SecurityUtils;
+//// import com.hccake.ballcat.log.enums.LoginEventTypeEnum;
+//// import com.hccake.ballcat.log.model.entity.LoginLog;
+//// import com.hccake.ballcat.log.service.LoginLogService;
+// import lombok.RequiredArgsConstructor;
+// import
+// org.ballcat.springsecurity.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken;
+// import org.springframework.context.event.EventListener;
+// import org.springframework.security.authentication.ProviderNotFoundException;
+// import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+// import
+// org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
+// import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
+// import org.springframework.security.authentication.event.LogoutSuccessEvent;
+// import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
+// import
+// org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
+// import
+// org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
+// import
+// org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
+//
+// import javax.servlet.http.HttpServletRequest;
+//
+//// import static com.hccake.ballcat.log.handler.LoginLogUtils.prodLoginLog;
+//
+/// **
+// * spring 授权服务器的登录日志处理器
+// *
+// * @author hccake
+// */
+// @RequiredArgsConstructor
+// public class SpringAuthorizationServerLoginLogHandler implements LoginLogHandler {
+//
+//// private final LoginLogService loginLogService;
+//
+// private final AuthorizationServerSettings authorizationServerSettings;
+//
+// /**
+// * 登录成功事件监听 记录用户登录日志
+// * @param event 登录成功 event
+// */
+// @EventListener(AuthenticationSuccessEvent.class)
+// public void onAuthenticationSuccessEvent(AuthenticationSuccessEvent event) {
+// Object source = event.getSource();
+// String username = null;
+//
+// String tokenEndpoint = authorizationServerSettings.getTokenEndpoint();
+// HttpServletRequest request = WebUtils.getRequest();
+// boolean isOauth2LoginRequest = request.getRequestURI().equals(tokenEndpoint);
+//
+// // Oauth2登录 和表单登录 处理分开
+// if (isOauth2LoginRequest && source instanceof OAuth2AccessTokenAuthenticationToken) {
+// username = SecurityUtils.getAuthentication().getName();
+// }
+// else if (!isOauth2LoginRequest && source instanceof
+// UsernamePasswordAuthenticationToken) {
+// username = ((UsernamePasswordAuthenticationToken) source).getName();
+// }
+//
+//// if (username != null) {
+//// LoginLog loginLog = prodLoginLog(username).setMsg("登录成功")
+//// .setStatus(LogStatusEnum.SUCCESS.getValue())
+//// .setEventType(LoginEventTypeEnum.LOGIN.getValue());
+//// loginLogService.save(loginLog);
+//// }
+// }
+//
+// /**
+// * 监听鉴权失败事件,记录登录失败日志
+// * @param event the event
+// */
+// @EventListener(AbstractAuthenticationFailureEvent.class)
+// public void onAuthenticationFailureEvent(AbstractAuthenticationFailureEvent event) {
+// if (event.getException().getClass().isAssignableFrom(ProviderNotFoundException.class))
+// {
+// return;
+// }
+//
+// Object source = event.getSource();
+// String username = null;
+//
+// String tokenEndpoint = authorizationServerSettings.getTokenEndpoint();
+// HttpServletRequest request = WebUtils.getRequest();
+// boolean isOauth2LoginRequest = request.getRequestURI().equals(tokenEndpoint);
+//
+// // Oauth2登录 和表单登录 处理分开
+// if (isOauth2LoginRequest && source instanceof
+// OAuth2AuthorizationGrantAuthenticationToken) {
+// username = ((OAuth2AuthorizationGrantAuthenticationToken) source).getName();
+// }
+// else if (!isOauth2LoginRequest && source instanceof
+// UsernamePasswordAuthenticationToken) {
+// username = ((UsernamePasswordAuthenticationToken) source).getName();
+// }
+//
+//// if (username != null) {
+//// LoginLog loginLog = prodLoginLog(username).setMsg(event.getException().getMessage())
+//// .setEventType(LoginEventTypeEnum.LOGIN.getValue())
+//// .setStatus(LogStatusEnum.FAIL.getValue());
+//// loginLogService.save(loginLog);
+//// }
+// }
+//
+// /**
+// * 登出成功事件监听
+// * @param event the event
+// */
+// @EventListener(LogoutSuccessEvent.class)
+// public void onLogoutSuccessEvent(LogoutSuccessEvent event) {
+// Object source = event.getSource();
+// String username = null;
+//
+// String tokenRevocationEndpoint =
+// authorizationServerSettings.getTokenRevocationEndpoint();
+// HttpServletRequest request = WebUtils.getRequest();
+// boolean isOauth2Login = request.getRequestURI().equals(tokenRevocationEndpoint);
+//
+// // Oauth2撤销令牌 和表单登出 处理分开
+// if (isOauth2Login && source instanceof OAuth2TokenRevocationAuthenticationToken) {
+// OAuth2Authorization authorization = ((OAuth2TokenRevocationAuthenticationToken)
+// source).getAuthorization();
+// username = authorization.getPrincipalName();
+// }
+// else if (!isOauth2Login && source instanceof UsernamePasswordAuthenticationToken) {
+// username = ((UsernamePasswordAuthenticationToken) source).getName();
+// }
+//
+//// if (username != null) {
+//// LoginLog loginLog = prodLoginLog(username).setMsg("登出成功")
+//// .setEventType(LoginEventTypeEnum.LOGOUT.getValue());
+//// loginLogService.save(loginLog);
+//// }
+// }
+//
+// }
diff --git a/marketing-scrm-admin/admin-core/src/main/resources/META-INF/spring.factories b/marketing-scrm-admin/admin-core/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..44aa29b
--- /dev/null
+++ b/marketing-scrm-admin/admin-core/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ com.baiye.upms.UpmsAutoConfiguration
\ No newline at end of file
diff --git a/marketing-scrm-admin/admin-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/marketing-scrm-admin/admin-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..8b53834
--- /dev/null
+++ b/marketing-scrm-admin/admin-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+com.baiye.upms.UpmsAutoConfiguration
\ No newline at end of file
diff --git a/marketing-scrm-admin/admin-websocket/.flattened-pom.xml b/marketing-scrm-admin/admin-websocket/.flattened-pom.xml
new file mode 100644
index 0000000..e2e2687
--- /dev/null
+++ b/marketing-scrm-admin/admin-websocket/.flattened-pom.xml
@@ -0,0 +1,31 @@
+
+
+ 4.0.0
+
+ com.baiye
+ marketing-scrm-admin
+ 1.0.0
+
+ com.baiye
+ admin-websocket
+ 1.0.0
+
+
+ com.baiye
+ admin-core
+ 1.0.0
+
+
+ com.baiye
+ marketing-scrm-starter-websocket
+ 1.0.0
+
+
+ com.baiye
+ distribute-notify-biz
+ 1.0.0
+ compile
+
+
+
diff --git a/marketing-scrm-admin/admin-websocket/pom.xml b/marketing-scrm-admin/admin-websocket/pom.xml
new file mode 100644
index 0000000..3cd0d34
--- /dev/null
+++ b/marketing-scrm-admin/admin-websocket/pom.xml
@@ -0,0 +1,32 @@
+
+
+
+ marketing-scrm-admin
+ com.baiye
+ ${revision}
+
+ 4.0.0
+ admin-websocket
+
+
+
+ com.baiye
+ admin-core
+ 1.0.0
+
+
+ com.baiye
+ marketing-scrm-starter-websocket
+ 1.0.0
+
+
+
+ com.baiye
+ distribute-notify-biz
+ 1.0.0
+ compile
+
+
+
+
diff --git a/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/AdminWebSocketAutoConfiguration.java b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/AdminWebSocketAutoConfiguration.java
new file mode 100644
index 0000000..5c8a6be
--- /dev/null
+++ b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/AdminWebSocketAutoConfiguration.java
@@ -0,0 +1,34 @@
+package com.baiye;
+
+import com.baiye.component.UserAttributeHandshakeInterceptor;
+import com.baiye.component.UserSessionKeyGenerator;
+import com.baiye.session.SessionKeyGenerator;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.web.socket.server.HandshakeInterceptor;
+
+/**
+ * @author Hccake 2021/1/5
+ * @version 1.0
+ */
+@Import({ SystemWebsocketEventListenerConfiguration.class})
+@Configuration
+@RequiredArgsConstructor
+public class AdminWebSocketAutoConfiguration {
+
+
+
+ @Bean
+ @ConditionalOnMissingBean(SessionKeyGenerator.class)
+ public SessionKeyGenerator userSessionKeyGenerator() {
+ return new UserSessionKeyGenerator();
+ }
+
+ @Bean
+ public HandshakeInterceptor authenticationHandshakeInterceptor() {
+ return new UserAttributeHandshakeInterceptor();
+ }
+}
diff --git a/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/SystemWebsocketEventListenerConfiguration.java b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/SystemWebsocketEventListenerConfiguration.java
new file mode 100644
index 0000000..9675c05
--- /dev/null
+++ b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/SystemWebsocketEventListenerConfiguration.java
@@ -0,0 +1,25 @@
+package com.baiye;
+
+import com.baiye.listener.SystemWebsocketEventListener;
+import com.baiye.distribute.MessageDistributor;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Hccake
+ */
+@RequiredArgsConstructor
+@ConditionalOnClass(SystemWebsocketEventListener.class)
+@Configuration(proxyBeanMethods = false)
+public class SystemWebsocketEventListenerConfiguration {
+
+ private final MessageDistributor messageDistributor;
+
+ @Bean
+ public SystemWebsocketEventListener systemWebsocketEventListener() {
+ return new SystemWebsocketEventListener(messageDistributor);
+ }
+
+}
diff --git a/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/component/UserAttributeHandshakeInterceptor.java b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/component/UserAttributeHandshakeInterceptor.java
new file mode 100644
index 0000000..902e30b
--- /dev/null
+++ b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/component/UserAttributeHandshakeInterceptor.java
@@ -0,0 +1,65 @@
+package com.baiye.component;
+
+import com.baiye.constant.AdminWebSocketConstants;
+import com.baiye.security.userdetails.User;
+import com.baiye.security.util.SecurityUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.server.HandshakeInterceptor;
+
+import java.util.Map;
+
+/**
+ * WebSocket 握手拦截器 在握手时记录下当前 session 对应的用户Id和token信息
+ *
+ * @author Hccake 2021/1/4
+ * @version 1.0
+ */
+@RequiredArgsConstructor
+public class UserAttributeHandshakeInterceptor implements HandshakeInterceptor {
+
+ /**
+ * Invoked before the handshake is processed.
+ * @param request the current request
+ * @param response the current response
+ * @param wsHandler the target WebSocket handler
+ * @param attributes the attributes from the HTTP handshake to associate with the
+ * WebSocket session; the provided attributes are copied, the original map is not
+ * used.
+ * @return whether to proceed with the handshake ({@code true}) or abort
+ * ({@code false})
+ */
+ @Override
+ public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
+ Map attributes) {
+ String accessToken = null;
+ // 获得 accessToken
+ if (request instanceof ServletServerHttpRequest) {
+ ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request;
+ accessToken = serverRequest.getServletRequest().getParameter(AdminWebSocketConstants.TOKEN_ATTR_NAME);
+ }
+ // 由于 WebSocket 握手是由 http 升级的,携带 token 已经被 Security 拦截验证了,所以可以直接获取到用户
+ User user = SecurityUtils.getUser();
+ attributes.put(AdminWebSocketConstants.TOKEN_ATTR_NAME, accessToken);
+ attributes.put(AdminWebSocketConstants.USER_KEY_ATTR_NAME, user.getUserId());
+ return true;
+ }
+
+ /**
+ * Invoked after the handshake is done. The response status and headers indicate the
+ * results of the handshake, i.e. whether it was successful or not.
+ * @param request the current request
+ * @param response the current response
+ * @param wsHandler the target WebSocket handler
+ * @param exception an exception raised during the handshake, or {@code null} if none
+ */
+ @Override
+ public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
+ Exception exception) {
+ // doNothing
+ }
+
+}
diff --git a/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/component/UserSessionKeyGenerator.java b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/component/UserSessionKeyGenerator.java
new file mode 100644
index 0000000..d8760f0
--- /dev/null
+++ b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/component/UserSessionKeyGenerator.java
@@ -0,0 +1,32 @@
+package com.baiye.component;
+
+import com.baiye.constant.AdminWebSocketConstants;
+import com.baiye.session.SessionKeyGenerator;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.socket.WebSocketSession;
+
+/**
+ *
+ * 用户 WebSocketSession 唯一标识生成器
+ *
+ *
+ * 此类主要使用当前 session 对应用户的唯一标识做为 session 的唯一标识 方便系统快速通过用户获取对应 session
+ *
+ * @author Hccake 2021/1/5
+ * @version 1.0
+ */
+@RequiredArgsConstructor
+public class UserSessionKeyGenerator implements SessionKeyGenerator {
+
+ /**
+ * 获取当前session的唯一标识,用户的唯一标识已经通过
+ * @see UserAttributeHandshakeInterceptor 存储在当前 session 的属性中
+ * @param webSocketSession 当前session
+ * @return session唯一标识
+ */
+ @Override
+ public Object sessionKey(WebSocketSession webSocketSession) {
+ return webSocketSession.getAttributes().get(AdminWebSocketConstants.USER_KEY_ATTR_NAME);
+ }
+
+}
diff --git a/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/constant/AdminWebSocketConstants.java b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/constant/AdminWebSocketConstants.java
new file mode 100644
index 0000000..183b2d1
--- /dev/null
+++ b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/constant/AdminWebSocketConstants.java
@@ -0,0 +1,22 @@
+package com.baiye.constant;
+
+/**
+ * @author Hccake 2021/1/5
+ * @version 1.0
+ */
+public final class AdminWebSocketConstants {
+
+ private AdminWebSocketConstants() {
+ }
+
+ /**
+ * 存储在 WebSocketSession Attribute 中的 token 属性名
+ */
+ public static final String TOKEN_ATTR_NAME = "access_token";
+
+ /**
+ * 存储在 WebSocketSession Attribute 中的 用户唯一标识 属性名
+ */
+ public static final String USER_KEY_ATTR_NAME = "userId";
+
+}
diff --git a/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/listener/NotifyWebsocketEventListener.java b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/listener/NotifyWebsocketEventListener.java
new file mode 100644
index 0000000..e8aeadd
--- /dev/null
+++ b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/listener/NotifyWebsocketEventListener.java
@@ -0,0 +1,86 @@
+package com.baiye.listener;
+
+
+import com.baiye.distribute.MessageDO;
+import com.baiye.distribute.MessageDistributor;
+import com.baiye.message.AnnouncementCloseMessage;
+import com.baiye.notify.event.AnnouncementCloseEvent;
+import com.baiye.notify.event.StationNotifyPushEvent;
+import com.baiye.notify.handler.NotifyInfoDelegateHandler;
+import com.baiye.notify.model.domain.NotifyInfo;
+import com.baiye.util.JsonUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalTime;
+import java.util.List;
+
+/**
+ * @author Hccake 2021/1/5
+ * @version 1.0
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class NotifyWebsocketEventListener {
+
+ private final MessageDistributor messageDistributor;
+
+ private final NotifyInfoDelegateHandler super NotifyInfo> notifyInfoDelegateHandler;
+
+ /**
+ * 公告关闭事件监听
+ *
+ * @param event the AnnouncementCloseEvent
+ */
+ @Async
+ @EventListener(AnnouncementCloseEvent.class)
+ public void onAnnouncementCloseEvent(AnnouncementCloseEvent event) {
+ // 构建公告关闭的消息体
+ AnnouncementCloseMessage message = new AnnouncementCloseMessage();
+ message.setId(event.getId());
+ String msg = JsonUtils.toJson(message);
+
+ // 广播公告关闭信息
+ MessageDO messageDO = new MessageDO().setMessageText(msg).setNeedBroadcast(true);
+ messageDistributor.distribute(messageDO);
+ }
+
+ /**
+ * 站内通知推送事件
+ *
+ * @param event the StationNotifyPushEvent
+ */
+ @Async
+ @EventListener(StationNotifyPushEvent.class)
+ public void onAnnouncementPublishEvent(StationNotifyPushEvent event) {
+ log.info("============ onAnnouncementPublishEvent time {} ============", LocalTime.now());
+ NotifyInfo notifyInfo = event.getNotifyInfo();
+ List userList = event.getUserIdList();
+ notifyInfoDelegateHandler.handle(userList, notifyInfo);
+ }
+
+
+
+
+ /**
+ * 公告关闭事件监听
+ *
+ * @param event the AnnouncementCloseEvent
+ */
+ @Async
+ @EventListener(AnnouncementCloseEvent.class)
+ public void read(AnnouncementCloseEvent event) {
+ // 构建公告关闭的消息体
+ AnnouncementCloseMessage message = new AnnouncementCloseMessage();
+ message.setId(event.getId());
+ String msg = JsonUtils.toJson(message);
+
+ // 广播公告关闭信息
+ MessageDO messageDO = new MessageDO().setMessageText(msg).setNeedBroadcast(true);
+ messageDistributor.distribute(messageDO);
+ }
+}
diff --git a/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/listener/SystemWebsocketEventListener.java b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/listener/SystemWebsocketEventListener.java
new file mode 100644
index 0000000..3bd2718
--- /dev/null
+++ b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/listener/SystemWebsocketEventListener.java
@@ -0,0 +1,34 @@
+package com.baiye.listener;
+
+import com.baiye.util.JsonUtils;
+import com.baiye.distribute.MessageDO;
+import com.baiye.distribute.MessageDistributor;
+import com.baiye.system.event.DictChangeEvent;
+import com.baiye.message.DictChangeMessage;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+
+@RequiredArgsConstructor
+public class SystemWebsocketEventListener {
+
+ private final MessageDistributor messageDistributor;
+
+ /**
+ * 字典修改事件监听
+ * @param event the `DictChangeEvent`
+ */
+ @Async
+ @EventListener(DictChangeEvent.class)
+ public void onDictChangeEvent(DictChangeEvent event) {
+ // 构建字典修改的消息体
+ DictChangeMessage dictChangeMessage = new DictChangeMessage();
+ dictChangeMessage.setDictCode(event.getDictCode());
+ String msg = JsonUtils.toJson(dictChangeMessage);
+
+ // 广播修改信息
+ MessageDO messageDO = new MessageDO().setMessageText(msg).setNeedBroadcast(true);
+ messageDistributor.distribute(messageDO);
+ }
+
+}
diff --git a/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/message/AnnouncementCloseMessage.java b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/message/AnnouncementCloseMessage.java
new file mode 100644
index 0000000..3381100
--- /dev/null
+++ b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/message/AnnouncementCloseMessage.java
@@ -0,0 +1,23 @@
+package com.baiye.message;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author Hccake 2021/1/7
+ * @version 1.0
+ */
+@Getter
+@Setter
+public class AnnouncementCloseMessage extends JsonWebSocketMessage {
+
+ public AnnouncementCloseMessage() {
+ super("announcement-close");
+ }
+
+ /**
+ * ID
+ */
+ private Long id;
+
+}
diff --git a/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/message/DictChangeMessage.java b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/message/DictChangeMessage.java
new file mode 100644
index 0000000..3f4ba64
--- /dev/null
+++ b/marketing-scrm-admin/admin-websocket/src/main/java/com/baiye/message/DictChangeMessage.java
@@ -0,0 +1,25 @@
+package com.baiye.message;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 字典修改消息
+ *
+ * @author Hccake 2021/1/5
+ * @version 1.0
+ */
+@Getter
+@Setter
+public class DictChangeMessage extends JsonWebSocketMessage {
+
+ public DictChangeMessage() {
+ super("dict-change");
+ }
+
+ /**
+ * 改动的字典标识
+ */
+ private String dictCode;
+
+}
diff --git a/marketing-scrm-admin/admin-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/marketing-scrm-admin/admin-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..385abad
--- /dev/null
+++ b/marketing-scrm-admin/admin-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+com.baiye.AdminWebSocketAutoConfiguration
\ No newline at end of file
diff --git a/marketing-scrm-admin/pom.xml b/marketing-scrm-admin/pom.xml
new file mode 100644
index 0000000..46e7497
--- /dev/null
+++ b/marketing-scrm-admin/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ com.baiye
+ marketing-scrm
+ ${revision}
+
+ 4.0.0
+ marketing-scrm-admin
+ pom
+
+
+ admin-core
+ admin-websocket
+
+
diff --git a/marketing-scrm-common/common-core/pom.xml b/marketing-scrm-common/common-core/pom.xml
new file mode 100644
index 0000000..759c894
--- /dev/null
+++ b/marketing-scrm-common/common-core/pom.xml
@@ -0,0 +1,85 @@
+
+
+
+ marketing-scrm-common
+ com.baiye
+ ${revision}
+
+ 4.0.0
+ common-core
+
+
+
+
+ cn.hutool
+ hutool-all
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+ com.baiye
+ common-model
+ ${revision}
+
+
+ com.baiye
+ common-util
+ ${revision}
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ compile
+
+
+ org.glassfish
+ jakarta.el
+ 3.0.3
+ test
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.springframework
+ spring-context
+
+
+
+ org.springframework
+ spring-web
+
+
+
+ com.google.guava
+ guava
+ 23.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+ 2.3.2.RELEASE
+
+
+
+ net.lingala.zip4j
+ zip4j
+
+
+
+
+
+
+
diff --git a/marketing-scrm-common/common-core/src/main/java/com/baiye/compose/ContextComponent.java b/marketing-scrm-common/common-core/src/main/java/com/baiye/compose/ContextComponent.java
new file mode 100644
index 0000000..ba16307
--- /dev/null
+++ b/marketing-scrm-common/common-core/src/main/java/com/baiye/compose/ContextComponent.java
@@ -0,0 +1,26 @@
+package com.baiye.compose;
+
+/**
+ * 上下文组件, 在接入对应的上下文时(如: spring 的 bean) 便于在 开始和结束时执行对应的方法
+ *
+ * 默认自动接入spring
+ *
+ *
+ * 一般用于线程类实例达成接入到对应的上下文环境时自动开启和结束线程
+ *
+ *
+ * @author lingting 2022/10/15 17:55
+ */
+public interface ContextComponent {
+
+ /**
+ * 上下文准备好之后调用, 内部做一些线程的初始化以及线程启动
+ */
+ void onApplicationStart();
+
+ /**
+ * 在上下文销毁前调用, 内部做线程停止和数据缓存相关
+ */
+ void onApplicationStop();
+
+}
diff --git a/marketing-scrm-common/common-core/src/main/java/com/baiye/config/RedisCacheConfig.java b/marketing-scrm-common/common-core/src/main/java/com/baiye/config/RedisCacheConfig.java
new file mode 100644
index 0000000..2692869
--- /dev/null
+++ b/marketing-scrm-common/common-core/src/main/java/com/baiye/config/RedisCacheConfig.java
@@ -0,0 +1,46 @@
+package com.baiye.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+
+/**
+ * redis 缓存配置
+ */
+@Slf4j
+@Configuration
+public class RedisCacheConfig{
+ //编写我们自己的RedisTemplate
+ // 自己定义了一个 RedisTemplate
+ @Bean
+ @SuppressWarnings("all")
+ public RedisTemplate