Commit 44629732 by yuwei

项目初始化

parent 2485cef0
......@@ -203,11 +203,4 @@ spring:
- name: Hystrix
args:
name: workflowHystrix
fallbackUri: forward:/fallback
# 即时通讯消息中心
- id: datax-service-websocket
uri: ws://localhost:9876
predicates:
- Path=/websocket/**
filters:
- StripPrefix=1
\ No newline at end of file
fallbackUri: forward:/fallback
\ No newline at end of file
# 数据源配置
spring:
redis:
database: 1
host: localhost
port: 6379
password: 1234@abcd # 密码(默认为空)
timeout: 6000ms # 连接超时时长(毫秒)
lettuce:
pool:
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 10 # 连接池中的最大空闲连接
min-idle: 5 # 连接池中的最小空闲连接
datasource:
dynamic:
primary: mysql
datasource:
mysql:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://localhost:3306/data_cloud?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 1234@abcd
mybatis-plus:
mapper-locations: classpath*:mapper/*Mapper.xml
type-aliases-package: cn.datax.service.websocket.api.entity
global-config:
db-config:
id-type: ASSIGN_ID
select-strategy: not_empty
insert-strategy: not_empty
update-strategy: not_empty
banner: false
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
call-setters-on-nulls: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# tio 配置
tio:
websocket:
server:
# websocket port default 9876
port: 9876
# 心跳时间
heartbeat-timeout: 60000
use-scanner: true
# 集群配置 默认关闭
# 集群参考 https://www.t-io.org/doc/tio/322
cluster:
enabled: false
# 集群是通过redis的Pub/Sub实现,所以需要配置Redis
redis:
ip: localhost
port: 6379
password: 1234@abcd
all: true
group: true
ip: true
user: true
# spring security 配置
security:
oauth2:
client:
access-token-uri: http://localhost:8612/auth/oauth/token
user-authorization-uri: http://localhost:8612/auth/oauth/authorize
client-id: datax
client-secret: 123456
scope: all
resource:
loadBalanced: true
token-info-uri: http://localhost:8612/auth/oauth/check_token
# Swagger界面内容配置
swagger:
enable: true
title: API接口文档
description: Api Documentation
version: 1.0.0
basePackage: cn.datax.service.websocket.controller
termsOfServiceUrl: http://www.baidu.com
contact:
name: yuwei
url: http://www.baidu.com
email: 312075478@qq.com
......@@ -40,7 +40,7 @@ public class RabbitMqListenerConfig {
*/
@RabbitListener(bindings = @QueueBinding(exchange = @Exchange(name = RabbitMqConstant.FANOUT_EXCHANGE_API, type = "fanout", durable = "true", autoDelete = "false"),
value = @Queue(value = RabbitMqConstant.FANOUT_API_QUEUE, durable = "true", exclusive = "false", autoDelete = "false")))
public String fanoutQueueRelease(Map map, Channel channel, Message message) throws Exception {
public void fanoutQueueRelease(Map map, Channel channel, Message message) throws Exception {
try {
String id = (String) map.get("id");
String type = (String) map.get("type");
......@@ -55,7 +55,6 @@ public class RabbitMqListenerConfig {
}
// 手动确认
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
return id;
}catch (Exception e){
log.error("全局异常信息ex={}, StackTrace={}", e.getMessage(), ThrowableUtil.getStackTrace(e));
if (message.getMessageProperties().getRedelivered()){
......@@ -69,6 +68,5 @@ public class RabbitMqListenerConfig {
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}
return null;
}
}
......@@ -20,14 +20,17 @@ import cn.datax.service.data.market.mapstruct.DataApiMapper;
import cn.datax.service.data.market.dao.DataApiDao;
import cn.datax.common.base.BaseServiceImpl;
import cn.datax.service.data.market.utils.SqlBuilderUtil;
import cn.datax.service.data.metadata.api.entity.MetadataAuthorizeEntity;
import cn.datax.service.data.metadata.api.entity.MetadataColumnEntity;
import cn.datax.service.data.metadata.api.entity.MetadataSourceEntity;
import cn.datax.service.data.metadata.api.entity.MetadataTableEntity;
import cn.datax.service.data.metadata.api.enums.DataLevel;
import cn.hutool.core.util.StrUtil;
import com.aspose.words.Document;
import com.aspose.words.MailMerge;
import com.aspose.words.net.System.Data.DataRow;
import com.aspose.words.net.System.Data.DataTable;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
......@@ -58,6 +61,7 @@ import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* <p>
......@@ -171,6 +175,7 @@ public class DataApiServiceImpl extends BaseServiceImpl<DataApiDao, DataApiEntit
List<ResParam> resParams = new ArrayList<>();
List<MetadataSourceEntity> sourceEntityList = (List<MetadataSourceEntity>) redisService.get(RedisConstant.METADATA_SOURCE_KEY);
MetadataSourceEntity sourceEntity = sourceEntityList.stream().filter(s -> sourceId.equals(s.getId())).findFirst().orElse(null);
boolean admin = SecurityUtil.isAdmin();
if (sourceEntity != null) {
List<MetadataTableEntity> tableEntityList = (List<MetadataTableEntity>) redisService.hget(RedisConstant.METADATA_TABLE_KEY, sourceEntity.getId());
Map<String, List<Map<String, String>>> map = cols.stream().collect(Collectors.groupingBy(e -> e.get("tableName").toString()));
......@@ -183,7 +188,19 @@ public class DataApiServiceImpl extends BaseServiceImpl<DataApiDao, DataApiEntit
entryValue.stream().forEach(m -> {
String columnName = m.get("columnName").toLowerCase();
String columnAliasName = m.get("columnAliasName");
MetadataColumnEntity columnEntity = columnEntityList.stream().filter(c -> columnName.equals(c.getColumnName().toLowerCase())).findFirst().orElse(null);
Stream<MetadataColumnEntity> stream = columnEntityList.stream().filter(c -> columnName.equals(c.getColumnName().toLowerCase()));
if (!admin) {
Set<String> set = new HashSet<>();
List<String> roleIds = SecurityUtil.getUserRoleIds();
roleIds.stream().forEach(role -> {
List<MetadataAuthorizeEntity> list = (List<MetadataAuthorizeEntity>) redisService.hget(RedisConstant.METADATA_AUTHORIZE_KEY, role);
set.addAll(Optional.ofNullable(list).orElseGet(ArrayList::new).stream()
.filter(s -> Objects.equals(DataLevel.COLUMN.getKey(), s.getObjectType()))
.map(s -> s.getObjectId()).collect(Collectors.toSet()));
});
stream = stream.filter(s -> set.contains(s.getId()));
}
MetadataColumnEntity columnEntity = stream.findFirst().orElse(null);
if (columnEntity != null) {
ResParam resParam = new ResParam();
resParam.setFieldName(columnEntity.getColumnName());
......@@ -367,13 +384,11 @@ public class DataApiServiceImpl extends BaseServiceImpl<DataApiDao, DataApiEntit
Map<String, Object> map = new HashMap<>(2);
map.put("id", id);
map.put("type", "1");
String obj = (String) Optional.ofNullable(rabbitTemplate.convertSendAndReceive(RabbitMqConstant.FANOUT_API_QUEUE, "", map)).orElse("");
if (StrUtil.isNotBlank(obj)) {
DataApiEntity dataApiEntity = new DataApiEntity();
dataApiEntity.setId(id);
dataApiEntity.setStatus(DataConstant.ApiState.RELEASE.getKey());
dataApiDao.updateById(dataApiEntity);
}
rabbitTemplate.convertAndSend(RabbitMqConstant.FANOUT_API_QUEUE, "", map);
LambdaUpdateWrapper<DataApiEntity> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(DataApiEntity::getStatus, DataConstant.ApiState.RELEASE.getKey());
updateWrapper.eq(DataApiEntity::getId, id);
dataApiDao.update(null, updateWrapper);
}
@Override
......@@ -381,13 +396,11 @@ public class DataApiServiceImpl extends BaseServiceImpl<DataApiDao, DataApiEntit
Map<String, Object> map = new HashMap<>(2);
map.put("id", id);
map.put("type", "2");
String obj = (String) Optional.ofNullable(rabbitTemplate.convertSendAndReceive(RabbitMqConstant.FANOUT_API_QUEUE, "", map)).orElse("");
if (StrUtil.isNotBlank(obj)) {
DataApiEntity dataApiEntity = new DataApiEntity();
dataApiEntity.setId(id);
dataApiEntity.setStatus(DataConstant.ApiState.CANCEL.getKey());
dataApiDao.updateById(dataApiEntity);
}
rabbitTemplate.convertAndSend(RabbitMqConstant.FANOUT_API_QUEUE, "", map);
LambdaUpdateWrapper<DataApiEntity> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(DataApiEntity::getStatus, DataConstant.ApiState.CANCEL.getKey());
updateWrapper.eq(DataApiEntity::getId, id);
dataApiDao.update(null, updateWrapper);
}
@Override
......
package cn.datax.service.data.quality.schedule.exception;
import cn.datax.service.data.quality.schedule.exception.util.ExceptionMessageFormat;
import cn.datax.service.data.quality.schedule.exception.util.factory.ExceptionMsgFormatFactory;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 子线程异常,子线程出现异常时抛出
*/
public class ChildThreadException extends Exception {
/**
* serialVersionUID
*/
private static final long serialVersionUID=1L;
/**
* 子线程的异常列表
*/
private List<Exception> exceptionList;
/**
* 异常信息格式化工具
*/
private ExceptionMessageFormat formatter;
/**
* 锁
*/
private Lock lock;
public ChildThreadException() {
super();
initial();
}
public ChildThreadException(String message) {
super(message);
initial();
}
public ChildThreadException(String message, StackTraceElement[] stackTrace) {
this(message);
setStackTrace(stackTrace);
}
private void initial() {
exceptionList = new ArrayList<Exception>();
lock = new ReentrantLock();
formatter = ExceptionMsgFormatFactory.getInstance().getFormatter(ExceptionMsgFormatFactory.STACK_TRACE);
}
/**
* 子线程是否有异常
* @return
*/
public boolean hasException() {
return exceptionList.size() > 0;
}
/**
* 添加子线程的异常
* @param e
*/
public void addException(Exception e) {
try {
lock.lock();
e.setStackTrace(e.getStackTrace());
exceptionList.add(e);
} finally {
lock.unlock();
}
}
/**
* 获取子线程的异常列表
* @return
*/
public List<Exception> getExceptionList() {
return exceptionList;
}
/**
* 清空子线程的异常列表
*/
public void clearExceptionList() {
exceptionList.clear();
}
/**
* 获取所有子线程异常的堆栈跟踪信息
* @return
*/
public String getAllStackTraceMessage() {
StringBuffer sb = new StringBuffer();
for (Exception e : exceptionList) {
sb.append(e.getClass().getName());
sb.append(": ");
sb.append(e.getMessage());
sb.append("\n");
sb.append(formatter.formate(e));
}
return sb.toString();
}
/**
* 打印所有子线程的异常的堆栈跟踪信息
*/
public void printAllStackTrace() {
printAllStackTrace(System.err);
}
/**
* 打印所有子线程的异常的堆栈跟踪信息
* @param s
*/
public void printAllStackTrace(PrintStream s) {
for (Exception e : exceptionList) {
e.printStackTrace(s);
}
}
}
package cn.datax.service.data.quality.schedule.exception.util;
/**
* 默认异常信息格式化工具
*/
public class DefaultExceptionMsgHandler implements ExceptionMessageFormat {
private DefaultExceptionMsgHandler() {
}
private static class SingletonHolder{
private static final DefaultExceptionMsgHandler instance = new DefaultExceptionMsgHandler();
}
public static DefaultExceptionMsgHandler getInstance(){
return SingletonHolder.instance;
}
/**
* 格式化异常信息
*/
@Override
public String formate(Exception e) {
return e.getMessage() + "\n";
}
}
package cn.datax.service.data.quality.schedule.exception.util;
/**
* 异常信息格式化
*/
public interface ExceptionMessageFormat {
public String formate(Exception e);
}
package cn.datax.service.data.quality.schedule.exception.util;
/**
* 堆栈跟踪信息格式化工具
*/
public class StackTraceMsgHandler implements ExceptionMessageFormat {
private StackTraceMsgHandler() {
}
private static class SingletonHolder {
private static final StackTraceMsgHandler instance = new StackTraceMsgHandler();
}
public static StackTraceMsgHandler getInstance() {
return SingletonHolder.instance;
}
/**
* 格式化堆栈跟踪信息
*/
@Override
public String formate(Exception e) {
StackTraceElement[] stackTrace = e.getStackTrace();
StringBuffer sb = new StringBuffer();
for (StackTraceElement stackTraceElement : stackTrace) {
sb.append("\tat " + stackTraceElement + "\n");
}
return sb.toString();
}
}
package cn.datax.service.data.quality.schedule.exception.util.factory;
import cn.datax.service.data.quality.schedule.exception.util.DefaultExceptionMsgHandler;
import cn.datax.service.data.quality.schedule.exception.util.ExceptionMessageFormat;
import cn.datax.service.data.quality.schedule.exception.util.StackTraceMsgHandler;
/**
* 异常信息格式化工厂
*/
public class ExceptionMsgFormatFactory {
public static final String STACK_TRACE = "StackTraceHandler";
private ExceptionMsgFormatFactory() {
}
private static class SingletonHolder {
private static final ExceptionMsgFormatFactory instance = new ExceptionMsgFormatFactory();
}
public static ExceptionMsgFormatFactory getInstance() {
return SingletonHolder.instance;
}
/**
* 获取格式化工具
*
* @param formatterName
* @return
*/
public ExceptionMessageFormat getFormatter(String formatterName) {
switch (formatterName) {
case STACK_TRACE:
return StackTraceMsgHandler.getInstance();
default:
break;
}
return DefaultExceptionMsgHandler.getInstance();
}
}
package cn.datax.service.data.quality.schedule.thread;
import cn.datax.service.data.quality.schedule.exception.ChildThreadException;
/**
* 多任务处理
*/
public interface MultiThreadHandler {
/**
* 添加任务
* @param tasks
*/
void addTask(Runnable... tasks);
/**
* 执行任务
* @throws ChildThreadException
*/
void run() throws ChildThreadException;
}
package cn.datax.service.data.quality.schedule.thread.parallel;
import cn.datax.service.data.quality.schedule.exception.ChildThreadException;
import cn.datax.service.data.quality.schedule.thread.MultiThreadHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* 并行线程处理
*/
public abstract class AbstractMultiParallelThreadHandler implements MultiThreadHandler {
/**
* 子线程倒计数锁
*/
protected CountDownLatch childLatch;
/**
* 任务列表
*/
protected List<Runnable> taskList;
/**
* 子线程异常
*/
protected ChildThreadException childThreadException;
public AbstractMultiParallelThreadHandler() {
taskList = new ArrayList<Runnable>();
childThreadException = new ChildThreadException();
}
public void setCountDownLatch(CountDownLatch latch) {
this.childLatch = latch;
}
/**
* {@inheritDoc}
*/
@Override
public void addTask(Runnable... tasks) {
if (null == tasks) {
taskList = new ArrayList<Runnable>();
}
for (Runnable task : tasks) {
taskList.add(task);
}
}
/**
* {@inheritDoc}
*/
@Override
public abstract void run() throws ChildThreadException;
}
package cn.datax.service.data.quality.schedule.thread.parallel;
import cn.datax.service.data.quality.schedule.exception.ChildThreadException;
import java.util.concurrent.CountDownLatch;
/**
* 并行任务参数
*/
public class MultiParallelContext {
/**
* 运行的任务
*/
private Runnable task;
/**
* 子线程倒计数锁
*/
private CountDownLatch childLatch;
/**
* 子线程异常
*/
private ChildThreadException childException;
public MultiParallelContext() {
}
public MultiParallelContext(Runnable task, CountDownLatch childLatch, ChildThreadException childException) {
this.task = task;
this.childLatch = childLatch;
this.childException = childException;
}
public Runnable getTask() {
return task;
}
public void setTask(Runnable task) {
this.task = task;
}
public CountDownLatch getChildLatch() {
return childLatch;
}
public void setChildLatch(CountDownLatch childLatch) {
this.childLatch = childLatch;
}
public ChildThreadException getChildException() {
return childException;
}
public void setChildException(ChildThreadException childException) {
this.childException = childException;
}
}
package cn.datax.service.data.quality.schedule.thread.parallel;
/**
* 并行线程对象
*/
public class MultiParallelRunnable implements Runnable {
/**
* 并行任务参数
*/
private MultiParallelContext context;
/**
* 构造函数
* @param context
*/
public MultiParallelRunnable(MultiParallelContext context) {
this.context = context;
}
/**
* 运行任务
*/
@Override
public void run() {
try {
context.getTask().run();
} catch (Exception e) {
e.printStackTrace();
context.getChildException().addException(e);
} finally {
context.getChildLatch().countDown();
}
}
}
package cn.datax.service.data.quality.schedule.thread.parallel;
import cn.datax.service.data.quality.schedule.exception.ChildThreadException;
import java.util.concurrent.CountDownLatch;
/**
* 并行任务处理工具
*/
public class MultiParallelThreadHandler extends AbstractMultiParallelThreadHandler {
/**
* 无参构造器
*/
public MultiParallelThreadHandler() {
super();
}
/**
* 根据任务数量运行任务
*/
@Override
public void run() throws ChildThreadException {
if (null == taskList || taskList.size() == 0) {
return;
} else if (taskList.size() == 1) {
runWithoutNewThread();
} else if (taskList.size() > 1) {
runInNewThread();
}
}
/**
* 新建线程运行任务
*
* @throws ChildThreadException
*/
private void runInNewThread() throws ChildThreadException {
childLatch = new CountDownLatch(taskList.size());
childThreadException.clearExceptionList();
for (Runnable task : taskList) {
invoke(new MultiParallelRunnable(new MultiParallelContext(task, childLatch, childThreadException)));
}
taskList.clear();
try {
childLatch.await();
} catch (InterruptedException e) {
childThreadException.addException(e);
}
throwChildExceptionIfRequired();
}
/**
* 默认线程执行方法
*
* @param command
*/
protected void invoke(Runnable command) {
if(command.getClass().isAssignableFrom(Thread.class)){
Thread.class.cast(command).start();
}else{
new Thread(command).start();
}
}
/**
* 在当前线程中直接运行
*
* @throws ChildThreadException
*/
private void runWithoutNewThread() throws ChildThreadException {
try {
taskList.get(0).run();
} catch (Exception e) {
childThreadException.addException(e);
}
throwChildExceptionIfRequired();
}
/**
* 根据需要抛出子线程异常
*
* @throws ChildThreadException
*/
private void throwChildExceptionIfRequired() throws ChildThreadException {
if (childThreadException.hasException()) {
childExceptionHandler(childThreadException);
}
}
/**
* 默认抛出子线程异常
* @param e
* @throws ChildThreadException
*/
protected void childExceptionHandler(ChildThreadException e) throws ChildThreadException {
throw e;
}
}
package cn.datax.service.data.quality.schedule.thread.parallel;
import java.util.concurrent.ExecutorService;
/**
* 使用线程池运行并行任务
*/
public class ParallelTaskWithThreadPool extends MultiParallelThreadHandler {
private ExecutorService service;
public ParallelTaskWithThreadPool() {
}
public ParallelTaskWithThreadPool(ExecutorService service) {
this.service = service;
}
public ExecutorService getService() {
return service;
}
public void setService(ExecutorService service) {
this.service = service;
}
/**
* 使用线程池运行
*/
@Override
protected void invoke(Runnable command) {
if(null != service){
service.execute(command);
}else{
super.invoke(command);
}
}
}
package cn.datax.service.data.quality.schedule.thread.test;
import cn.datax.service.data.quality.schedule.exception.ChildThreadException;
import cn.datax.service.data.quality.schedule.thread.MultiThreadHandler;
import cn.datax.service.data.quality.schedule.thread.parallel.MultiParallelThreadHandler;
import cn.datax.service.data.quality.schedule.thread.parallel.ParallelTaskWithThreadPool;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCase implements Runnable {
private String name;
private Map<String, Object> result;
public TestCase(String name, Map<String, Object> result) {
this.name = name;
this.result = result;
}
@Override
public void run() {
// 模拟线程执行1000ms
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 模拟线程1和线程3抛出异常
if(name.equals("1") || name.equals("3")) {
throw new RuntimeException(name + ": throw exception");
}
result.put(name, "complete part " + name + "!");
}
// public static void main(String[] args) {
// System.out.println("main begin \t=================");
// Map<String, Object> resultMap = new HashMap<>(8, 1);
//// MultiThreadHandler handler = new MultiParallelThreadHandler();
// ExecutorService service = Executors.newFixedThreadPool(3);
// MultiThreadHandler handler = new ParallelTaskWithThreadPool(service);
// TestCase task = null;
// // 启动5个子线程作为要处理的并行任务,共同完成结果集resultMap
// for(int i=1; i<=5 ; i++){
// task = new TestCase("" + i, resultMap);
// handler.addTask(task);
// }
// try {
// handler.run();
// } catch (ChildThreadException e) {
// System.out.println(e.getAllStackTraceMessage());
// }
// System.out.println(resultMap);
// service.shutdown();
// System.out.println("main end \t=================");
// }
}
\ No newline at end of file
......@@ -25,6 +25,5 @@
<module>data-masterdata-service-parent</module>
<module>data-quality-service-parent</module>
<module>workflow-service-parent</module>
<module>websocket-service-parent</module>
</modules>
</project>
<?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>
<artifactId>datax-modules</artifactId>
<groupId>cn.datax</groupId>
<version>2.0.0</version>
</parent>
<packaging>pom</packaging>
<modelVersion>4.0.0</modelVersion>
<version>2.0.0</version>
<description>即时通讯</description>
<artifactId>websocket-service-parent</artifactId>
<modules>
<module>websocket-service-api</module>
<module>websocket-service</module>
</modules>
</project>
\ No newline at end of file
<?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>
<artifactId>websocket-service-parent</artifactId>
<groupId>cn.datax</groupId>
<version>2.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<version>2.0.0</version>
<artifactId>websocket-service-api</artifactId>
<dependencies>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-micro-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!--feign 依赖-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>cn.datax</groupId>
<artifactId>datax-common-core</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
<?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>
<artifactId>websocket-service-parent</artifactId>
<groupId>cn.datax</groupId>
<version>2.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<version>2.0.0</version>
<artifactId>websocket-service</artifactId>
<dependencies>
<!--web 模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<!--配置中心客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.t-io</groupId>
<artifactId>tio-websocket-spring-boot-starter</artifactId>
<version>${tio-websocket.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cn.datax</groupId>
<artifactId>datax-common-mybatis</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>cn.datax</groupId>
<artifactId>datax-common-redis</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>cn.datax</groupId>
<artifactId>datax-common-security</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>cn.datax</groupId>
<artifactId>websocket-service-api</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package cn.datax.service.websocket;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.tio.websocket.starter.EnableTioWebSocketServer;
@EnableTioWebSocketServer
@EnableFeignClients
@SpringBootApplication
public class DataxWebsocketApplication {
public static void main(String[] args) {
SpringApplication.run(DataxWebsocketApplication.class);
}
}
package cn.datax.service.websocket.config;
import cn.datax.common.security.handler.DataAccessDeniedHandler;
import cn.datax.common.security.handler.DataAuthExceptionEntryPoint;
import cn.datax.common.security.utils.DataRedisTokenServices;
import cn.datax.common.security.utils.RedisTokenStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class DataResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private DataAccessDeniedHandler accessDeniedHandler;
@Autowired
private DataAuthExceptionEntryPoint exceptionEntryPoint;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore redisTokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
DataRedisTokenServices dataTokenServices = new DataRedisTokenServices();
dataTokenServices.setTokenStore(redisTokenStore());
resources
.tokenStore(redisTokenStore())
.tokenServices(dataTokenServices)
.authenticationEntryPoint(exceptionEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}
@Override
public void configure(HttpSecurity http) throws Exception {
//允许使用iframe 嵌套,避免swagger-ui 不被加载的问题
http.headers().frameOptions().disable();
http.authorizeRequests()
.antMatchers(
"/actuator/**",
"/v2/api-docs/**",
"/swagger-ui.html",
"/doc.html",
"/swagger-resources/**",
"/webjars/**",
// feign 内部调用不用授权
"/inner/**"
).permitAll()
.anyRequest().authenticated()
.and().csrf().disable();
}
}
package cn.datax.service.websocket.config;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Component
@RequiredArgsConstructor
public class StartedUpRunner implements ApplicationRunner {
private final ConfigurableApplicationContext context;
private final Environment environment;
@Override
public void run(ApplicationArguments args) {
if (context.isActive()) {
String banner = "-----------------------------------------\n" +
"服务启动成功,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()) + "\n" +
"服务名称:" + environment.getProperty("spring.application.name") + "\n" +
"端口号:" + environment.getProperty("server.port") + "\n" +
"-----------------------------------------";
System.out.println(banner);
}
}
}
package cn.datax.service.websocket.config;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.beans.factory.annotation.Autowired;
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.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.*;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.List;
@Configuration
@ConditionalOnProperty(prefix = "swagger", name = "enable", havingValue = "true")
@EnableConfigurationProperties(SwaggerProperties.class)
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerConfig {
@Autowired
private SwaggerProperties swaggerProperties;
/**
* 创建API应用
* apiInfo() 增加API相关信息
* 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现,
* 本例采用指定扫描的包路径来定义指定要建立API的目录。
*
* @return
*/
@Bean
public Docket createRestApi(){
//版本类型是swagger2
return new Docket(DocumentationType.SWAGGER_2)
//通过调用自定义方法apiInfo,获得文档的主要信息
.apiInfo(apiInfo())
//设置全局参数
.globalOperationParameters(globalParamBuilder())
//设置全局响应参数
.globalResponseMessage(RequestMethod.GET,responseBuilder())
.globalResponseMessage(RequestMethod.POST,responseBuilder())
.globalResponseMessage(RequestMethod.PUT,responseBuilder())
.globalResponseMessage(RequestMethod.DELETE,responseBuilder())
.select()
//扫描该包下面的API注解
.apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
.paths(PathSelectors.any())
.build()
//设置安全认证
.securitySchemes(security());
}
/**
* 创建该API的基本信息(这些基本信息会展现在文档页面中)
* 访问地址:http://项目实际地址/swagger-ui.html
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(swaggerProperties.getTitle())
.description(swaggerProperties.getDescription())
.termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
.version(swaggerProperties.getVersion())
.contact(new Contact(swaggerProperties.getContact().getName(), swaggerProperties.getContact().getUrl(), swaggerProperties.getContact().getEmail()))
.build();
}
/**
* 安全认证参数
* @return
*/
private List<ApiKey> security() {
List<ApiKey> apiKeys = new ArrayList<>();
apiKeys.add(new ApiKey("Authorization", "Authorization", "header"));
return apiKeys;
}
/**
* 构建全局参数列表
* @return
*/
private List<Parameter> globalParamBuilder(){
List<Parameter> pars = new ArrayList<>();
pars.add(parameterBuilder("Authorization","令牌","string","header",false).build());
return pars;
}
/**
* 创建参数
* @return
*/
private ParameterBuilder parameterBuilder(String name, String desc, String type, String parameterType, boolean required) {
ParameterBuilder tokenPar = new ParameterBuilder();
tokenPar.name(name).description(desc).modelRef(new ModelRef(type)).parameterType(parameterType).required(required).build();
return tokenPar;
}
/**
* 创建全局响应值
* @return
*/
private List<ResponseMessage> responseBuilder() {
List<ResponseMessage> responseMessageList = new ArrayList<>();
responseMessageList.add(new ResponseMessageBuilder().code(200).message("响应成功").build());
responseMessageList.add(new ResponseMessageBuilder().code(500).message("服务器内部错误").build());
return responseMessageList;
}
}
package cn.datax.service.websocket.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(ignoreUnknownFields = false, prefix = "swagger")
public class SwaggerProperties {
private Boolean enable;
private String title;
private String description;
private String version;
private String termsOfServiceUrl;
private String basePackage;
private Contact contact;
public Boolean getEnable() {
return enable;
}
public void setEnable(Boolean enable) {
this.enable = enable;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getTermsOfServiceUrl() {
return termsOfServiceUrl;
}
public void setTermsOfServiceUrl(String termsOfServiceUrl) {
this.termsOfServiceUrl = termsOfServiceUrl;
}
public String getBasePackage() {
return basePackage;
}
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
public Contact getContact() {
return contact;
}
public void setContact(Contact contact) {
this.contact = contact;
}
public static class Contact {
private String name;
private String url;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
}
package cn.datax.service.websocket.controller;
import cn.datax.common.base.BaseController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/inner")
public class InnerController extends BaseController {
}
package cn.datax.service.websocket.controller;
import cn.datax.common.base.BaseController;
import cn.hutool.core.util.StrUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.tio.core.Tio;
import org.tio.websocket.common.WsResponse;
import org.tio.websocket.starter.TioWebSocketServerBootstrap;
@RestController
@RequestMapping("/push")
public class PushController extends BaseController {
@Autowired
private TioWebSocketServerBootstrap bootstrap;
@GetMapping("/msg")
public void pushMessage(String msg){
if (StrUtil.isEmpty(msg)){
msg = "hello tio websocket";
}
Tio.sendToAll(bootstrap.getServerTioConfig(), WsResponse.fromText(msg,"utf-8"));
}
}
package cn.datax.service.websocket.server;
import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.http.common.HttpRequest;
import org.tio.http.common.HttpResponse;
import org.tio.websocket.common.WsRequest;
import org.tio.websocket.common.WsResponse;
import org.tio.websocket.server.handler.IWsMsgHandler;
/**
* 和 Tio WebSocket 用法一致,需要实现 IWsMsgHandler 接口,
* 可以添加 @Service 注解,不加的话会自动扫描该类(需要配置 tio.websocket.server.use-scanner: true)
*/
public class MyWsMsgHandler implements IWsMsgHandler {
/**
* 握手
* @param httpRequest
* @param httpResponse
* @param channelContext
* @return
* @throws Exception
*/
@Override
public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
System.out.println("握手成功");
return httpResponse;
}
/**
* 握手完毕
* @param httpRequest
* @param httpResponse
* @param channelContext
* @throws Exception
*/
@Override
public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
System.out.println("握手完毕");
}
/**
* binaryType = arraybuffer
* @param wsRequest
* @param bytes
* @param channelContext
* @return
* @throws Exception
*/
@Override
public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
System.out.println("接收到bytes消息:" + new String(bytes));
return null;
}
/**
* binaryType = blob
* @param wsRequest
* @param s
* @param channelContext
* @return
* @throws Exception
*/
@Override
public Object onText(WsRequest wsRequest, String s, ChannelContext channelContext) throws Exception {
System.out.println("接收到blob消息:" + s);
Tio.sendToAll(channelContext.getTioConfig(), WsResponse.fromText("服务端收到了消息:" + s, "utf-8"));
return null;
}
/**
* 关闭
* @param wsRequest
* @param bytes
* @param channelContext
* @return
* @throws Exception
*/
@Override
public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
return null;
}
}
package cn.datax.service.websocket.server.listener;
import org.tio.core.ChannelContext;
import org.tio.core.intf.Packet;
import org.tio.websocket.server.WsServerAioListener;
public class MyAioListener extends WsServerAioListener {
@Override
public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect) throws Exception {
super.onAfterConnected(channelContext, isConnected, isReconnect);
}
@Override
public void onBeforeClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) throws Exception {
super.onBeforeClose(channelContext, throwable, remark, isRemove);
}
@Override
public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess) throws Exception {
super.onAfterSent(channelContext, packet, isSentSuccess);
}
@Override
public void onAfterDecoded(ChannelContext channelContext, Packet packet, int packetSize) throws Exception {
super.onAfterDecoded(channelContext, packet, packetSize);
}
@Override
public void onAfterHandled(ChannelContext channelContext, Packet packet, long cost) throws Exception {
super.onAfterHandled(channelContext, packet, cost);
}
@Override
public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes) throws Exception {
super.onAfterReceivedBytes(channelContext, receivedBytes);
}
}
package cn.datax.service.websocket.server.listener;
import org.tio.core.ChannelContext;
import org.tio.core.intf.GroupListener;
public class MyGroupListener implements GroupListener {
@Override
public void onAfterBind(ChannelContext channelContext, String s) throws Exception {
}
@Override
public void onAfterUnbind(ChannelContext channelContext, String s) throws Exception {
}
}
package cn.datax.service.websocket.server.listener;
import org.tio.core.ChannelContext;
import org.tio.core.TioConfig;
import org.tio.core.intf.Packet;
import org.tio.core.stat.IpStat;
import org.tio.core.stat.IpStatListener;
public class MyIpStatListener implements IpStatListener {
@Override
public void onExpired(TioConfig tioConfig, IpStat ipStat) {
}
@Override
public void onAfterConnected(ChannelContext channelContext, boolean b, boolean b1, IpStat ipStat) throws Exception {
}
@Override
public void onDecodeError(ChannelContext channelContext, IpStat ipStat) {
}
@Override
public void onAfterSent(ChannelContext channelContext, Packet packet, boolean b, IpStat ipStat) throws Exception {
}
@Override
public void onAfterDecoded(ChannelContext channelContext, Packet packet, int i, IpStat ipStat) throws Exception {
}
@Override
public void onAfterReceivedBytes(ChannelContext channelContext, int i, IpStat ipStat) throws Exception {
}
@Override
public void onAfterHandled(ChannelContext channelContext, Packet packet, IpStat ipStat, long l) throws Exception {
}
}
server:
port: 8815
spring:
application:
name: datax-service-websocket
profiles:
active: dev
cloud:
config:
fail-fast: true
name: ${spring.application.name}
profile: ${spring.profiles.active}
discovery:
enabled: true
service-id: datax-config
# 注册中心配置
eureka:
instance:
lease-renewal-interval-in-seconds: 20
client:
register-with-eureka: true
fetch-registry: true
instance-info-replication-interval-seconds: 30
registry-fetch-interval-seconds: 3
service-url:
defaultZone: http://localhost:8610/eureka
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<springProperty scope="context" name="springAppName" source="spring.application.name"/>
<property name="log.path" value="logs/datax-service-websocket"/>
<property name="log.maxHistory" value="15"/>
<property name="log.totalSizeCap" value="500MB"/>
<property name="log.maxFileSize" value="10MB"/>
<property name="log.colorPattern"
value="%magenta(%d{yyyy-MM-dd HH:mm:ss}) %highlight(%-5level) %boldCyan(${springAppName:-}) %yellow(%thread) %green(%logger) %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level ${springAppName:-} %thread %logger %msg%n"/>
<!--输出到控制台-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.colorPattern}</pattern>
</encoder>
</appender>
<!--输出到文件-->
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是project_info.log -->
<!-- 2.如果日期没有发生变化,但是当前日志的文件大小超过10MB时,对当前日志进行分割 重命名-->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件路径和名称-->
<File>${log.path}/info/info.log</File>
<!--是否追加到文件末尾,默认为true-->
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 日志文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
<!-- 文件名:logs/project_info.2017-12-05.0.log -->
<!-- 注意:SizeAndTimeBasedRollingPolicy中 %i和%d令牌都是强制性的,必须存在,要不会报错 -->
<fileNamePattern>${log.path}/info/info.%d.%i.log</fileNamePattern>
<!-- 每产生一个日志文件,该日志文件的保存期限为30天, ps:maxHistory的单位是根据fileNamePattern中的翻转策略自动推算出来的,例如上面选用了yyyy-MM-dd,则单位为天
如果上面选用了yyyy-MM,则单位为月,另外上面的单位默认为yyyy-MM-dd-->
<MaxHistory>${log.maxHistory}</MaxHistory>
<!-- 每个日志文件到2mb的时候开始切分,最多保留30天,但最大到500MB,哪怕没到30天也要删除多余的日志 -->
<totalSizeCap>${log.totalSizeCap}</totalSizeCap>
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成5KB看效果 -->
<maxFileSize>${log.maxFileSize}</maxFileSize>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${log.path}/error/error.log</File>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/error.%d.%i.log</fileNamePattern>
<MaxHistory>${log.maxHistory}</MaxHistory>
<totalSizeCap>${log.totalSizeCap}</totalSizeCap>
<maxFileSize>${log.maxFileSize}</maxFileSize>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<root level="debug">
<appender-ref ref="console"/>
</root>
<root level="info">
<appender-ref ref="file_info"/>
<appender-ref ref="file_error"/>
</root>
</configuration>
\ No newline at end of file
module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,batch,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
# 开启过滤
filter=true
# 配置不打印的内容
exclude=select 1
\ No newline at end of file
......@@ -13,7 +13,7 @@
v-if="!item.hidden"
:index="item.path"
>
<!-- <i class="el-icon-menu"></i>-->
<!-- <i class="el-icon-menu"></i>-->
<span slot="title">{{ item.meta ? item.meta.title : item.children[0].meta.title }}</span>
</el-menu-item>
</app-link>
......@@ -22,27 +22,30 @@
<div class="right-menu">
<screenfull id="screenfull" class="right-menu-btn" />
<span class="right-menu-btn">
<el-badge is-dot class="badge">
<i class="el-icon-bell"></i>
</el-badge>
<span class="right-menu-btn" @click="handleLock">
<i class="el-icon-lock" />
</span>
<!-- <span class="right-menu-btn">-->
<!-- <el-badge is-dot class="badge">-->
<!-- <i class="el-icon-bell"></i>-->
<!-- </el-badge>-->
<!-- </span>-->
<el-dropdown>
<span class="right-menu-btn">
{{ user.nickname }}
<i class="el-icon-arrow-down el-icon--right"></i>
<i class="el-icon-arrow-down el-icon--right" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="handlePassword">
<i style="padding-right: 8px" class="el-icon-user"></i>修改密码
<i style="padding-right: 8px" class="el-icon-user" />修改密码
</el-dropdown-item>
<el-dropdown-item @click.native="logout">
<i style="padding-right: 8px" class="el-icon-turn-off"></i>退出系统
<i style="padding-right: 8px" class="el-icon-turn-off" />退出系统
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<change-password :visible.sync="dialogHandlePasswordVisible" :id="user.id" @handlePasswordFinished="logout" />
<change-password :id="user.id" :visible.sync="dialogHandlePasswordVisible" @handlePasswordFinished="logout" />
</div>
</template>
......@@ -179,6 +182,22 @@ export default {
},
handlePassword() {
this.dialogHandlePasswordVisible = true
},
handleLock() {
this.$prompt('请输入锁屏密码', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
showClose: false,
closeOnClickModal: false,
inputPattern: /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{4,}$/,
inputErrorMessage: '锁屏密码长度要大于4位,由数字和字母组成'
}).then(({ value }) => {
this.$store.commit('user/SET_LOCK', value)
setTimeout(() => {
this.$router.push({ path: '/lock' })
}, 100)
}).catch(() => {
})
}
}
}
......
......@@ -49,6 +49,12 @@ export const constantRoutes = [
},
{
path: '/lock',
component: () => import('@/views/lock/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/error-page/404'),
hidden: true
......
......@@ -3,6 +3,7 @@ const getters = {
device: state => state.app.device,
token: state => state.user.token,
user: state => state.user.detail,
user_lock: state => state.user.lock,
user_menus: state => state.user.menus,
user_perms: state => state.user.perms,
permission_routes: state => state.permission.routes,
......
......@@ -8,7 +8,8 @@ const getDefaultState = () => {
token: getToken(),
detail: {},
menus: [],
perms: []
perms: [],
lock: ''
}
}
......@@ -16,7 +17,8 @@ const state = {
token: getToken(),
detail: Storage.get('data_ui_user_detail') || {},
menus: Storage.get('data_ui_user_menus') || [],
perms: Storage.get('data_ui_user_perms') || []
perms: Storage.get('data_ui_user_perms') || [],
lock: Storage.get('data_ui_user_lock') || ''
}
const mutations = {
......@@ -34,6 +36,10 @@ const mutations = {
},
SET_PERMS: (state, perms) => {
state.perms = perms
},
SET_LOCK: (state, lock) => {
Storage.set('data_ui_user_lock', lock)
state.lock = lock
}
}
......
......@@ -23,7 +23,7 @@ html {
}
#app {
height: $contentHeight;
height: calc(100vh);
}
*,
......
......@@ -6,7 +6,6 @@
<script>
import { mapGetters } from 'vuex'
import websocket from '@/utils/websocket'
export default {
name: 'Dashboard',
......@@ -15,12 +14,6 @@ export default {
'user'
])
}
// mounted() {
// websocket.init()
// },
// beforeDestroy() {
// websocket.close()
// }
}
</script>
......
<template>
<div class="lock-container">
<div class="lock-form">
<el-input
v-model="password"
placeholder="请输入锁屏密码"
type="password"
class="input-with-select"
@keyup.enter.native="handleLogin"
>
<el-button
slot="append"
icon="el-icon-unlock"
@click="handleLogin"
/>
<el-button
slot="append"
icon="el-icon-turn-off"
@click="handleLogout"
/>
</el-input>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'Lock',
data() {
return {
password: ''
}
},
computed: {
...mapGetters([
'user_lock'
])
},
methods: {
handleLogout() {
this.$confirm('是否退出系统, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$store.dispatch('user/logout').then(() => {
this.$router.push({ path: '/login' })
})
}).catch(() => {
})
},
handleLogin() {
if (this.password !== this.user_lock) {
this.password = ''
this.$message({
message: '解锁密码错误,请重新输入',
type: 'error'
})
return
}
setTimeout(() => {
this.$router.push({ path: '/' })
}, 1000)
}
}
}
</script>
<style lang="scss" scoped>
.lock-container {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
.lock-form {
width: 300px;
}
}
</style>
<template>
<div class="login-container">
<div class="login-container" :style="'background-image:url('+ Background +');'">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
<div class="title-container">
<h3 class="title">系统登陆</h3>
</div>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<el-input
ref="username"
v-model="loginForm.username"
placeholder="Username"
name="username"
type="text"
tabindex="1"
auto-complete="on"
/>
<el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="Password"
name="password"
tabindex="2"
auto-complete="on"
@keyup.enter.native="handleLogin"
/>
<span class="show-pwd" @click="showPwd">
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
</span>
<el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码" @keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">Login</el-button>
<!-- <div class="tips">-->
<!-- <span style="margin-right:20px;">username: admin</span>-->
<!-- <span> password: any</span>-->
<!-- </div>-->
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>
</el-form>
</div>
</template>
<script>
import { log } from '@/api/system/user'
import Background from '@/assets/images/background.jpg'
export default {
name: 'Login',
......@@ -73,6 +41,7 @@ export default {
}
}
return {
Background: Background,
loginForm: {
username: 'admin',
password: '123456'
......@@ -82,7 +51,6 @@ export default {
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
},
loading: false,
passwordType: 'password',
redirect: undefined
}
},
......@@ -95,16 +63,6 @@ export default {
}
},
methods: {
showPwd() {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => {
this.$refs.password.focus()
})
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
......@@ -118,7 +76,6 @@ export default {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
......@@ -127,113 +84,32 @@ export default {
}
</script>
<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
$bg:#283443;
$light_gray:#fff;
$cursor: #fff;
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
.login-container .el-input input {
color: $cursor;
}
}
/* reset element-ui css */
<style lang="scss" scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-size: cover;
}
.title-container {
margin: 0 auto 30px auto;
text-align: center;
color: #707070;
}
.login-form {
border-radius: 6px;
background: #ffffff;
width: 385px;
padding: 25px 25px 5px 25px;
.el-input {
display: inline-block;
height: 47px;
width: 85%;
height: 38px;
input {
background: transparent;
border: 0px;
-webkit-appearance: none;
border-radius: 0px;
padding: 12px 5px 12px 15px;
color: $light_gray;
height: 47px;
caret-color: $cursor;
&:-webkit-autofill {
box-shadow: 0 0 0px 1000px $bg inset !important;
-webkit-text-fill-color: $cursor !important;
}
}
}
.el-form-item {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.1);
border-radius: 5px;
color: #454545;
}
}
</style>
<style lang="scss" scoped>
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;
.login-container {
min-height: 100%;
width: 100%;
background-color: $bg;
overflow: hidden;
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 160px 35px 0;
margin: 0 auto;
overflow: hidden;
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container {
padding: 6px 5px 6px 15px;
color: $dark_gray;
vertical-align: middle;
width: 30px;
display: inline-block;
}
.title-container {
position: relative;
.title {
font-size: 26px;
color: $light_gray;
margin: 0px auto 40px auto;
text-align: center;
font-weight: bold;
height: 38px;
}
}
.show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
color: $dark_gray;
cursor: pointer;
user-select: none;
.input-icon{
height: 39px;width: 14px;margin-left: 2px;
}
}
</style>
......@@ -60,7 +60,6 @@
<mybatis-spring.version>2.1.2</mybatis-spring.version>
<bitwalker.version>1.21</bitwalker.version>
<flowable.version>6.5.0</flowable.version>
<tio-websocket.version>3.6.0.v20200315-RELEASE</tio-websocket.version>
</properties>
<modules>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment