├── .dockerignore ├── .gitattributes ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── docker ├── .env ├── docker-compose.yml └── jenkins_build.sh ├── pom.xml ├── preview └── demo.png ├── src ├── main │ ├── java │ │ └── com │ │ │ └── rtucloud │ │ │ └── cs │ │ │ └── proxy │ │ │ ├── StartProgram.java │ │ │ ├── codec │ │ │ ├── BackendDecode.java │ │ │ ├── BackendEncode.java │ │ │ ├── FrontendDecode.java │ │ │ └── FrontendEncode.java │ │ │ ├── config │ │ │ ├── AppConfig.java │ │ │ ├── SchedulingConfig.java │ │ │ └── WebServerConfig.java │ │ │ ├── dao │ │ │ ├── entity │ │ │ │ ├── BackendServerInfo.java │ │ │ │ └── FrontendPortInfo.java │ │ │ └── repository │ │ │ │ ├── BackendServerRepository.java │ │ │ │ └── FrontendPortRepository.java │ │ │ ├── handler │ │ │ ├── ProxyBackendHandler.java │ │ │ └── ProxyFrontendHandler.java │ │ │ ├── server │ │ │ ├── BackendPipeline.java │ │ │ ├── ExecutorsServer.java │ │ │ ├── FrontendPipeline.java │ │ │ └── ProxyServer.java │ │ │ └── utils │ │ │ └── ApplicationContextUtil.java │ └── resources │ │ ├── application.yml │ │ ├── db │ │ ├── V1.1__schema.sql │ │ └── V1.2__data.sql │ │ ├── logback.xml │ │ └── mybatis-config.xml └── test │ └── java │ └── com │ └── rtucloud │ └── cs │ └── proxy │ ├── BaseTest.java │ └── spring │ └── test │ └── AsyncTest.java ├── target ├── NettyReverseProxy-1.0.jar ├── config │ ├── application.yml │ ├── db │ │ ├── V1.1__schema.sql │ │ └── V1.2__data.sql │ └── logback.xml └── lib │ ├── HdrHistogram-2.1.10.jar │ ├── HikariCP-2.7.9.jar │ ├── LatencyUtils-2.0.3.jar │ ├── accessors-smart-1.2.jar │ ├── android-json-0.0.20131108.vaadin1.jar │ ├── asm-5.0.4.jar │ ├── assertj-core-3.9.1.jar │ ├── attoparser-2.0.5.RELEASE.jar │ ├── byte-buddy-1.7.11.jar │ ├── byte-buddy-agent-1.7.11.jar │ ├── classmate-1.3.4.jar │ ├── flyway-core-5.0.7.jar │ ├── hamcrest-core-1.3.jar │ ├── hamcrest-library-1.3.jar │ ├── hibernate-validator-6.0.14.Final.jar │ ├── jackson-annotations-2.9.0.jar │ ├── jackson-core-2.9.8.jar │ ├── jackson-databind-2.9.8.jar │ ├── jackson-datatype-jdk8-2.9.8.jar │ ├── jackson-datatype-joda-2.9.8.jar │ ├── jackson-datatype-jsr310-2.9.8.jar │ ├── jackson-jaxrs-base-2.9.8.jar │ ├── jackson-jaxrs-json-provider-2.9.8.jar │ ├── jackson-module-jaxb-annotations-2.9.8.jar │ ├── jackson-module-parameter-names-2.9.8.jar │ ├── javassist-3.22.0-GA.jar │ ├── javax.annotation-api-1.3.2.jar │ ├── jboss-logging-3.3.2.Final.jar │ ├── joda-time-2.9.9.jar │ ├── jolokia-core-1.5.0.jar │ ├── json-path-2.4.0.jar │ ├── json-simple-1.1.1.jar │ ├── json-smart-2.3.jar │ ├── jsonassert-1.5.0.jar │ ├── jsqlparser-1.2.jar │ ├── jul-to-slf4j-1.7.25.jar │ ├── junit-4.12.jar │ ├── log4j-api-2.10.0.jar │ ├── log4j-to-slf4j-2.10.0.jar │ ├── logback-classic-1.2.3.jar │ ├── logback-core-1.2.3.jar │ ├── logstash-logback-encoder-5.3.jar │ ├── micrometer-core-1.0.9.jar │ ├── mockito-core-2.15.0.jar │ ├── mybatis-3.5.1.jar │ ├── mybatis-plus-3.1.1.jar │ ├── mybatis-plus-annotation-3.1.1.jar │ ├── mybatis-plus-boot-starter-3.1.1.jar │ ├── mybatis-plus-core-3.1.1.jar │ ├── mybatis-plus-extension-3.1.1.jar │ ├── mybatis-spring-2.0.1.jar │ ├── netty-all-4.1.31.Final.jar │ ├── netty-buffer-4.1.31.Final.jar │ ├── netty-codec-4.1.31.Final.jar │ ├── netty-codec-http-4.1.31.Final.jar │ ├── netty-codec-socks-4.1.31.Final.jar │ ├── netty-common-4.1.31.Final.jar │ ├── netty-handler-4.1.31.Final.jar │ ├── netty-handler-proxy-4.1.31.Final.jar │ ├── netty-resolver-4.1.31.Final.jar │ ├── netty-transport-4.1.31.Final.jar │ ├── netty-transport-native-epoll-4.1.31.Final-linux-x86_64.jar │ ├── netty-transport-native-unix-common-4.1.31.Final.jar │ ├── nio-multipart-parser-1.1.0.jar │ ├── nio-stream-storage-1.1.3.jar │ ├── objenesis-2.6.jar │ ├── reactive-streams-1.0.2.jar │ ├── reactor-core-3.1.14.RELEASE.jar │ ├── reactor-extra-3.1.9.RELEASE.jar │ ├── reactor-netty-0.7.14.RELEASE.jar │ ├── slf4j-api-1.7.25.jar │ ├── snakeyaml-1.19.jar │ ├── spring-aop-5.0.12.RELEASE.jar │ ├── spring-beans-5.0.12.RELEASE.jar │ ├── spring-boot-2.0.8.RELEASE.jar │ ├── spring-boot-actuator-2.0.8.RELEASE.jar │ ├── spring-boot-actuator-autoconfigure-2.0.8.RELEASE.jar │ ├── spring-boot-admin-client-2.0.5.jar │ ├── spring-boot-admin-server-2.0.5.jar │ ├── spring-boot-admin-server-cloud-2.0.5.jar │ ├── spring-boot-admin-server-ui-2.0.5.jar │ ├── spring-boot-admin-starter-client-2.0.5.jar │ ├── spring-boot-admin-starter-server-2.0.5.jar │ ├── spring-boot-autoconfigure-2.0.8.RELEASE.jar │ ├── spring-boot-configuration-processor-2.0.8.RELEASE.jar │ ├── spring-boot-devtools-2.0.8.RELEASE.jar │ ├── spring-boot-starter-2.0.8.RELEASE.jar │ ├── spring-boot-starter-actuator-2.0.8.RELEASE.jar │ ├── spring-boot-starter-jdbc-2.0.8.RELEASE.jar │ ├── spring-boot-starter-json-2.0.8.RELEASE.jar │ ├── spring-boot-starter-logging-2.0.8.RELEASE.jar │ ├── spring-boot-starter-reactor-netty-2.0.8.RELEASE.jar │ ├── spring-boot-starter-test-2.0.8.RELEASE.jar │ ├── spring-boot-starter-thymeleaf-2.0.8.RELEASE.jar │ ├── spring-boot-starter-tomcat-2.0.8.RELEASE.jar │ ├── spring-boot-starter-web-2.0.8.RELEASE.jar │ ├── spring-boot-starter-webflux-2.0.8.RELEASE.jar │ ├── spring-boot-test-2.0.8.RELEASE.jar │ ├── spring-boot-test-autoconfigure-2.0.8.RELEASE.jar │ ├── spring-context-5.0.12.RELEASE.jar │ ├── spring-core-5.0.12.RELEASE.jar │ ├── spring-expression-5.0.12.RELEASE.jar │ ├── spring-jcl-5.0.12.RELEASE.jar │ ├── spring-jdbc-5.0.12.RELEASE.jar │ ├── spring-test-5.0.12.RELEASE.jar │ ├── spring-tx-5.0.12.RELEASE.jar │ ├── spring-web-5.0.12.RELEASE.jar │ ├── spring-webflux-5.0.12.RELEASE.jar │ ├── spring-webmvc-5.0.12.RELEASE.jar │ ├── sqlite-jdbc-3.23.1.jar │ ├── thymeleaf-3.0.11.RELEASE.jar │ ├── thymeleaf-extras-java8time-3.0.2.RELEASE.jar │ ├── thymeleaf-spring5-3.0.11.RELEASE.jar │ ├── tomcat-embed-core-8.5.37.jar │ ├── tomcat-embed-el-8.5.37.jar │ ├── tomcat-embed-websocket-8.5.37.jar │ ├── unbescape-1.1.6.RELEASE.jar │ ├── validation-api-2.0.1.Final.jar │ └── xmlunit-core-2.5.1.jar └── ui ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .stylelintrc.js ├── CNAME ├── README.md ├── config ├── config.ts ├── defaultSettings.ts └── plugin.config.ts ├── jest-puppeteer.config.js ├── jest.config.js ├── jsconfig.json ├── mock ├── notices.ts ├── route.ts └── user.ts ├── package.json ├── public ├── favicon.png └── icons │ ├── icon-128x128.png │ ├── icon-192x192.png │ └── icon-512x512.png ├── src ├── assets │ └── logo.svg ├── components │ ├── Authorized │ │ ├── Authorized.tsx │ │ ├── AuthorizedRoute.tsx │ │ ├── CheckPermissions.tsx │ │ ├── PromiseRender.tsx │ │ ├── Secured.tsx │ │ ├── index.tsx │ │ └── renderAuthorize.ts │ ├── CopyBlock │ │ ├── index.less │ │ └── index.tsx │ ├── GlobalHeader │ │ ├── AvatarDropdown.tsx │ │ ├── NoticeIconView.tsx │ │ ├── RightContent.tsx │ │ └── index.less │ ├── HeaderDropdown │ │ ├── index.less │ │ └── index.tsx │ ├── HeaderSearch │ │ ├── index.less │ │ └── index.tsx │ ├── NoticeIcon │ │ ├── NoticeList.less │ │ ├── NoticeList.tsx │ │ ├── index.less │ │ └── index.tsx │ ├── PageLoading │ │ └── index.tsx │ ├── SelectLang │ │ ├── index.less │ │ └── index.tsx │ └── SettingDrawer │ │ └── themeColorClient.ts ├── e2e │ ├── __mocks__ │ │ └── antd-pro-merge-less.js │ ├── baseLayout.e2e.js │ └── topMenu.e2e.js ├── global.less ├── global.tsx ├── layouts │ ├── BasicLayout.tsx │ ├── BlankLayout.tsx │ ├── UserLayout.less │ └── UserLayout.tsx ├── locales │ ├── en-US.ts │ ├── en-US │ │ ├── component.ts │ │ ├── globalHeader.ts │ │ ├── menu.ts │ │ ├── pwa.ts │ │ ├── settingDrawer.ts │ │ └── settings.ts │ ├── pt-BR.ts │ ├── pt-BR │ │ ├── component.ts │ │ ├── globalHeader.ts │ │ ├── menu.ts │ │ ├── pwa.ts │ │ ├── settingDrawer.ts │ │ └── settings.ts │ ├── zh-CN.ts │ ├── zh-CN │ │ ├── component.ts │ │ ├── globalHeader.ts │ │ ├── menu.ts │ │ ├── pwa.ts │ │ ├── settingDrawer.ts │ │ └── settings.ts │ ├── zh-TW.ts │ └── zh-TW │ │ ├── component.ts │ │ ├── globalHeader.ts │ │ ├── menu.ts │ │ ├── pwa.ts │ │ ├── settingDrawer.ts │ │ └── settings.ts ├── manifest.json ├── models │ ├── connect.d.ts │ ├── global.ts │ ├── login.ts │ ├── setting.ts │ └── user.ts ├── pages │ ├── 404.tsx │ ├── Authorized.tsx │ ├── Welcome.tsx │ └── document.ejs ├── service-worker.js ├── services │ └── user.ts ├── typings.d.ts └── utils │ ├── Authorized.ts │ ├── authority.test.ts │ ├── authority.ts │ ├── request.ts │ ├── utils.less │ ├── utils.test.ts │ └── utils.ts ├── tests └── run-tests.js ├── tsconfig.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | **classes 2 | **generated-sources 3 | **generated-test-sources 4 | **maven-archiver 5 | **maven-status 6 | **test-classes -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=java 2 | *.css linguist-language=java 3 | *.html linguist-language=java 4 | *.tsx linguist-language=java 5 | *.ts linguist-language=java -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | .settings/ 4 | log/ 5 | .DS_Store 6 | *.iml 7 | *.db 8 | .idea/ 9 | logs/ 10 | target/generated-sources/ 11 | target/maven-archiver/ 12 | target/maven-status/ 13 | target/classes/ 14 | target/generated-test-sources/ 15 | target/test-classes/ 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # 第一阶段构建:Docker容器基础操作系统镜像 3 | ############################################################################### 4 | FROM anapsix/alpine-java:8_server-jre_unlimited as operating_system 5 | 6 | RUN echo "http://mirrors.ustc.edu.cn/alpine/v3.4/main" > /etc/apk/repositories \ 7 | && echo "http://mirrors.ustc.edu.cn/alpine/v3.4/community" >> /etc/apk/repositories \ 8 | && apk update \ 9 | && apk add tzdata \ 10 | && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ 11 | && echo "Asia/Shanghai" > /etc/timezone \ 12 | && apk del tzdata \ 13 | && rm -rf /var/cache/apk/* 14 | 15 | ############################################################################### 16 | # 第二阶段构建:将应用打包进容器 17 | ############################################################################### 18 | FROM operating_system 19 | 20 | MAINTAINER liuyuan <405653510@qq.com> 21 | 22 | ADD ./target/ /opt/NettyReverseProxy/App/ 23 | 24 | WORKDIR /opt/NettyReverseProxy/App/ 25 | 26 | ENTRYPOINT ["java", "-jar"] 27 | 28 | CMD ["NettyReverseProxy-1.0.jar"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NettyReverseProxy 2 | 3 | ## 解决的痛点 4 | >由于Nginx的反向代理只能代理后端服务器中的其中一个服务器,无法做到同时转发数据到后端多个服务器(一对N),所以此中间件由此需求产生。 5 | 6 | ## 原理图 7 | ![image](https://github.com/GreatGarlic/NettyReverseProxy/blob/master/preview/demo.png) 8 | 9 | ## 特点 10 | >1.支持一对N数据同时转发(解决Nginx同时只能转发后端一台服务器的问题) 11 | > 12 | >2.支持后端服务器断线重连(断线重连时间间隔可配置) 13 | > 14 | >3.无代码侵入,独立部署 15 | > 16 | >ps:如果你使用Docker部署,即可实现真正的一键部署(基础镜像的构建与应用的打包通过多阶段构建写在一个Dockerfile中) 17 | -------------------------------------------------------------------------------- /docker/.env: -------------------------------------------------------------------------------- 1 | web_port=61111 -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | netty-reverse-proxy: 4 | build: 5 | context: .. 6 | dockerfile: Dockerfile 7 | image: netty-reverse-proxy:1.0.0 8 | restart: always 9 | env_file: 10 | - .env 11 | network_mode: "host" 12 | volumes: 13 | - ./log:/opt/NettyReverseProxy/App/log 14 | user: root 15 | -------------------------------------------------------------------------------- /docker/jenkins_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -l 2 | 3 | projectName="netty_reverse_Proxy" 4 | 5 | 6 | 7 | echo "反向代理打包" 8 | 9 | cd ${WORKSPACE}/ 10 | 11 | mvn clean 12 | 13 | mvn package 14 | 15 | echo "反向代理部署" 16 | 17 | cd ${WORKSPACE}/docker 18 | 19 | docker-compose -p ${projectName} down --rmi all 20 | 21 | docker-compose -p ${projectName} up -d 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /preview/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/preview/demo.png -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/StartProgram.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy; 2 | 3 | import de.codecentric.boot.admin.server.config.EnableAdminServer; 4 | import org.mybatis.spring.annotation.MapperScan; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | 8 | 9 | @SpringBootApplication 10 | @EnableAdminServer 11 | @MapperScan(basePackages = "com.rtucloud.cs.proxy.dao.repository") 12 | public class StartProgram { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(StartProgram.class, args); 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/codec/BackendDecode.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufUtil; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.ByteToMessageDecoder; 7 | 8 | import java.net.SocketAddress; 9 | import java.util.List; 10 | 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * 16 | * 后端解码器. 17 | */ 18 | public class BackendDecode extends ByteToMessageDecoder { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(BackendDecode.class); 21 | 22 | @Override 23 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 24 | // 防止不发报文就关闭连接出现的错误 25 | if (!in.isReadable()) { 26 | return; 27 | } 28 | LOGGER.info(String.format("[%s]收到数据:%s", (ctx.channel().remoteAddress()).toString(), ByteBufUtil.hexDump(in))); 29 | byte[] ss = new byte[in.readableBytes()]; 30 | 31 | in.readBytes(ss); 32 | 33 | out.add(ss); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/codec/BackendEncode.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufUtil; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.MessageToByteEncoder; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | /** 12 | * 13 | * 后端编码器. 14 | */ 15 | public class BackendEncode extends MessageToByteEncoder { 16 | 17 | private static final Logger LOGGER = LoggerFactory.getLogger(BackendEncode.class); 18 | 19 | @Override 20 | protected void encode(ChannelHandlerContext ctx, byte[] msg, ByteBuf out) throws Exception { 21 | LOGGER.info(String.format("[%s]发送出的报文:[%s]",(ctx.channel().remoteAddress()).toString(),ByteBufUtil.hexDump((byte[]) msg))); 22 | out.writeBytes(msg); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/codec/FrontendDecode.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufUtil; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.ByteToMessageDecoder; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; 10 | import org.springframework.context.annotation.Scope; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.net.InetSocketAddress; 14 | import java.util.List; 15 | 16 | /** 17 | * 前端解码器. 18 | */ 19 | @Component 20 | @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) 21 | public class FrontendDecode extends ByteToMessageDecoder { 22 | 23 | private static final Logger LOGGER = LoggerFactory.getLogger(FrontendDecode.class); 24 | 25 | @Override 26 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 27 | // 防止不发报文就关闭连接出现的错误 28 | if (!in.isReadable()) { 29 | return; 30 | } 31 | LOGGER.info(String.format("[%s]收到数据:%s", (ctx.channel().remoteAddress()).toString(), ByteBufUtil.hexDump(in))); 32 | byte[] ss = new byte[in.readableBytes()]; 33 | 34 | in.readBytes(ss); 35 | 36 | out.add(ss); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/codec/FrontendEncode.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufUtil; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.MessageToByteEncoder; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; 10 | import org.springframework.context.annotation.Scope; 11 | import org.springframework.stereotype.Component; 12 | 13 | /** 14 | * 15 | * 后端编码器. 16 | */ 17 | @Component 18 | @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) 19 | public class FrontendEncode extends MessageToByteEncoder { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(FrontendEncode.class); 22 | 23 | @Override 24 | protected void encode(ChannelHandlerContext ctx, byte[] msg, ByteBuf out) throws Exception { 25 | LOGGER.info(String.format("发送出的报文:[%s]", ByteBufUtil.hexDump((byte[]) msg))); 26 | out.writeBytes(msg); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.config; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * 应用程序配置属性. 8 | */ 9 | @Component("appConfig") 10 | @ConfigurationProperties(prefix = "app-config") 11 | public class AppConfig { 12 | 13 | private long interval; 14 | 15 | public long getInterval() { 16 | return interval; 17 | } 18 | 19 | public void setInterval(long interval) { 20 | this.interval = interval; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/config/SchedulingConfig.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.config; 2 | 3 | import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; 4 | import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.scheduling.annotation.AsyncConfigurer; 8 | import org.springframework.scheduling.annotation.EnableAsync; 9 | import org.springframework.scheduling.annotation.EnableScheduling; 10 | import org.springframework.scheduling.annotation.SchedulingConfigurer; 11 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 12 | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 13 | import org.springframework.scheduling.config.ScheduledTaskRegistrar; 14 | 15 | import java.util.concurrent.Executor; 16 | 17 | 18 | @EnableScheduling 19 | @EnableAsync(proxyTargetClass = true) 20 | @Configuration 21 | public class SchedulingConfig implements SchedulingConfigurer, AsyncConfigurer { 22 | 23 | @Override 24 | public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { 25 | taskRegistrar.setScheduler(taskScheduler()); 26 | } 27 | 28 | @Override 29 | public Executor getAsyncExecutor() { 30 | return taskExecutor(); 31 | } 32 | 33 | @Override 34 | public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { 35 | return new SimpleAsyncUncaughtExceptionHandler(); 36 | } 37 | @Bean(name = "taskScheduler") 38 | public ThreadPoolTaskScheduler taskScheduler() { 39 | ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); 40 | scheduler.setPoolSize(10); 41 | scheduler.setDaemon(true); 42 | scheduler.setThreadNamePrefix("taskScheduler-"); 43 | return scheduler; 44 | } 45 | 46 | @Bean(name = "taskExecutor") 47 | public ThreadPoolTaskExecutor taskExecutor() { 48 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 49 | executor.setCorePoolSize(5); 50 | executor.setMaxPoolSize(10); 51 | executor.setQueueCapacity(10); 52 | executor.setDaemon(true); 53 | executor.setThreadNamePrefix("taskExecutor-"); 54 | return executor; 55 | } 56 | 57 | @Bean(name = "frontendWorkTaskExecutor") 58 | public Executor createFrontendWorkTaskExecutor() { 59 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 60 | executor.setCorePoolSize(1); 61 | executor.setMaxPoolSize(1); 62 | executor.setKeepAliveSeconds(0); 63 | executor.setQueueCapacity(0); 64 | executor.setDaemon(true); 65 | executor.setThreadNamePrefix("frontendWorkTaskExecutor-"); 66 | return executor; 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/config/WebServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.config; 2 | 3 | import org.apache.catalina.connector.Connector; 4 | import org.apache.coyote.http11.AbstractHttp11Protocol; 5 | import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; 6 | import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; 7 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.web.servlet.DispatcherServlet; 11 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 12 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 13 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 14 | 15 | import java.time.Duration; 16 | 17 | @Configuration 18 | public class WebServerConfig { 19 | 20 | /** 21 | * tomcat配置 22 | */ 23 | @Bean 24 | public TomcatServletWebServerFactory servletContainer() { 25 | TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); 26 | factory.getSession().setTimeout(Duration.ofMinutes(10)); 27 | factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { 28 | @Override 29 | public void customize(Connector connector) { 30 | AbstractHttp11Protocol httpProtocol = (AbstractHttp11Protocol) connector.getProtocolHandler(); 31 | httpProtocol.setCompression("on"); 32 | httpProtocol.setCompressibleMimeType("text/html,text/xml,text/plain,application/json,application/xml"); 33 | } 34 | }); 35 | return factory; 36 | } 37 | 38 | /** 39 | * 修改默认dispatcherServlet配置 40 | */ 41 | @Bean 42 | public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) { 43 | ServletRegistrationBean registration = new ServletRegistrationBean<>(dispatcherServlet); 44 | registration.getUrlMappings().clear(); 45 | registration.addUrlMappings("/"); 46 | registration.setAsyncSupported(true); 47 | return registration; 48 | } 49 | 50 | /** 51 | * SpringMVC配置 52 | */ 53 | @Bean 54 | public WebMvcConfigurer corsConfigurer() { 55 | 56 | return new WebMvcConfigurer() { 57 | /** 58 | * 跨域配置 59 | */ 60 | @Override 61 | public void addCorsMappings(CorsRegistry registry) { 62 | registry.addMapping("/**") 63 | .allowCredentials(true) 64 | .allowedOrigins("*") 65 | .allowedMethods("*") 66 | .allowedHeaders("*") 67 | .exposedHeaders("Authorization") 68 | .maxAge(1800); 69 | } 70 | 71 | /** 72 | * 资源加载配置 73 | */ 74 | @Override 75 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 76 | registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/"); 77 | registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources" + 78 | "/webjars/"); 79 | } 80 | 81 | }; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/dao/entity/BackendServerInfo.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.dao.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | 8 | 9 | @TableName(value = "backend_server_info") 10 | public class BackendServerInfo { 11 | @TableId(value = "id",type = IdType.AUTO) 12 | private String id; 13 | @TableField(value = "ip") 14 | private String ip; 15 | @TableField(value = "port") 16 | private int port; 17 | 18 | public String getId() { 19 | return id; 20 | } 21 | 22 | public void setId(String id) { 23 | this.id = id; 24 | } 25 | 26 | public String getIp() { 27 | return ip; 28 | } 29 | 30 | public void setIp(String ip) { 31 | this.ip = ip; 32 | } 33 | 34 | public int getPort() { 35 | return port; 36 | } 37 | 38 | public void setPort(int port) { 39 | this.port = port; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/dao/entity/FrontendPortInfo.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.dao.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | 8 | 9 | @TableName("frontend_port_info") 10 | public class FrontendPortInfo { 11 | 12 | @TableId(value = "id", type = IdType.AUTO) 13 | private String id; 14 | @TableField(value = "port") 15 | private int port; 16 | 17 | public String getId() { 18 | return id; 19 | } 20 | 21 | public void setId(String id) { 22 | this.id = id; 23 | } 24 | 25 | public int getPort() { 26 | return port; 27 | } 28 | 29 | public void setPort(int port) { 30 | this.port = port; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/dao/repository/BackendServerRepository.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.dao.repository; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.rtucloud.cs.proxy.dao.entity.BackendServerInfo; 5 | 6 | public interface BackendServerRepository extends BaseMapper { 7 | 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/dao/repository/FrontendPortRepository.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.dao.repository; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.rtucloud.cs.proxy.dao.entity.FrontendPortInfo; 5 | 6 | public interface FrontendPortRepository extends BaseMapper { 7 | 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/handler/ProxyBackendHandler.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.handler; 2 | 3 | import com.rtucloud.cs.proxy.StartProgram; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.handler.timeout.IdleState; 6 | import io.netty.handler.timeout.IdleStateEvent; 7 | import io.netty.util.Attribute; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import io.netty.channel.Channel; 12 | import io.netty.channel.ChannelFuture; 13 | import io.netty.channel.ChannelFutureListener; 14 | import io.netty.channel.ChannelHandlerContext; 15 | import io.netty.channel.SimpleChannelInboundHandler; 16 | import io.netty.handler.timeout.IdleState; 17 | import io.netty.handler.timeout.IdleStateEvent; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | public class ProxyBackendHandler extends SimpleChannelInboundHandler { 22 | private static final Logger LOGGER = LoggerFactory.getLogger(ProxyBackendHandler.class); 23 | 24 | private Channel inboundChannel; 25 | private ProxyFrontendHandler proxyFrontendHandler; 26 | private String host; 27 | private int port; 28 | 29 | public ProxyBackendHandler(Channel inboundChannel, ProxyFrontendHandler proxyFrontendHandler, String host, 30 | int port) { 31 | this.inboundChannel = inboundChannel; 32 | this.proxyFrontendHandler = proxyFrontendHandler; 33 | this.host = host; 34 | this.port = port; 35 | } 36 | 37 | // 当和目标服务器的通道连接建立时 38 | @Override 39 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 40 | 41 | LOGGER.info("目标服务器地址:" + ctx.channel().remoteAddress()); 42 | } 43 | 44 | /** 45 | * Closes the specified channel after all queued write requests are flushed. 46 | */ 47 | public static void closeOnFlush(Channel ch) { 48 | if (ch.isActive()) { 49 | ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); 50 | } 51 | } 52 | /** 53 | * msg是从目标服务器返回的消息 54 | * 55 | * @param ctx 56 | * @param msg 57 | * @throws Exception 58 | */ 59 | @Override 60 | public void channelRead0(final ChannelHandlerContext ctx, byte[] msg) throws Exception { 61 | LOGGER.info("服务器返回消息"); 62 | //接收目标服务器发送来的数据并打印 然后把数据写入代理服务器和客户端的通道里 63 | //通过inboundChannel向客户端写入数据 64 | inboundChannel.writeAndFlush(msg).addListener(new ChannelFutureListener() { 65 | @Override 66 | public void operationComplete(ChannelFuture future) throws Exception { 67 | if (!future.isSuccess()) { 68 | closeOnFlush(future.channel()); 69 | // future.channel().close(); 70 | } 71 | } 72 | }); 73 | } 74 | 75 | @Override 76 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 77 | LOGGER.info("目标服务器关闭连接"); 78 | if (proxyFrontendHandler.isConnect()) { 79 | proxyFrontendHandler.createBootstrap(inboundChannel, host, port); 80 | } 81 | } 82 | 83 | @Override 84 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 85 | LOGGER.error("发生异常:", cause); 86 | // ctx.channel().close(); 87 | closeOnFlush(ctx.channel()); 88 | } 89 | 90 | 91 | @Override 92 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 93 | if (evt instanceof IdleStateEvent) { 94 | IdleStateEvent e = (IdleStateEvent) evt; 95 | if (e.state() == IdleState.ALL_IDLE) { 96 | if (proxyFrontendHandler.isConnect()) { 97 | return; 98 | } 99 | LOGGER.debug("空闲时间到,关闭连接."); 100 | // ctx.channel().close(); 101 | closeOnFlush(ctx.channel()); 102 | } 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/server/BackendPipeline.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.server; 2 | 3 | import com.rtucloud.cs.proxy.codec.BackendDecode; 4 | import com.rtucloud.cs.proxy.codec.BackendEncode; 5 | import com.rtucloud.cs.proxy.handler.ProxyBackendHandler; 6 | import com.rtucloud.cs.proxy.handler.ProxyFrontendHandler; 7 | 8 | import io.netty.channel.Channel; 9 | import io.netty.channel.ChannelInitializer; 10 | import io.netty.channel.ChannelPipeline; 11 | import io.netty.channel.socket.SocketChannel; 12 | import io.netty.handler.timeout.IdleStateHandler; 13 | 14 | import java.util.concurrent.TimeUnit; 15 | 16 | public class BackendPipeline extends ChannelInitializer { 17 | 18 | private Channel inboundChannel; 19 | private ProxyFrontendHandler proxyFrontendHandler; 20 | private String host; 21 | private int port; 22 | 23 | public BackendPipeline(Channel inboundChannel,ProxyFrontendHandler proxyFrontendHandler,String host,int port) { 24 | this.inboundChannel = inboundChannel; 25 | this.proxyFrontendHandler=proxyFrontendHandler; 26 | this.host=host; 27 | this.port=port; 28 | } 29 | 30 | @Override 31 | protected void initChannel(SocketChannel ch) throws Exception { 32 | 33 | ChannelPipeline pipeline = ch.pipeline(); 34 | // 注册handler 35 | pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 0, 2, 36 | TimeUnit.MINUTES)); 37 | pipeline.addLast(new BackendDecode()); 38 | pipeline.addLast(new BackendEncode()); 39 | pipeline.addLast(new ProxyBackendHandler(inboundChannel,proxyFrontendHandler,host,port)); 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/server/ExecutorsServer.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.server; 2 | 3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 4 | import com.rtucloud.cs.proxy.config.AppConfig; 5 | import com.rtucloud.cs.proxy.dao.entity.FrontendPortInfo; 6 | import com.rtucloud.cs.proxy.dao.repository.FrontendPortRepository; 7 | import io.netty.bootstrap.ServerBootstrap; 8 | import io.netty.channel.ChannelFuture; 9 | import io.netty.channel.ChannelOption; 10 | import io.netty.channel.EventLoopGroup; 11 | import io.netty.channel.nio.NioEventLoopGroup; 12 | import io.netty.channel.socket.nio.NioServerSocketChannel; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.scheduling.annotation.Async; 17 | import org.springframework.scheduling.annotation.AsyncResult; 18 | import org.springframework.stereotype.Component; 19 | 20 | import java.net.InetSocketAddress; 21 | import java.util.concurrent.Future; 22 | 23 | @Component 24 | public class ExecutorsServer { 25 | private static final Logger LOGGER = LoggerFactory.getLogger(ExecutorsServer.class); 26 | 27 | @Autowired 28 | FrontendPipeline frontendPipeline; 29 | @Autowired 30 | AppConfig appConfig; 31 | @Autowired 32 | FrontendPortRepository frontendPortRepository; 33 | 34 | @Async("frontendWorkTaskExecutor") 35 | public Future initProxyServer() { 36 | EventLoopGroup bossGroup = new NioEventLoopGroup(); 37 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 38 | try { 39 | ServerBootstrap b = new ServerBootstrap(); 40 | b.group(bossGroup, workerGroup) 41 | .channel(NioServerSocketChannel.class) 42 | .childHandler(frontendPipeline) 43 | .childOption(ChannelOption.AUTO_READ, false); 44 | Page page = new Page<>(1, 1); 45 | //这里设置不进行count查询 46 | page.setTotal(1); 47 | FrontendPortInfo frontendPortInfo = frontendPortRepository.selectPage(page, null).getRecords().get(0); 48 | ChannelFuture f = b.bind(frontendPortInfo.getPort()).sync(); 49 | LOGGER.debug("启动代理服务,端口:" + ((InetSocketAddress) f.channel().localAddress()).getPort()); 50 | f.channel().closeFuture().sync(); 51 | } catch (InterruptedException e) { 52 | LOGGER.debug("代理服务关闭!"); 53 | } catch (Exception e) { 54 | LOGGER.error("代理服务启动失败!", e); 55 | } finally { 56 | workerGroup.shutdownGracefully(); 57 | bossGroup.shutdownGracefully(); 58 | } 59 | return new AsyncResult<>(true); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/server/FrontendPipeline.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.server; 2 | 3 | import com.rtucloud.cs.proxy.codec.FrontendDecode; 4 | import com.rtucloud.cs.proxy.codec.FrontendEncode; 5 | import com.rtucloud.cs.proxy.handler.ProxyFrontendHandler; 6 | import com.rtucloud.cs.proxy.utils.ApplicationContextUtil; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.ChannelPipeline; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.handler.timeout.IdleStateHandler; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.concurrent.TimeUnit; 14 | 15 | @Component("frontendPipeline") 16 | public class FrontendPipeline extends ChannelInitializer { 17 | 18 | @Override 19 | protected void initChannel(SocketChannel ch) throws Exception { 20 | ChannelPipeline pipeline = ch.pipeline(); 21 | // 注册handler 22 | pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 0, 1, 23 | TimeUnit.MINUTES)); 24 | pipeline.addLast(ApplicationContextUtil.getBean(FrontendDecode.class)); 25 | pipeline.addLast(ApplicationContextUtil.getBean(FrontendEncode.class)); 26 | pipeline.addLast(ApplicationContextUtil.getBean(ProxyFrontendHandler.class)); 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/server/ProxyServer.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.server; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.DependsOn; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.annotation.PostConstruct; 8 | import javax.annotation.PreDestroy; 9 | import java.util.concurrent.Future; 10 | 11 | @Component 12 | @DependsOn("flywayInitializer") 13 | public class ProxyServer { 14 | 15 | @Autowired 16 | ExecutorsServer executorsServer; 17 | 18 | private volatile Future future = null; 19 | 20 | @PostConstruct 21 | public synchronized void startProxyServer() { 22 | if (future == null || future.isDone()) { 23 | future = executorsServer.initProxyServer(); 24 | } 25 | } 26 | 27 | @PreDestroy 28 | public synchronized void stopProxyServer() { 29 | if (future != null && (!future.isDone())) { 30 | future.cancel(true); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/rtucloud/cs/proxy/utils/ApplicationContextUtil.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.utils; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * SpringIOC工具类,用于非Spring容器管理的类手动获取Spring容器中的Bean. 10 | */ 11 | @Component 12 | public class ApplicationContextUtil implements ApplicationContextAware { 13 | 14 | private static ApplicationContext applicationContext; 15 | 16 | public static ApplicationContext getContext() { 17 | return ApplicationContextUtil.applicationContext; 18 | } 19 | 20 | public static T getBean(String name, Class requiredType) { 21 | if (applicationContext == null) { 22 | return null; 23 | } 24 | return applicationContext.getBean(name, requiredType); 25 | } 26 | 27 | 28 | public static T getBean(Class requiredType) throws BeansException { 29 | if (applicationContext == null) { 30 | return null; 31 | } 32 | return applicationContext.getBean(requiredType); 33 | } 34 | 35 | @Override 36 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 37 | ApplicationContextUtil.applicationContext = applicationContext; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | app-config: 2 | #尝试重连间隔时间(单位:毫秒) 3 | interval: 5000 4 | server: 5 | # WebServer bind port 6 | port: ${web_port:9010} 7 | 8 | spring: 9 | 10 | datasource: 11 | initialization-mode: never 12 | driver-class-name: org.sqlite.JDBC 13 | url: jdbc:sqlite:springboot.db 14 | username: 15 | password: 16 | 17 | boot: 18 | admin: 19 | context-path: /admin 20 | monitor: 21 | #被监控的应用信息的更新频率,单位毫秒 22 | period: 1000ms 23 | #被监控的应用信息的过期时间,单位毫秒 24 | status-lifetime: 1000ms 25 | client: 26 | url: http://localhost:${server.port}/admin 27 | instance: 28 | prefer-ip: true 29 | #用于向SBA推送监控端点的位置的属性 30 | serviceBaseUrl: http://localhost:${server.port} 31 | 32 | flyway: 33 | enabled: true 34 | baseline-on-migrate: true 35 | locations: classpath:db 36 | 37 | management: 38 | endpoints: 39 | web: 40 | exposure: 41 | include: "*" 42 | endpoint: 43 | health: 44 | show-details: ALWAYS 45 | logging: 46 | file: log/all.log 47 | 48 | mybatis-plus: 49 | mapper-locations: classpath:com/rtucloud/cs/proxy/dao/mapper/*.xml 50 | config-location: classpath:mybatis-config.xml -------------------------------------------------------------------------------- /src/main/resources/db/V1.1__schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE frontend_port_info ( 2 | id integer, 3 | port integer not null, 4 | primary key (id) 5 | ); 6 | 7 | CREATE TABLE "backend_server_info" ( 8 | "id" integer, 9 | "ip" TEXT(20) NOT NULL, 10 | "port" integer NOT NULL, 11 | PRIMARY KEY ("id") 12 | ); -------------------------------------------------------------------------------- /src/main/resources/db/V1.2__data.sql: -------------------------------------------------------------------------------- 1 | insert into frontend_port_info (port) values (9016); 2 | insert into backend_server_info (ip,port) values ("127.0.0.1",61000); -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | System.out 8 | 9 | %d-%c-%M-%t-%p: %m%n 10 | 11 | 12 | 13 | 14 | 16 | log/all.log 17 | true 18 | 19 | ALL 20 | 21 | 22 | log/all-%d{yyyy-MM-dd,aux}/all-%d{yyyy-MM-dd}.%i.zip 23 | 32MB 24 | 25 | 26 | utf-8 27 | %d-%c-%M-%t-%p: %m%n 28 | 29 | 30 | 32 | true 33 | log/error/error.log 34 | 35 | utf-8 36 | %d-%c-%M-%t-%p: %m%n 37 | 38 | 39 | ERROR 40 | 41 | 42 | log/error/error-%d{yyyy-MM-dd,aux}/error-%d{yyyy-MM-dd}.%i.zip 43 | 32MB 44 | 45 | 46 | 47 | 48 | true 49 | log/data/codec.log 50 | 51 | utf-8 52 | %d-%c-%M-%t-%p: %m%n 53 | 54 | 55 | ALL 56 | 57 | 58 | log/data/codec-%d{yyyy-MM-dd,aux}/codec-%d{yyyy-MM-dd}.%i.zip 59 | 32MB 60 | 61 | 62 | 63 | 64 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/main/resources/mybatis-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/test/java/com/rtucloud/cs/proxy/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy; 2 | 3 | import cn.hutool.socket.SocketUtil; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.junit.Test; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.net.InetAddress; 11 | import java.net.Socket; 12 | import java.net.UnknownHostException; 13 | @Slf4j 14 | public class BaseTest { 15 | 16 | @Test 17 | public void test1() throws UnknownHostException { 18 | InetAddress[] addresses=InetAddress.getAllByName("nandcloud.cn"); 19 | for(InetAddress addr:addresses) 20 | { 21 | System.out.println(addr); 22 | } 23 | // InetAddress inetAddress= InetAddress.getByName(""); 24 | // 25 | // 26 | // System.out.println(inetAddress.getHostAddress()); 27 | 28 | } 29 | @Test 30 | public void test4()throws Exception{ 31 | Socket socket= SocketUtil.connect("ns1.dnspod.net",6666); 32 | 33 | InputStream is=socket.getInputStream(); 34 | BufferedReader br=new BufferedReader(new InputStreamReader(is)); 35 | String reply=br.readLine(); 36 | br.close(); 37 | is.close(); 38 | socket.close(); 39 | log.debug(reply); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/rtucloud/cs/proxy/spring/test/AsyncTest.java: -------------------------------------------------------------------------------- 1 | package com.rtucloud.cs.proxy.spring.test; 2 | 3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 4 | import com.rtucloud.cs.proxy.StartProgram; 5 | import com.rtucloud.cs.proxy.dao.entity.BackendServerInfo; 6 | import com.rtucloud.cs.proxy.dao.entity.FrontendPortInfo; 7 | import com.rtucloud.cs.proxy.dao.repository.BackendServerRepository; 8 | import com.rtucloud.cs.proxy.dao.repository.FrontendPortRepository; 9 | import com.rtucloud.cs.proxy.server.ProxyServer; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.test.context.junit4.SpringRunner; 17 | 18 | import java.util.concurrent.CountDownLatch; 19 | 20 | @RunWith(SpringRunner.class) // SpringJUnit支持,由此引入Spring-Test框架支持! 21 | @SpringBootTest(classes = StartProgram.class) // 指定我们SpringBoot工程的Application启动类 22 | public class AsyncTest { 23 | 24 | private static final Logger LOGGER = LoggerFactory.getLogger(AsyncTest.class); 25 | 26 | 27 | @Autowired 28 | FrontendPortRepository frontendPortRepository; 29 | @Autowired 30 | BackendServerRepository backendServerRepository; 31 | @Autowired 32 | ProxyServer proxyServer; 33 | 34 | @Test 35 | public void test1() throws Exception { 36 | 37 | proxyServer.startProxyServer(); 38 | Thread.sleep(5000); 39 | proxyServer.stopProxyServer(); 40 | Thread.sleep(5000); 41 | proxyServer.startProxyServer(); 42 | 43 | 44 | CountDownLatch countDownLatch = new CountDownLatch(1); 45 | try { 46 | countDownLatch.await(); 47 | } catch (InterruptedException e) { 48 | e.printStackTrace(); 49 | } 50 | } 51 | 52 | @Test 53 | public void test2() { 54 | 55 | FrontendPortInfo frontendPortInfo = new FrontendPortInfo(); 56 | // frontendPortInfo.setId(6); 57 | frontendPortInfo.setPort(500); 58 | frontendPortRepository.insert(frontendPortInfo); 59 | 60 | BackendServerInfo backendServerInfo = new BackendServerInfo(); 61 | backendServerInfo.setPort(666); 62 | backendServerRepository.insert(backendServerInfo); 63 | Page page = new Page<>(1, 1); 64 | page.setOptimizeCountSql(false); 65 | frontendPortInfo = frontendPortRepository.selectPage(page, null).getRecords().get(0); 66 | System.out.println(frontendPortInfo.getPort()); 67 | System.out.println(frontendPortInfo.getId()); 68 | System.out.println(backendServerInfo.getId()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /target/NettyReverseProxy-1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/NettyReverseProxy-1.0.jar -------------------------------------------------------------------------------- /target/config/application.yml: -------------------------------------------------------------------------------- 1 | app-config: 2 | #尝试重连间隔时间(单位:毫秒) 3 | interval: 5000 4 | server: 5 | # WebServer bind port 6 | port: ${web_port:9010} 7 | 8 | spring: 9 | 10 | datasource: 11 | initialization-mode: never 12 | driver-class-name: org.sqlite.JDBC 13 | url: jdbc:sqlite:springboot.db 14 | username: 15 | password: 16 | 17 | boot: 18 | admin: 19 | context-path: /admin 20 | monitor: 21 | #被监控的应用信息的更新频率,单位毫秒 22 | period: 1000ms 23 | #被监控的应用信息的过期时间,单位毫秒 24 | status-lifetime: 1000ms 25 | client: 26 | url: http://localhost:${server.port}/admin 27 | instance: 28 | prefer-ip: true 29 | #用于向SBA推送监控端点的位置的属性 30 | serviceBaseUrl: http://localhost:${server.port} 31 | 32 | flyway: 33 | enabled: true 34 | baseline-on-migrate: true 35 | locations: classpath:db 36 | 37 | management: 38 | endpoints: 39 | web: 40 | exposure: 41 | include: "*" 42 | endpoint: 43 | health: 44 | show-details: ALWAYS 45 | logging: 46 | file: log/all.log 47 | 48 | mybatis-plus: 49 | mapper-locations: classpath:com/rtucloud/cs/proxy/dao/mapper/*.xml 50 | config-location: classpath:mybatis-config.xml -------------------------------------------------------------------------------- /target/config/db/V1.1__schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE frontend_port_info ( 2 | id integer, 3 | port integer not null, 4 | primary key (id) 5 | ); 6 | 7 | CREATE TABLE "backend_server_info" ( 8 | "id" integer, 9 | "ip" TEXT(20) NOT NULL, 10 | "port" integer NOT NULL, 11 | PRIMARY KEY ("id") 12 | ); -------------------------------------------------------------------------------- /target/config/db/V1.2__data.sql: -------------------------------------------------------------------------------- 1 | insert into frontend_port_info (port) values (9016); 2 | insert into backend_server_info (ip,port) values ("127.0.0.1",61000); -------------------------------------------------------------------------------- /target/config/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | System.out 8 | 9 | %d-%c-%M-%t-%p: %m%n 10 | 11 | 12 | 13 | 14 | 15 | 17 | log/all.log 18 | true 19 | 20 | ALL 21 | 22 | 23 | log/all-%d{yyyy-MM-dd,aux}/all-%d{yyyy-MM-dd}.%i.zip 24 | 32MB 25 | 26 | 27 | utf-8 28 | %d-%c-%M-%t-%p: %m%n 29 | 30 | 31 | 33 | true 34 | log/error/error.log 35 | 36 | utf-8 37 | %d-%c-%M-%t-%p: %m%n 38 | 39 | 40 | ERROR 41 | 42 | 43 | log/error/error-%d{yyyy-MM-dd,aux}/error-%d{yyyy-MM-dd}.%i.zip 44 | 32MB 45 | 46 | 47 | 48 | 49 | true 50 | log/data/codec.log 51 | 52 | utf-8 53 | %d-%c-%M-%t-%p: %m%n 54 | 55 | 56 | ALL 57 | 58 | 59 | log/data/codec-%d{yyyy-MM-dd,aux}/codec-%d{yyyy-MM-dd}.%i.zip 60 | 32MB 61 | 62 | 63 | 64 | 65 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /target/lib/HdrHistogram-2.1.10.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/HdrHistogram-2.1.10.jar -------------------------------------------------------------------------------- /target/lib/HikariCP-2.7.9.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/HikariCP-2.7.9.jar -------------------------------------------------------------------------------- /target/lib/LatencyUtils-2.0.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/LatencyUtils-2.0.3.jar -------------------------------------------------------------------------------- /target/lib/accessors-smart-1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/accessors-smart-1.2.jar -------------------------------------------------------------------------------- /target/lib/android-json-0.0.20131108.vaadin1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/android-json-0.0.20131108.vaadin1.jar -------------------------------------------------------------------------------- /target/lib/asm-5.0.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/asm-5.0.4.jar -------------------------------------------------------------------------------- /target/lib/assertj-core-3.9.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/assertj-core-3.9.1.jar -------------------------------------------------------------------------------- /target/lib/attoparser-2.0.5.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/attoparser-2.0.5.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/byte-buddy-1.7.11.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/byte-buddy-1.7.11.jar -------------------------------------------------------------------------------- /target/lib/byte-buddy-agent-1.7.11.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/byte-buddy-agent-1.7.11.jar -------------------------------------------------------------------------------- /target/lib/classmate-1.3.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/classmate-1.3.4.jar -------------------------------------------------------------------------------- /target/lib/flyway-core-5.0.7.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/flyway-core-5.0.7.jar -------------------------------------------------------------------------------- /target/lib/hamcrest-core-1.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/hamcrest-core-1.3.jar -------------------------------------------------------------------------------- /target/lib/hamcrest-library-1.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/hamcrest-library-1.3.jar -------------------------------------------------------------------------------- /target/lib/hibernate-validator-6.0.14.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/hibernate-validator-6.0.14.Final.jar -------------------------------------------------------------------------------- /target/lib/jackson-annotations-2.9.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jackson-annotations-2.9.0.jar -------------------------------------------------------------------------------- /target/lib/jackson-core-2.9.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jackson-core-2.9.8.jar -------------------------------------------------------------------------------- /target/lib/jackson-databind-2.9.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jackson-databind-2.9.8.jar -------------------------------------------------------------------------------- /target/lib/jackson-datatype-jdk8-2.9.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jackson-datatype-jdk8-2.9.8.jar -------------------------------------------------------------------------------- /target/lib/jackson-datatype-joda-2.9.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jackson-datatype-joda-2.9.8.jar -------------------------------------------------------------------------------- /target/lib/jackson-datatype-jsr310-2.9.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jackson-datatype-jsr310-2.9.8.jar -------------------------------------------------------------------------------- /target/lib/jackson-jaxrs-base-2.9.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jackson-jaxrs-base-2.9.8.jar -------------------------------------------------------------------------------- /target/lib/jackson-jaxrs-json-provider-2.9.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jackson-jaxrs-json-provider-2.9.8.jar -------------------------------------------------------------------------------- /target/lib/jackson-module-jaxb-annotations-2.9.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jackson-module-jaxb-annotations-2.9.8.jar -------------------------------------------------------------------------------- /target/lib/jackson-module-parameter-names-2.9.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jackson-module-parameter-names-2.9.8.jar -------------------------------------------------------------------------------- /target/lib/javassist-3.22.0-GA.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/javassist-3.22.0-GA.jar -------------------------------------------------------------------------------- /target/lib/javax.annotation-api-1.3.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/javax.annotation-api-1.3.2.jar -------------------------------------------------------------------------------- /target/lib/jboss-logging-3.3.2.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jboss-logging-3.3.2.Final.jar -------------------------------------------------------------------------------- /target/lib/joda-time-2.9.9.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/joda-time-2.9.9.jar -------------------------------------------------------------------------------- /target/lib/jolokia-core-1.5.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jolokia-core-1.5.0.jar -------------------------------------------------------------------------------- /target/lib/json-path-2.4.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/json-path-2.4.0.jar -------------------------------------------------------------------------------- /target/lib/json-simple-1.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/json-simple-1.1.1.jar -------------------------------------------------------------------------------- /target/lib/json-smart-2.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/json-smart-2.3.jar -------------------------------------------------------------------------------- /target/lib/jsonassert-1.5.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jsonassert-1.5.0.jar -------------------------------------------------------------------------------- /target/lib/jsqlparser-1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jsqlparser-1.2.jar -------------------------------------------------------------------------------- /target/lib/jul-to-slf4j-1.7.25.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/jul-to-slf4j-1.7.25.jar -------------------------------------------------------------------------------- /target/lib/junit-4.12.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/junit-4.12.jar -------------------------------------------------------------------------------- /target/lib/log4j-api-2.10.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/log4j-api-2.10.0.jar -------------------------------------------------------------------------------- /target/lib/log4j-to-slf4j-2.10.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/log4j-to-slf4j-2.10.0.jar -------------------------------------------------------------------------------- /target/lib/logback-classic-1.2.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/logback-classic-1.2.3.jar -------------------------------------------------------------------------------- /target/lib/logback-core-1.2.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/logback-core-1.2.3.jar -------------------------------------------------------------------------------- /target/lib/logstash-logback-encoder-5.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/logstash-logback-encoder-5.3.jar -------------------------------------------------------------------------------- /target/lib/micrometer-core-1.0.9.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/micrometer-core-1.0.9.jar -------------------------------------------------------------------------------- /target/lib/mockito-core-2.15.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/mockito-core-2.15.0.jar -------------------------------------------------------------------------------- /target/lib/mybatis-3.5.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/mybatis-3.5.1.jar -------------------------------------------------------------------------------- /target/lib/mybatis-plus-3.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/mybatis-plus-3.1.1.jar -------------------------------------------------------------------------------- /target/lib/mybatis-plus-annotation-3.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/mybatis-plus-annotation-3.1.1.jar -------------------------------------------------------------------------------- /target/lib/mybatis-plus-boot-starter-3.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/mybatis-plus-boot-starter-3.1.1.jar -------------------------------------------------------------------------------- /target/lib/mybatis-plus-core-3.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/mybatis-plus-core-3.1.1.jar -------------------------------------------------------------------------------- /target/lib/mybatis-plus-extension-3.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/mybatis-plus-extension-3.1.1.jar -------------------------------------------------------------------------------- /target/lib/mybatis-spring-2.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/mybatis-spring-2.0.1.jar -------------------------------------------------------------------------------- /target/lib/netty-all-4.1.31.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/netty-all-4.1.31.Final.jar -------------------------------------------------------------------------------- /target/lib/netty-buffer-4.1.31.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/netty-buffer-4.1.31.Final.jar -------------------------------------------------------------------------------- /target/lib/netty-codec-4.1.31.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/netty-codec-4.1.31.Final.jar -------------------------------------------------------------------------------- /target/lib/netty-codec-http-4.1.31.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/netty-codec-http-4.1.31.Final.jar -------------------------------------------------------------------------------- /target/lib/netty-codec-socks-4.1.31.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/netty-codec-socks-4.1.31.Final.jar -------------------------------------------------------------------------------- /target/lib/netty-common-4.1.31.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/netty-common-4.1.31.Final.jar -------------------------------------------------------------------------------- /target/lib/netty-handler-4.1.31.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/netty-handler-4.1.31.Final.jar -------------------------------------------------------------------------------- /target/lib/netty-handler-proxy-4.1.31.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/netty-handler-proxy-4.1.31.Final.jar -------------------------------------------------------------------------------- /target/lib/netty-resolver-4.1.31.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/netty-resolver-4.1.31.Final.jar -------------------------------------------------------------------------------- /target/lib/netty-transport-4.1.31.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/netty-transport-4.1.31.Final.jar -------------------------------------------------------------------------------- /target/lib/netty-transport-native-epoll-4.1.31.Final-linux-x86_64.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/netty-transport-native-epoll-4.1.31.Final-linux-x86_64.jar -------------------------------------------------------------------------------- /target/lib/netty-transport-native-unix-common-4.1.31.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/netty-transport-native-unix-common-4.1.31.Final.jar -------------------------------------------------------------------------------- /target/lib/nio-multipart-parser-1.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/nio-multipart-parser-1.1.0.jar -------------------------------------------------------------------------------- /target/lib/nio-stream-storage-1.1.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/nio-stream-storage-1.1.3.jar -------------------------------------------------------------------------------- /target/lib/objenesis-2.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/objenesis-2.6.jar -------------------------------------------------------------------------------- /target/lib/reactive-streams-1.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/reactive-streams-1.0.2.jar -------------------------------------------------------------------------------- /target/lib/reactor-core-3.1.14.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/reactor-core-3.1.14.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/reactor-extra-3.1.9.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/reactor-extra-3.1.9.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/reactor-netty-0.7.14.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/reactor-netty-0.7.14.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/slf4j-api-1.7.25.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/slf4j-api-1.7.25.jar -------------------------------------------------------------------------------- /target/lib/snakeyaml-1.19.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/snakeyaml-1.19.jar -------------------------------------------------------------------------------- /target/lib/spring-aop-5.0.12.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-aop-5.0.12.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-beans-5.0.12.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-beans-5.0.12.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-actuator-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-actuator-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-actuator-autoconfigure-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-actuator-autoconfigure-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-admin-client-2.0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-admin-client-2.0.5.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-admin-server-2.0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-admin-server-2.0.5.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-admin-server-cloud-2.0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-admin-server-cloud-2.0.5.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-admin-server-ui-2.0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-admin-server-ui-2.0.5.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-admin-starter-client-2.0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-admin-starter-client-2.0.5.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-admin-starter-server-2.0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-admin-starter-server-2.0.5.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-autoconfigure-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-autoconfigure-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-configuration-processor-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-configuration-processor-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-devtools-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-devtools-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-starter-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-starter-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-starter-actuator-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-starter-actuator-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-starter-jdbc-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-starter-jdbc-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-starter-json-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-starter-json-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-starter-logging-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-starter-logging-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-starter-reactor-netty-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-starter-reactor-netty-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-starter-test-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-starter-test-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-starter-thymeleaf-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-starter-thymeleaf-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-starter-tomcat-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-starter-tomcat-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-starter-web-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-starter-web-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-starter-webflux-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-starter-webflux-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-test-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-test-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-boot-test-autoconfigure-2.0.8.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-boot-test-autoconfigure-2.0.8.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-context-5.0.12.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-context-5.0.12.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-core-5.0.12.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-core-5.0.12.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-expression-5.0.12.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-expression-5.0.12.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-jcl-5.0.12.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-jcl-5.0.12.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-jdbc-5.0.12.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-jdbc-5.0.12.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-test-5.0.12.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-test-5.0.12.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-tx-5.0.12.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-tx-5.0.12.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-web-5.0.12.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-web-5.0.12.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-webflux-5.0.12.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-webflux-5.0.12.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/spring-webmvc-5.0.12.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/spring-webmvc-5.0.12.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/sqlite-jdbc-3.23.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/sqlite-jdbc-3.23.1.jar -------------------------------------------------------------------------------- /target/lib/thymeleaf-3.0.11.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/thymeleaf-3.0.11.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/thymeleaf-extras-java8time-3.0.2.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/thymeleaf-extras-java8time-3.0.2.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/thymeleaf-spring5-3.0.11.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/thymeleaf-spring5-3.0.11.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/tomcat-embed-core-8.5.37.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/tomcat-embed-core-8.5.37.jar -------------------------------------------------------------------------------- /target/lib/tomcat-embed-el-8.5.37.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/tomcat-embed-el-8.5.37.jar -------------------------------------------------------------------------------- /target/lib/tomcat-embed-websocket-8.5.37.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/tomcat-embed-websocket-8.5.37.jar -------------------------------------------------------------------------------- /target/lib/unbescape-1.1.6.RELEASE.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/unbescape-1.1.6.RELEASE.jar -------------------------------------------------------------------------------- /target/lib/validation-api-2.0.1.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/validation-api-2.0.1.Final.jar -------------------------------------------------------------------------------- /target/lib/xmlunit-core-2.5.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/target/lib/xmlunit-core-2.5.1.jar -------------------------------------------------------------------------------- /ui/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /ui/.eslintignore: -------------------------------------------------------------------------------- 1 | /lambda/ 2 | /scripts 3 | /config 4 | -------------------------------------------------------------------------------- /ui/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { strictEslint } = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...strictEslint, 5 | globals: { 6 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true, 7 | page: true, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | **/node_modules 5 | # roadhog-api-doc ignore 6 | /src/utils/request-temp.js 7 | _roadhog-api-doc 8 | 9 | # production 10 | /dist 11 | /.vscode 12 | 13 | # misc 14 | .DS_Store 15 | npm-debug.log* 16 | yarn-error.log 17 | 18 | /coverage 19 | .idea 20 | 21 | package-lock.json 22 | *bak 23 | .vscode 24 | 25 | # visual studio code 26 | .history 27 | *.log 28 | functions/* 29 | .temp/** 30 | 31 | # umi 32 | .umi 33 | .umi-production 34 | 35 | # screenshot 36 | screenshot 37 | .firebase 38 | .eslintcache 39 | 40 | build 41 | -------------------------------------------------------------------------------- /ui/.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.svg 2 | package.json 3 | .umi 4 | .umi-production 5 | /dist 6 | .dockerignore 7 | .DS_Store 8 | .eslintignore 9 | *.png 10 | *.toml 11 | docker 12 | .editorconfig 13 | Dockerfile* 14 | .gitignore 15 | .prettierignore 16 | LICENSE 17 | .eslintcache 18 | *.lock 19 | yarn-error.log -------------------------------------------------------------------------------- /ui/.prettierrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.prettier, 5 | }; 6 | -------------------------------------------------------------------------------- /ui/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.stylelint, 5 | }; 6 | -------------------------------------------------------------------------------- /ui/CNAME: -------------------------------------------------------------------------------- 1 | preview.pro.ant.design -------------------------------------------------------------------------------- /ui/README.md: -------------------------------------------------------------------------------- 1 | # Ant Design Pro 2 | 3 | This project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use. 4 | 5 | ## Environment Prepare 6 | 7 | Install `node_modules`: 8 | 9 | ```bash 10 | npm install 11 | ``` 12 | 13 | or 14 | 15 | ```bash 16 | yarn 17 | ``` 18 | 19 | ## Provided Scripts 20 | 21 | Ant Design Pro provides some useful script to help you quick start and build with web project, code style check and test. 22 | 23 | Scripts provided in `package.json`. It's safe to modify or add additional script: 24 | 25 | ### Start project 26 | 27 | ```bash 28 | npm start 29 | ``` 30 | 31 | ### Build project 32 | 33 | ```bash 34 | npm run build 35 | ``` 36 | 37 | ### Check code style 38 | 39 | ```bash 40 | npm run lint 41 | ``` 42 | 43 | You can also use script to auto fix some lint error: 44 | 45 | ```bash 46 | npm run lint:fix 47 | ``` 48 | 49 | ### Test code 50 | 51 | ```bash 52 | npm test 53 | ``` 54 | 55 | ## More 56 | 57 | You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro). 58 | -------------------------------------------------------------------------------- /ui/config/defaultSettings.ts: -------------------------------------------------------------------------------- 1 | import { MenuTheme } from 'antd/es/menu'; 2 | 3 | export type ContentWidth = 'Fluid' | 'Fixed'; 4 | 5 | export interface DefaultSettings { 6 | /** 7 | * theme for nav menu 8 | */ 9 | navTheme: MenuTheme; 10 | /** 11 | * primary color of ant design 12 | */ 13 | primaryColor: string; 14 | /** 15 | * nav menu position: `sidemenu` or `topmenu` 16 | */ 17 | layout: 'sidemenu' | 'topmenu'; 18 | /** 19 | * layout of content: `Fluid` or `Fixed`, only works when layout is topmenu 20 | */ 21 | contentWidth: ContentWidth; 22 | /** 23 | * sticky header 24 | */ 25 | fixedHeader: boolean; 26 | /** 27 | * auto hide header 28 | */ 29 | autoHideHeader: boolean; 30 | /** 31 | * sticky siderbar 32 | */ 33 | fixSiderbar: boolean; 34 | menu: { locale: boolean }; 35 | title: string; 36 | pwa: boolean; 37 | // Your custom iconfont Symbol script Url 38 | // eg://at.alicdn.com/t/font_1039637_btcrd5co4w.js 39 | // 注意:如果需要图标多色,Iconfont 图标项目里要进行批量去色处理 40 | // Usage: https://github.com/ant-design/ant-design-pro/pull/3517 41 | iconfontUrl: string; 42 | colorWeak: boolean; 43 | } 44 | 45 | export default { 46 | navTheme: 'dark', 47 | primaryColor: '#1890FF', 48 | layout: 'sidemenu', 49 | contentWidth: 'Fluid', 50 | fixedHeader: false, 51 | autoHideHeader: false, 52 | fixSiderbar: false, 53 | colorWeak: false, 54 | menu: { 55 | locale: true, 56 | }, 57 | title: 'Ant Design Pro', 58 | pwa: false, 59 | iconfontUrl: '', 60 | } as DefaultSettings; 61 | -------------------------------------------------------------------------------- /ui/config/plugin.config.ts: -------------------------------------------------------------------------------- 1 | // Change theme plugin 2 | // eslint-disable-next-line eslint-comments/abdeils - enable - pair; 3 | /* eslint-disable import/no-extraneous-dependencies */ 4 | import ThemeColorReplacer from 'webpack-theme-color-replacer'; 5 | import generate from '@ant-design/colors/lib/generate'; 6 | import path from 'path'; 7 | 8 | function getModulePackageName(module: { context: string }) { 9 | if (!module.context) return null; 10 | 11 | const nodeModulesPath = path.join(__dirname, '../node_modules/'); 12 | if (module.context.substring(0, nodeModulesPath.length) !== nodeModulesPath) { 13 | return null; 14 | } 15 | 16 | const moduleRelativePath = module.context.substring(nodeModulesPath.length); 17 | const [moduleDirName] = moduleRelativePath.split(path.sep); 18 | let packageName: string | null = moduleDirName; 19 | // handle tree shaking 20 | if (packageName && packageName.match('^_')) { 21 | // eslint-disable-next-line prefer-destructuring 22 | packageName = packageName.match(/^_(@?[^@]+)/)![1]; 23 | } 24 | return packageName; 25 | } 26 | 27 | export default (config: any) => { 28 | // preview.pro.ant.design only do not use in your production; 29 | if ( 30 | process.env.ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' || 31 | process.env.NODE_ENV !== 'production' 32 | ) { 33 | config.plugin('webpack-theme-color-replacer').use(ThemeColorReplacer, [ 34 | { 35 | fileName: 'css/theme-colors-[contenthash:8].css', 36 | matchColors: getAntdSerials('#1890ff'), // 主色系列 37 | // 改变样式选择器,解决样式覆盖问题 38 | changeSelector(selector: string): string { 39 | switch (selector) { 40 | case '.ant-calendar-today .ant-calendar-date': 41 | return ':not(.ant-calendar-selected-date)' + selector; 42 | case '.ant-btn:focus,.ant-btn:hover': 43 | return '.ant-btn:focus:not(.ant-btn-primary),.ant-btn:hover:not(.ant-btn-primary)'; 44 | case '.ant-btn.active,.ant-btn:active': 45 | return '.ant-btn.active:not(.ant-btn-primary),.ant-btn:active:not(.ant-btn-primary)'; 46 | default: 47 | return selector; 48 | } 49 | }, 50 | // isJsUgly: true, 51 | }, 52 | ]); 53 | } 54 | 55 | // optimize chunks 56 | config.optimization 57 | // share the same chunks across different modules 58 | .runtimeChunk(false) 59 | .splitChunks({ 60 | chunks: 'async', 61 | name: 'vendors', 62 | maxInitialRequests: Infinity, 63 | minSize: 0, 64 | cacheGroups: { 65 | vendors: { 66 | test: (module: { context: string }) => { 67 | const packageName = getModulePackageName(module); 68 | if (packageName) { 69 | return ['bizcharts', '@antv_data-set'].indexOf(packageName) >= 0; 70 | } 71 | return false; 72 | }, 73 | name(module: { context: string }) { 74 | const packageName = getModulePackageName(module); 75 | if (packageName) { 76 | if (['bizcharts', '@antv_data-set'].indexOf(packageName) >= 0) { 77 | return 'viz'; // visualization package 78 | } 79 | } 80 | return 'misc'; 81 | }, 82 | }, 83 | }, 84 | }); 85 | }; 86 | 87 | const getAntdSerials = (color: string) => { 88 | const lightNum = 9; 89 | const devide10 = 10; 90 | // 淡化(即less的tint) 91 | const lightens = new Array(lightNum).fill(undefined).map((_, i: number) => { 92 | return ThemeColorReplacer.varyColor.lighten(color, i / devide10); 93 | }); 94 | const colorPalettes = generate(color); 95 | return lightens.concat(colorPalettes); 96 | }; 97 | -------------------------------------------------------------------------------- /ui/jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | // ps https://github.com/GoogleChrome/puppeteer/issues/3120 2 | module.exports = { 3 | launch: { 4 | args: [ 5 | '--disable-gpu', 6 | '--disable-dev-shm-usage', 7 | '--no-first-run', 8 | '--no-zygote', 9 | '--no-sandbox', 10 | ], 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /ui/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testURL: 'http://localhost:8000', 3 | preset: 'jest-puppeteer', 4 | globals: { 5 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /ui/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ui/mock/notices.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | 3 | const getNotices = (req: Request, res: Response) => { 4 | res.json([ 5 | { 6 | id: '000000001', 7 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', 8 | title: '你收到了 14 份新周报', 9 | datetime: '2017-08-09', 10 | type: 'notification', 11 | }, 12 | { 13 | id: '000000002', 14 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png', 15 | title: '你推荐的 曲妮妮 已通过第三轮面试', 16 | datetime: '2017-08-08', 17 | type: 'notification', 18 | }, 19 | { 20 | id: '000000003', 21 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png', 22 | title: '这种模板可以区分多种通知类型', 23 | datetime: '2017-08-07', 24 | read: true, 25 | type: 'notification', 26 | }, 27 | { 28 | id: '000000004', 29 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', 30 | title: '左侧图标用于区分不同的类型', 31 | datetime: '2017-08-07', 32 | type: 'notification', 33 | }, 34 | { 35 | id: '000000005', 36 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', 37 | title: '内容不要超过两行字,超出时自动截断', 38 | datetime: '2017-08-07', 39 | type: 'notification', 40 | }, 41 | { 42 | id: '000000006', 43 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', 44 | title: '曲丽丽 评论了你', 45 | description: '描述信息描述信息描述信息', 46 | datetime: '2017-08-07', 47 | type: 'message', 48 | clickClose: true, 49 | }, 50 | { 51 | id: '000000007', 52 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', 53 | title: '朱偏右 回复了你', 54 | description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', 55 | datetime: '2017-08-07', 56 | type: 'message', 57 | clickClose: true, 58 | }, 59 | { 60 | id: '000000008', 61 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', 62 | title: '标题', 63 | description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', 64 | datetime: '2017-08-07', 65 | type: 'message', 66 | clickClose: true, 67 | }, 68 | { 69 | id: '000000009', 70 | title: '任务名称', 71 | description: '任务需要在 2017-01-12 20:00 前启动', 72 | extra: '未开始', 73 | status: 'todo', 74 | type: 'event', 75 | }, 76 | { 77 | id: '000000010', 78 | title: '第三方紧急代码变更', 79 | description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', 80 | extra: '马上到期', 81 | status: 'urgent', 82 | type: 'event', 83 | }, 84 | { 85 | id: '000000011', 86 | title: '信息安全考试', 87 | description: '指派竹尔于 2017-01-09 前完成更新并发布', 88 | extra: '已耗时 8 天', 89 | status: 'doing', 90 | type: 'event', 91 | }, 92 | { 93 | id: '000000012', 94 | title: 'ABCD 版本发布', 95 | description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', 96 | extra: '进行中', 97 | status: 'processing', 98 | type: 'event', 99 | }, 100 | ]); 101 | }; 102 | 103 | export default { 104 | 'GET /api/notices': getNotices, 105 | }; 106 | -------------------------------------------------------------------------------- /ui/mock/route.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | '/api/auth_routes': { 3 | '/form/advanced-form': { authority: ['admin', 'user'] }, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /ui/mock/user.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | // 代码中会兼容本地 service mock 以及部署站点的静态数据 3 | export default { 4 | // 支持值为 Object 和 Array 5 | 'GET /api/currentUser': { 6 | name: 'Serati Ma', 7 | avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png', 8 | userid: '00000001', 9 | email: 'antdesign@alipay.com', 10 | signature: '海纳百川,有容乃大', 11 | title: '交互专家', 12 | group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', 13 | tags: [ 14 | { 15 | key: '0', 16 | label: '很有想法的', 17 | }, 18 | { 19 | key: '1', 20 | label: '专注设计', 21 | }, 22 | { 23 | key: '2', 24 | label: '辣~', 25 | }, 26 | { 27 | key: '3', 28 | label: '大长腿', 29 | }, 30 | { 31 | key: '4', 32 | label: '川妹子', 33 | }, 34 | { 35 | key: '5', 36 | label: '海纳百川', 37 | }, 38 | ], 39 | notifyCount: 12, 40 | unreadCount: 11, 41 | country: 'China', 42 | geographic: { 43 | province: { 44 | label: '浙江省', 45 | key: '330000', 46 | }, 47 | city: { 48 | label: '杭州市', 49 | key: '330100', 50 | }, 51 | }, 52 | address: '西湖区工专路 77 号', 53 | phone: '0752-268888888', 54 | }, 55 | // GET POST 可省略 56 | 'GET /api/users': [ 57 | { 58 | key: '1', 59 | name: 'John Brown', 60 | age: 32, 61 | address: 'New York No. 1 Lake Park', 62 | }, 63 | { 64 | key: '2', 65 | name: 'Jim Green', 66 | age: 42, 67 | address: 'London No. 1 Lake Park', 68 | }, 69 | { 70 | key: '3', 71 | name: 'Joe Black', 72 | age: 32, 73 | address: 'Sidney No. 1 Lake Park', 74 | }, 75 | ], 76 | 'POST /api/login/account': (req: Request, res: Response) => { 77 | const { password, userName, type } = req.body; 78 | if (password === 'ant.design' && userName === 'admin') { 79 | res.send({ 80 | status: 'ok', 81 | type, 82 | currentAuthority: 'admin', 83 | }); 84 | return; 85 | } 86 | if (password === 'ant.design' && userName === 'user') { 87 | res.send({ 88 | status: 'ok', 89 | type, 90 | currentAuthority: 'user', 91 | }); 92 | return; 93 | } 94 | res.send({ 95 | status: 'error', 96 | type, 97 | currentAuthority: 'guest', 98 | }); 99 | }, 100 | 'POST /api/register': (req: Request, res: Response) => { 101 | res.send({ status: 'ok', currentAuthority: 'user' }); 102 | }, 103 | 'GET /api/500': (req: Request, res: Response) => { 104 | res.status(500).send({ 105 | timestamp: 1513932555104, 106 | status: 500, 107 | error: 'error', 108 | message: 'error', 109 | path: '/base/category/list', 110 | }); 111 | }, 112 | 'GET /api/404': (req: Request, res: Response) => { 113 | res.status(404).send({ 114 | timestamp: 1513932643431, 115 | status: 404, 116 | error: 'Not Found', 117 | message: 'No message available', 118 | path: '/base/category/list/2121212', 119 | }); 120 | }, 121 | 'GET /api/403': (req: Request, res: Response) => { 122 | res.status(403).send({ 123 | timestamp: 1513932555104, 124 | status: 403, 125 | error: 'Unauthorized', 126 | message: 'Unauthorized', 127 | path: '/base/category/list', 128 | }); 129 | }, 130 | 'GET /api/401': (req: Request, res: Response) => { 131 | res.status(401).send({ 132 | timestamp: 1513932555104, 133 | status: 401, 134 | error: 'Unauthorized', 135 | message: 'Unauthorized', 136 | path: '/base/category/list', 137 | }); 138 | }, 139 | }; 140 | -------------------------------------------------------------------------------- /ui/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/ui/public/favicon.png -------------------------------------------------------------------------------- /ui/public/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/ui/public/icons/icon-128x128.png -------------------------------------------------------------------------------- /ui/public/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/ui/public/icons/icon-192x192.png -------------------------------------------------------------------------------- /ui/public/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreatGarlic/NettyReverseProxy/d30509c69ac9d36df1021b238db2939b252c0dd1/ui/public/icons/icon-512x512.png -------------------------------------------------------------------------------- /ui/src/components/Authorized/Authorized.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import check, { IAuthorityType } from './CheckPermissions'; 3 | 4 | import AuthorizedRoute from './AuthorizedRoute'; 5 | import Secured from './Secured'; 6 | 7 | interface AuthorizedProps { 8 | authority: IAuthorityType; 9 | noMatch?: React.ReactNode; 10 | } 11 | 12 | type IAuthorizedType = React.FunctionComponent & { 13 | Secured: typeof Secured; 14 | check: typeof check; 15 | AuthorizedRoute: typeof AuthorizedRoute; 16 | }; 17 | 18 | const Authorized: React.FunctionComponent = ({ 19 | children, 20 | authority, 21 | noMatch = null, 22 | }) => { 23 | const childrenRender: React.ReactNode = typeof children === 'undefined' ? null : children; 24 | const dom = check(authority, childrenRender, noMatch); 25 | return <>{dom}; 26 | }; 27 | 28 | export default Authorized as IAuthorizedType; 29 | -------------------------------------------------------------------------------- /ui/src/components/Authorized/AuthorizedRoute.tsx: -------------------------------------------------------------------------------- 1 | import { Redirect, Route } from 'umi'; 2 | 3 | import React from 'react'; 4 | import Authorized from './Authorized'; 5 | import { IAuthorityType } from './CheckPermissions'; 6 | 7 | interface AuthorizedRoutePops { 8 | currentAuthority: string; 9 | component: React.ComponentClass; 10 | render: (props: any) => React.ReactNode; 11 | redirectPath: string; 12 | authority: IAuthorityType; 13 | } 14 | 15 | const AuthorizedRoute: React.SFC = ({ 16 | component: Component, 17 | render, 18 | authority, 19 | redirectPath, 20 | ...rest 21 | }) => ( 22 | } />} 25 | > 26 | (Component ? : render(props))} 29 | /> 30 | 31 | ); 32 | 33 | export default AuthorizedRoute; 34 | -------------------------------------------------------------------------------- /ui/src/components/Authorized/CheckPermissions.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CURRENT } from './renderAuthorize'; 3 | // eslint-disable-next-line import/no-cycle 4 | import PromiseRender from './PromiseRender'; 5 | 6 | export type IAuthorityType = 7 | | undefined 8 | | string 9 | | string[] 10 | | Promise 11 | | ((currentAuthority: string | string[]) => IAuthorityType); 12 | 13 | /** 14 | * 通用权限检查方法 15 | * Common check permissions method 16 | * @param { 权限判定 | Permission judgment } authority 17 | * @param { 你的权限 | Your permission description } currentAuthority 18 | * @param { 通过的组件 | Passing components } target 19 | * @param { 未通过的组件 | no pass components } Exception 20 | */ 21 | const checkPermissions = ( 22 | authority: IAuthorityType, 23 | currentAuthority: string | string[], 24 | target: T, 25 | Exception: K, 26 | ): T | K | React.ReactNode => { 27 | // 没有判定权限.默认查看所有 28 | // Retirement authority, return target; 29 | if (!authority) { 30 | return target; 31 | } 32 | // 数组处理 33 | if (Array.isArray(authority)) { 34 | if (Array.isArray(currentAuthority)) { 35 | if (currentAuthority.some(item => authority.includes(item))) { 36 | return target; 37 | } 38 | } else if (authority.includes(currentAuthority)) { 39 | return target; 40 | } 41 | return Exception; 42 | } 43 | // string 处理 44 | if (typeof authority === 'string') { 45 | if (Array.isArray(currentAuthority)) { 46 | if (currentAuthority.some(item => authority === item)) { 47 | return target; 48 | } 49 | } else if (authority === currentAuthority) { 50 | return target; 51 | } 52 | return Exception; 53 | } 54 | // Promise 处理 55 | if (authority instanceof Promise) { 56 | return ok={target} error={Exception} promise={authority} />; 57 | } 58 | // Function 处理 59 | if (typeof authority === 'function') { 60 | try { 61 | const bool = authority(currentAuthority); 62 | // 函数执行后返回值是 Promise 63 | if (bool instanceof Promise) { 64 | return ok={target} error={Exception} promise={bool} />; 65 | } 66 | if (bool) { 67 | return target; 68 | } 69 | return Exception; 70 | } catch (error) { 71 | throw error; 72 | } 73 | } 74 | throw new Error('unsupported parameters'); 75 | }; 76 | 77 | export { checkPermissions }; 78 | 79 | function check(authority: IAuthorityType, target: T, Exception: K): T | K | React.ReactNode { 80 | return checkPermissions(authority, CURRENT, target, Exception); 81 | } 82 | 83 | export default check; 84 | -------------------------------------------------------------------------------- /ui/src/components/Authorized/PromiseRender.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Spin } from 'antd'; 3 | import isEqual from 'lodash/isEqual'; 4 | import { isComponentClass } from './Secured'; 5 | // eslint-disable-next-line import/no-cycle 6 | 7 | interface PromiseRenderProps { 8 | ok: T; 9 | error: K; 10 | promise: Promise; 11 | } 12 | 13 | interface PromiseRenderState { 14 | component: React.ComponentClass | React.FunctionComponent; 15 | } 16 | 17 | export default class PromiseRender extends React.Component< 18 | PromiseRenderProps, 19 | PromiseRenderState 20 | > { 21 | state: PromiseRenderState = { 22 | component: () => null, 23 | }; 24 | 25 | componentDidMount() { 26 | this.setRenderComponent(this.props); 27 | } 28 | 29 | shouldComponentUpdate = (nextProps: PromiseRenderProps, nextState: PromiseRenderState) => { 30 | const { component } = this.state; 31 | if (!isEqual(nextProps, this.props)) { 32 | this.setRenderComponent(nextProps); 33 | } 34 | if (nextState.component !== component) return true; 35 | return false; 36 | }; 37 | 38 | // set render Component : ok or error 39 | setRenderComponent(props: PromiseRenderProps) { 40 | const ok = this.checkIsInstantiation(props.ok); 41 | const error = this.checkIsInstantiation(props.error); 42 | props.promise 43 | .then(() => { 44 | this.setState({ 45 | component: ok, 46 | }); 47 | return true; 48 | }) 49 | .catch(() => { 50 | this.setState({ 51 | component: error, 52 | }); 53 | }); 54 | } 55 | 56 | // Determine whether the incoming component has been instantiated 57 | // AuthorizedRoute is already instantiated 58 | // Authorized render is already instantiated, children is no instantiated 59 | // Secured is not instantiated 60 | checkIsInstantiation = ( 61 | target: React.ReactNode | React.ComponentClass, 62 | ): React.FunctionComponent => { 63 | if (isComponentClass(target)) { 64 | const Target = target as React.ComponentClass; 65 | return (props: any) => ; 66 | } 67 | if (React.isValidElement(target)) { 68 | return (props: any) => React.cloneElement(target, props); 69 | } 70 | return () => target as (React.ReactNode & null); 71 | }; 72 | 73 | render() { 74 | const { component: Component } = this.state; 75 | const { ok, error, promise, ...rest } = this.props; 76 | 77 | return Component ? ( 78 | 79 | ) : ( 80 |
89 | 90 |
91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /ui/src/components/Authorized/Secured.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CheckPermissions from './CheckPermissions'; 3 | 4 | /** 5 | * 默认不能访问任何页面 6 | * default is "NULL" 7 | */ 8 | const Exception403 = () => 403; 9 | 10 | export const isComponentClass = (component: React.ComponentClass | React.ReactNode): boolean => { 11 | if (!component) return false; 12 | const proto = Object.getPrototypeOf(component); 13 | if (proto === React.Component || proto === Function.prototype) return true; 14 | return isComponentClass(proto); 15 | }; 16 | 17 | // Determine whether the incoming component has been instantiated 18 | // AuthorizedRoute is already instantiated 19 | // Authorized render is already instantiated, children is no instantiated 20 | // Secured is not instantiated 21 | const checkIsInstantiation = (target: React.ComponentClass | React.ReactNode) => { 22 | if (isComponentClass(target)) { 23 | const Target = target as React.ComponentClass; 24 | return (props: any) => ; 25 | } 26 | if (React.isValidElement(target)) { 27 | return (props: any) => React.cloneElement(target, props); 28 | } 29 | return () => target; 30 | }; 31 | 32 | /** 33 | * 用于判断是否拥有权限访问此 view 权限 34 | * authority 支持传入 string, () => boolean | Promise 35 | * e.g. 'user' 只有 user 用户能访问 36 | * e.g. 'user,admin' user 和 admin 都能访问 37 | * e.g. ()=>boolean 返回true能访问,返回false不能访问 38 | * e.g. Promise then 能访问 catch不能访问 39 | * e.g. authority support incoming string, () => boolean | Promise 40 | * e.g. 'user' only user user can access 41 | * e.g. 'user, admin' user and admin can access 42 | * e.g. () => boolean true to be able to visit, return false can not be accessed 43 | * e.g. Promise then can not access the visit to catch 44 | * @param {string | function | Promise} authority 45 | * @param {ReactNode} error 非必需参数 46 | */ 47 | const authorize = (authority: string, error?: React.ReactNode) => { 48 | /** 49 | * conversion into a class 50 | * 防止传入字符串时找不到staticContext造成报错 51 | * String parameters can cause staticContext not found error 52 | */ 53 | let classError: boolean | React.FunctionComponent = false; 54 | if (error) { 55 | classError = (() => error) as React.FunctionComponent; 56 | } 57 | if (!authority) { 58 | throw new Error('authority is required'); 59 | } 60 | return function decideAuthority(target: React.ComponentClass | React.ReactNode) { 61 | const component = CheckPermissions(authority, target, classError || Exception403); 62 | return checkIsInstantiation(component); 63 | }; 64 | }; 65 | 66 | export default authorize; 67 | -------------------------------------------------------------------------------- /ui/src/components/Authorized/index.tsx: -------------------------------------------------------------------------------- 1 | import Authorized from './Authorized'; 2 | import AuthorizedRoute from './AuthorizedRoute'; 3 | import Secured from './Secured'; 4 | import check from './CheckPermissions'; 5 | import renderAuthorize from './renderAuthorize'; 6 | 7 | Authorized.Secured = Secured; 8 | Authorized.AuthorizedRoute = AuthorizedRoute; 9 | Authorized.check = check; 10 | 11 | const RenderAuthorize = renderAuthorize(Authorized); 12 | 13 | export default RenderAuthorize; 14 | -------------------------------------------------------------------------------- /ui/src/components/Authorized/renderAuthorize.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable eslint-comments/disable-enable-pair */ 2 | /* eslint-disable import/no-mutable-exports */ 3 | let CURRENT: string | string[] = 'NULL'; 4 | 5 | type CurrentAuthorityType = string | string[] | (() => typeof CURRENT); 6 | /** 7 | * use authority or getAuthority 8 | * @param {string|()=>String} currentAuthority 9 | */ 10 | const renderAuthorize = (Authorized: T): ((currentAuthority: CurrentAuthorityType) => T) => ( 11 | currentAuthority: CurrentAuthorityType, 12 | ): T => { 13 | if (currentAuthority) { 14 | if (typeof currentAuthority === 'function') { 15 | CURRENT = currentAuthority(); 16 | } 17 | if ( 18 | Object.prototype.toString.call(currentAuthority) === '[object String]' || 19 | Array.isArray(currentAuthority) 20 | ) { 21 | CURRENT = currentAuthority as string[]; 22 | } 23 | } else { 24 | CURRENT = 'NULL'; 25 | } 26 | return Authorized; 27 | }; 28 | 29 | export { CURRENT }; 30 | export default (Authorized: T) => renderAuthorize(Authorized); 31 | -------------------------------------------------------------------------------- /ui/src/components/CopyBlock/index.less: -------------------------------------------------------------------------------- 1 | .copy-block { 2 | position: fixed; 3 | right: 80px; 4 | bottom: 40px; 5 | z-index: 99; 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | justify-content: center; 10 | width: 40px; 11 | height: 40px; 12 | font-size: 20px; 13 | background: #fff; 14 | border-radius: 40px; 15 | box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 16 | 0 1px 10px 0 rgba(0, 0, 0, 0.12); 17 | cursor: pointer; 18 | } 19 | 20 | .copy-block-view { 21 | position: relative; 22 | .copy-block-code { 23 | display: inline-block; 24 | margin: 0 0.2em; 25 | padding: 0.2em 0.4em 0.1em; 26 | font-size: 85%; 27 | border-radius: 3px; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ui/src/components/CopyBlock/index.tsx: -------------------------------------------------------------------------------- 1 | import { Icon, Popover, Typography } from 'antd'; 2 | import React, { useRef } from 'react'; 3 | 4 | import { FormattedMessage } from 'umi-plugin-react/locale'; 5 | import { connect } from 'dva'; 6 | import { isAntDesignPro } from '@/utils/utils'; 7 | import styles from './index.less'; 8 | 9 | const firstUpperCase = (pathString: string): string => 10 | pathString 11 | .replace('.', '') 12 | .split(/\/|-/) 13 | .map((s): string => s.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase())) 14 | .filter((s): boolean => !!s) 15 | .join(''); 16 | 17 | // when click block copy, send block url to ga 18 | const onBlockCopy = (label: string) => { 19 | if (!isAntDesignPro()) { 20 | return; 21 | } 22 | 23 | const ga = window && window.ga; 24 | if (ga) { 25 | ga('send', 'event', { 26 | eventCategory: 'block', 27 | eventAction: 'copy', 28 | eventLabel: label, 29 | }); 30 | } 31 | }; 32 | 33 | const BlockCodeView: React.SFC<{ 34 | url: string; 35 | }> = ({ url }) => { 36 | const blockUrl = `npx umi block add ${firstUpperCase(url)} --path=${url}`; 37 | return ( 38 |
39 | onBlockCopy(url), 43 | }} 44 | style={{ 45 | display: 'flex', 46 | }} 47 | > 48 |
49 |           {blockUrl}
50 |         
51 |
52 |
53 | ); 54 | }; 55 | 56 | interface RoutingType { 57 | location: { 58 | pathname: string; 59 | }; 60 | } 61 | 62 | export default connect(({ routing }: { routing: RoutingType }) => ({ 63 | location: routing.location, 64 | }))(({ location }: RoutingType) => { 65 | const url = location.pathname; 66 | const divDom = useRef(null); 67 | return ( 68 | } 70 | placement="topLeft" 71 | content={} 72 | trigger="click" 73 | getPopupContainer={dom => (divDom.current ? divDom.current : dom)} 74 | > 75 |
76 | 77 |
78 |
79 | ); 80 | }); 81 | -------------------------------------------------------------------------------- /ui/src/components/GlobalHeader/AvatarDropdown.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, Icon, Menu, Spin } from 'antd'; 2 | import { ClickParam } from 'antd/es/menu'; 3 | import { FormattedMessage } from 'umi-plugin-react/locale'; 4 | import React from 'react'; 5 | import { connect } from 'dva'; 6 | import router from 'umi/router'; 7 | 8 | import { ConnectProps, ConnectState } from '@/models/connect'; 9 | import { CurrentUser } from '@/models/user'; 10 | import HeaderDropdown from '../HeaderDropdown'; 11 | import styles from './index.less'; 12 | 13 | export interface GlobalHeaderRightProps extends ConnectProps { 14 | currentUser?: CurrentUser; 15 | menu?: boolean; 16 | } 17 | 18 | class AvatarDropdown extends React.Component { 19 | onMenuClick = (event: ClickParam) => { 20 | const { key } = event; 21 | 22 | if (key === 'logout') { 23 | const { dispatch } = this.props; 24 | if (dispatch) { 25 | dispatch({ 26 | type: 'login/logout', 27 | }); 28 | } 29 | 30 | return; 31 | } 32 | router.push(`/account/${key}`); 33 | }; 34 | 35 | render(): React.ReactNode { 36 | const { currentUser = {}, menu } = this.props; 37 | if (!menu) { 38 | return ( 39 | 40 | 41 | {currentUser.name} 42 | 43 | ); 44 | } 45 | const menuHeaderDropdown = ( 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | ); 62 | 63 | return currentUser && currentUser.name ? ( 64 | 65 | 66 | 67 | {currentUser.name} 68 | 69 | 70 | ) : ( 71 | 72 | ); 73 | } 74 | } 75 | export default connect(({ user }: ConnectState) => ({ 76 | currentUser: user.currentUser, 77 | }))(AvatarDropdown); 78 | -------------------------------------------------------------------------------- /ui/src/components/GlobalHeader/RightContent.tsx: -------------------------------------------------------------------------------- 1 | import { Icon, Tooltip } from 'antd'; 2 | import React from 'react'; 3 | import { connect } from 'dva'; 4 | import { formatMessage } from 'umi-plugin-react/locale'; 5 | import { ConnectProps, ConnectState } from '@/models/connect'; 6 | 7 | import Avatar from './AvatarDropdown'; 8 | import HeaderSearch from '../HeaderSearch'; 9 | import SelectLang from '../SelectLang'; 10 | import styles from './index.less'; 11 | 12 | export type SiderTheme = 'light' | 'dark'; 13 | export interface GlobalHeaderRightProps extends ConnectProps { 14 | theme?: SiderTheme; 15 | layout: 'sidemenu' | 'topmenu'; 16 | } 17 | 18 | const GlobalHeaderRight: React.SFC = props => { 19 | const { theme, layout } = props; 20 | let className = styles.right; 21 | 22 | if (theme === 'dark' && layout === 'topmenu') { 23 | className = `${styles.right} ${styles.dark}`; 24 | } 25 | 26 | return ( 27 |
28 | { 45 | console.log('input', value); 46 | }} 47 | onPressEnter={value => { 48 | console.log('enter', value); 49 | }} 50 | /> 51 | 56 | 62 | 63 | 64 | 65 | 66 | 67 |
68 | ); 69 | }; 70 | 71 | export default connect(({ settings }: ConnectState) => ({ 72 | theme: settings.navTheme, 73 | layout: settings.layout, 74 | }))(GlobalHeaderRight); 75 | -------------------------------------------------------------------------------- /ui/src/components/GlobalHeader/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | @pro-header-hover-bg: rgba(0, 0, 0, 0.025); 4 | 5 | .logo { 6 | display: inline-block; 7 | height: @layout-header-height; 8 | padding: 0 0 0 24px; 9 | font-size: 20px; 10 | line-height: @layout-header-height; 11 | vertical-align: top; 12 | cursor: pointer; 13 | img { 14 | display: inline-block; 15 | vertical-align: middle; 16 | } 17 | } 18 | 19 | .menu { 20 | :global(.anticon) { 21 | margin-right: 8px; 22 | } 23 | :global(.ant-dropdown-menu-item) { 24 | min-width: 160px; 25 | } 26 | } 27 | 28 | .trigger { 29 | height: @layout-header-height; 30 | padding: ~'calc((@{layout-header-height} - 20px) / 2)' 24px; 31 | font-size: 20px; 32 | cursor: pointer; 33 | transition: all 0.3s, padding 0s; 34 | &:hover { 35 | background: @pro-header-hover-bg; 36 | } 37 | } 38 | 39 | .right { 40 | float: right; 41 | height: 100%; 42 | overflow: hidden; 43 | .action { 44 | display: inline-block; 45 | height: 100%; 46 | padding: 0 12px; 47 | cursor: pointer; 48 | transition: all 0.3s; 49 | > i { 50 | color: @text-color; 51 | vertical-align: middle; 52 | } 53 | &:hover { 54 | background: @pro-header-hover-bg; 55 | } 56 | &:global(.opened) { 57 | background: @pro-header-hover-bg; 58 | } 59 | } 60 | .search { 61 | padding: 0 12px; 62 | &:hover { 63 | background: transparent; 64 | } 65 | } 66 | .account { 67 | .avatar { 68 | margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0; 69 | margin-right: 8px; 70 | color: @primary-color; 71 | vertical-align: top; 72 | background: rgba(255, 255, 255, 0.85); 73 | } 74 | } 75 | } 76 | 77 | .dark { 78 | height: @layout-header-height; 79 | .action { 80 | color: rgba(255, 255, 255, 0.85); 81 | > i { 82 | color: rgba(255, 255, 255, 0.85); 83 | } 84 | &:hover, 85 | &:global(.opened) { 86 | background: @primary-color; 87 | } 88 | :global(.ant-badge) { 89 | color: rgba(255, 255, 255, 0.85); 90 | } 91 | } 92 | } 93 | 94 | @media only screen and (max-width: @screen-md) { 95 | :global(.ant-divider-vertical) { 96 | vertical-align: unset; 97 | } 98 | .name { 99 | display: none; 100 | } 101 | i.trigger { 102 | padding: 22px 12px; 103 | } 104 | .logo { 105 | position: relative; 106 | padding-right: 12px; 107 | padding-left: 12px; 108 | } 109 | .right { 110 | position: absolute; 111 | top: 0; 112 | right: 12px; 113 | background: #fff; 114 | .account { 115 | .avatar { 116 | margin-right: 0; 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /ui/src/components/HeaderDropdown/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .container > * { 4 | background-color: #fff; 5 | border-radius: 4px; 6 | box-shadow: @shadow-1-down; 7 | } 8 | 9 | @media screen and (max-width: @screen-xs) { 10 | .container { 11 | width: 100% !important; 12 | } 13 | .container > * { 14 | border-radius: 0 !important; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ui/src/components/HeaderDropdown/index.tsx: -------------------------------------------------------------------------------- 1 | import { DropDownProps } from 'antd/es/dropdown'; 2 | import { Dropdown } from 'antd'; 3 | import React from 'react'; 4 | import classNames from 'classnames'; 5 | import styles from './index.less'; 6 | 7 | declare type OverlayFunc = () => React.ReactNode; 8 | 9 | export interface HeaderDropdownProps extends DropDownProps { 10 | overlayClassName?: string; 11 | overlay: React.ReactNode | OverlayFunc; 12 | placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter'; 13 | } 14 | 15 | const HeaderDropdown: React.FC = ({ overlayClassName: cls, ...restProps }) => ( 16 | 17 | ); 18 | 19 | export default HeaderDropdown; 20 | -------------------------------------------------------------------------------- /ui/src/components/HeaderSearch/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .headerSearch { 4 | :global(.anticon-search) { 5 | font-size: 16px; 6 | cursor: pointer; 7 | } 8 | .input { 9 | width: 0; 10 | background: transparent; 11 | border-radius: 0; 12 | transition: width 0.3s, margin-left 0.3s; 13 | :global(.ant-select-selection) { 14 | background: transparent; 15 | } 16 | input { 17 | padding-right: 0; 18 | padding-left: 0; 19 | border: 0; 20 | box-shadow: none !important; 21 | } 22 | &, 23 | &:hover, 24 | &:focus { 25 | border-bottom: 1px solid @border-color-base; 26 | } 27 | &.show { 28 | width: 210px; 29 | margin-left: 8px; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ui/src/components/NoticeIcon/NoticeList.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .list { 4 | max-height: 400px; 5 | overflow: auto; 6 | &::-webkit-scrollbar { 7 | display: none; 8 | } 9 | .item { 10 | padding-right: 24px; 11 | padding-left: 24px; 12 | overflow: hidden; 13 | cursor: pointer; 14 | transition: all 0.3s; 15 | 16 | .meta { 17 | width: 100%; 18 | } 19 | 20 | .avatar { 21 | margin-top: 4px; 22 | background: #fff; 23 | } 24 | .iconElement { 25 | font-size: 32px; 26 | } 27 | 28 | &.read { 29 | opacity: 0.4; 30 | } 31 | &:last-child { 32 | border-bottom: 0; 33 | } 34 | &:hover { 35 | background: @primary-1; 36 | } 37 | .title { 38 | margin-bottom: 8px; 39 | font-weight: normal; 40 | } 41 | .description { 42 | font-size: 12px; 43 | line-height: @line-height-base; 44 | } 45 | .datetime { 46 | margin-top: 4px; 47 | font-size: 12px; 48 | line-height: @line-height-base; 49 | } 50 | .extra { 51 | float: right; 52 | margin-top: -1.5px; 53 | margin-right: 0; 54 | color: @text-color-secondary; 55 | font-weight: normal; 56 | } 57 | } 58 | .loadMore { 59 | padding: 8px 0; 60 | color: @primary-6; 61 | text-align: center; 62 | cursor: pointer; 63 | &.loadedAll { 64 | color: rgba(0, 0, 0, 0.25); 65 | cursor: unset; 66 | } 67 | } 68 | } 69 | 70 | .notFound { 71 | padding: 73px 0 88px; 72 | color: @text-color-secondary; 73 | text-align: center; 74 | img { 75 | display: inline-block; 76 | height: 76px; 77 | margin-bottom: 16px; 78 | } 79 | } 80 | 81 | .bottomBar { 82 | height: 46px; 83 | color: @text-color; 84 | line-height: 46px; 85 | text-align: center; 86 | border-top: 1px solid @border-color-split; 87 | border-radius: 0 0 @border-radius-base @border-radius-base; 88 | transition: all 0.3s; 89 | div { 90 | display: inline-block; 91 | width: 50%; 92 | cursor: pointer; 93 | transition: all 0.3s; 94 | user-select: none; 95 | &:hover { 96 | color: @heading-color; 97 | } 98 | &:only-child { 99 | width: 100%; 100 | } 101 | &:not(:only-child):last-child { 102 | border-left: 1px solid @border-color-split; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /ui/src/components/NoticeIcon/NoticeList.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, List } from 'antd'; 2 | 3 | import React from 'react'; 4 | import classNames from 'classnames'; 5 | import { NoticeIconData } from './index'; 6 | import styles from './NoticeList.less'; 7 | 8 | export interface NoticeIconTabProps { 9 | count?: number; 10 | name?: string; 11 | showClear?: boolean; 12 | showViewMore?: boolean; 13 | style?: React.CSSProperties; 14 | title: string; 15 | tabKey: string; 16 | data?: NoticeIconData[]; 17 | onClick?: (item: NoticeIconData) => void; 18 | onClear?: () => void; 19 | emptyText?: string; 20 | clearText?: string; 21 | viewMoreText?: string; 22 | list: NoticeIconData[]; 23 | onViewMore?: (e: any) => void; 24 | } 25 | const NoticeList: React.SFC = ({ 26 | data = [], 27 | onClick, 28 | onClear, 29 | title, 30 | onViewMore, 31 | emptyText, 32 | showClear = true, 33 | clearText, 34 | viewMoreText, 35 | showViewMore = false, 36 | }) => { 37 | if (data.length === 0) { 38 | return ( 39 |
40 | not found 44 |
{emptyText}
45 |
46 | ); 47 | } 48 | return ( 49 |
50 | 51 | className={styles.list} 52 | dataSource={data} 53 | renderItem={(item, i) => { 54 | const itemCls = classNames(styles.item, { 55 | [styles.read]: item.read, 56 | }); 57 | // eslint-disable-next-line no-nested-ternary 58 | const leftIcon = item.avatar ? ( 59 | typeof item.avatar === 'string' ? ( 60 | 61 | ) : ( 62 | {item.avatar} 63 | ) 64 | ) : null; 65 | 66 | return ( 67 | onClick && onClick(item)} 71 | > 72 | 77 | {item.title} 78 |
{item.extra}
79 |
80 | } 81 | description={ 82 |
83 |
{item.description}
84 |
{item.datetime}
85 |
86 | } 87 | /> 88 | 89 | ); 90 | }} 91 | /> 92 |
93 | {showClear ? ( 94 |
95 | {clearText} {title} 96 |
97 | ) : null} 98 | {showViewMore ? ( 99 |
{ 101 | if (onViewMore) { 102 | onViewMore(e); 103 | } 104 | }} 105 | > 106 | {viewMoreText} 107 |
108 | ) : null} 109 |
110 | 111 | ); 112 | }; 113 | 114 | export default NoticeList; 115 | -------------------------------------------------------------------------------- /ui/src/components/NoticeIcon/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .popover { 4 | position: relative; 5 | width: 336px; 6 | } 7 | 8 | .noticeButton { 9 | display: inline-block; 10 | cursor: pointer; 11 | transition: all 0.3s; 12 | } 13 | .icon { 14 | padding: 4px; 15 | vertical-align: middle; 16 | } 17 | 18 | .badge { 19 | font-size: 16px; 20 | } 21 | 22 | .tabs { 23 | :global { 24 | .ant-tabs-nav-scroll { 25 | text-align: center; 26 | } 27 | .ant-tabs-bar { 28 | margin-bottom: 0; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ui/src/components/PageLoading/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Spin } from 'antd'; 3 | 4 | // loading components from code split 5 | // https://umijs.org/plugin/umi-plugin-react.html#dynamicimport 6 | const PageLoading: React.FC = () => ( 7 |
8 | 9 |
10 | ); 11 | export default PageLoading; 12 | -------------------------------------------------------------------------------- /ui/src/components/SelectLang/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .menu { 4 | :global(.anticon) { 5 | margin-right: 8px; 6 | } 7 | :global(.ant-dropdown-menu-item) { 8 | min-width: 160px; 9 | } 10 | } 11 | 12 | .dropDown { 13 | line-height: @layout-header-height; 14 | vertical-align: top; 15 | cursor: pointer; 16 | > i { 17 | font-size: 16px !important; 18 | transform: none !important; 19 | svg { 20 | position: relative; 21 | top: -1px; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ui/src/components/SelectLang/index.tsx: -------------------------------------------------------------------------------- 1 | import { Icon, Menu } from 'antd'; 2 | import { formatMessage, getLocale, setLocale } from 'umi-plugin-react/locale'; 3 | 4 | import { ClickParam } from 'antd/es/menu'; 5 | import React from 'react'; 6 | import classNames from 'classnames'; 7 | import HeaderDropdown from '../HeaderDropdown'; 8 | import styles from './index.less'; 9 | 10 | interface SelectLangProps { 11 | className?: string; 12 | } 13 | const SelectLang: React.FC = props => { 14 | const { className } = props; 15 | const selectedLang = getLocale(); 16 | const changeLang = ({ key }: ClickParam): void => setLocale(key, false); 17 | const locales = ['zh-CN', 'zh-TW', 'en-US', 'pt-BR']; 18 | const languageLabels = { 19 | 'zh-CN': '简体中文', 20 | 'zh-TW': '繁体中文', 21 | 'en-US': 'English', 22 | 'pt-BR': 'Português', 23 | }; 24 | const languageIcons = { 25 | 'zh-CN': '🇨🇳', 26 | 'zh-TW': '🇭🇰', 27 | 'en-US': '🇬🇧', 28 | 'pt-BR': '🇧🇷', 29 | }; 30 | const langMenu = ( 31 | 32 | {locales.map(locale => ( 33 | 34 | 35 | {languageIcons[locale]} 36 | {' '} 37 | {languageLabels[locale]} 38 | 39 | ))} 40 | 41 | ); 42 | return ( 43 | 44 | 45 | 46 | 47 | 48 | ); 49 | }; 50 | 51 | export default SelectLang; 52 | -------------------------------------------------------------------------------- /ui/src/components/SettingDrawer/themeColorClient.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line eslint-comments/disable-enable-pair 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | import client from 'webpack-theme-color-replacer/client'; 4 | import generate from '@ant-design/colors/lib/generate'; 5 | 6 | export default { 7 | getAntdSerials(color: string): string[] { 8 | const lightCount = 9; 9 | const divide = 10; 10 | // 淡化(即less的tint) 11 | let lightens = new Array(lightCount).fill(0); 12 | lightens = lightens.map((_, i) => client.varyColor.lighten(color, i / divide)); 13 | const colorPalettes = generate(color); 14 | return lightens.concat(colorPalettes); 15 | }, 16 | changeColor(color?: string): Promise { 17 | if (!color) { 18 | return Promise.resolve(); 19 | } 20 | const options = { 21 | // new colors array, one-to-one corresponde with `matchColors` 22 | newColors: this.getAntdSerials(color), 23 | changeUrl(cssUrl: string): string { 24 | // while router is not `hash` mode, it needs absolute path 25 | return `/${cssUrl}`; 26 | }, 27 | }; 28 | return client.changer.changeColor(options, Promise); 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /ui/src/e2e/__mocks__/antd-pro-merge-less.js: -------------------------------------------------------------------------------- 1 | export default undefined; 2 | -------------------------------------------------------------------------------- /ui/src/e2e/baseLayout.e2e.js: -------------------------------------------------------------------------------- 1 | const RouterConfig = require('../../config/config').default.routes; 2 | const { uniq } = require('lodash'); 3 | 4 | const BASE_URL = `http://localhost:${process.env.PORT || 8000}`; 5 | 6 | function formatter(routes, parentPath = '') { 7 | const fixedParentPath = parentPath.replace(/\/{1,}/g, '/'); 8 | let result = []; 9 | routes.forEach(item => { 10 | if (item.path) { 11 | result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/')); 12 | } 13 | if (item.routes) { 14 | result = result.concat( 15 | formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath), 16 | ); 17 | } 18 | }); 19 | return uniq(result.filter(item => !!item)); 20 | } 21 | 22 | describe('Ant Design Pro E2E test', () => { 23 | const testPage = path => async () => { 24 | await page.goto(`${BASE_URL}${path}`); 25 | await page.waitForSelector('footer', { 26 | timeout: 2000, 27 | }); 28 | const haveFooter = await page.evaluate( 29 | () => document.getElementsByTagName('footer').length > 0, 30 | ); 31 | expect(haveFooter).toBeTruthy(); 32 | }; 33 | 34 | const routers = formatter(RouterConfig); 35 | console.log('routers', routers); 36 | routers.forEach(route => { 37 | it(`test pages ${route}`, testPage(route)); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /ui/src/e2e/topMenu.e2e.js: -------------------------------------------------------------------------------- 1 | const BASE_URL = `http://localhost:${process.env.PORT || 8000}`; 2 | 3 | describe('Homepage', () => { 4 | it('topmenu should have footer', async () => { 5 | const params = '/form/basic-form?navTheme=light&layout=topmenu'; 6 | await page.goto(`${BASE_URL}${params}`); 7 | await page.waitForSelector('footer', { 8 | timeout: 2000, 9 | }); 10 | const haveFooter = await page.evaluate( 11 | () => document.getElementsByTagName('footer').length > 0, 12 | ); 13 | expect(haveFooter).toBeTruthy(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /ui/src/global.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | html, 4 | body, 5 | #root { 6 | height: 100%; 7 | } 8 | 9 | .colorWeak { 10 | filter: invert(80%); 11 | } 12 | 13 | .ant-layout { 14 | min-height: 100vh; 15 | } 16 | 17 | canvas { 18 | display: block; 19 | } 20 | 21 | body { 22 | text-rendering: optimizeLegibility; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | ul, 28 | ol { 29 | list-style: none; 30 | } 31 | 32 | @media (max-width: @screen-xs) { 33 | .ant-table { 34 | width: 100%; 35 | overflow-x: auto; 36 | &-thead > tr, 37 | &-tbody > tr { 38 | > th, 39 | > td { 40 | white-space: pre; 41 | > span { 42 | display: block; 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ui/src/global.tsx: -------------------------------------------------------------------------------- 1 | import { Button, message, notification } from 'antd'; 2 | 3 | import React from 'react'; 4 | import { formatMessage } from 'umi-plugin-react/locale'; 5 | import defaultSettings from '../config/defaultSettings'; 6 | 7 | const { pwa } = defaultSettings; 8 | // if pwa is true 9 | if (pwa) { 10 | // Notify user if offline now 11 | window.addEventListener('sw.offline', () => { 12 | message.warning(formatMessage({ id: 'app.pwa.offline' })); 13 | }); 14 | 15 | // Pop up a prompt on the page asking the user if they want to use the latest version 16 | window.addEventListener('sw.updated', (event: Event) => { 17 | const e = event as CustomEvent; 18 | const reloadSW = async () => { 19 | // Check if there is sw whose state is waiting in ServiceWorkerRegistration 20 | // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration 21 | const worker = e.detail && e.detail.waiting; 22 | if (!worker) { 23 | return true; 24 | } 25 | // Send skip-waiting event to waiting SW with MessageChannel 26 | await new Promise((resolve, reject) => { 27 | const channel = new MessageChannel(); 28 | channel.port1.onmessage = msgEvent => { 29 | if (msgEvent.data.error) { 30 | reject(msgEvent.data.error); 31 | } else { 32 | resolve(msgEvent.data); 33 | } 34 | }; 35 | worker.postMessage({ type: 'skip-waiting' }, [channel.port2]); 36 | }); 37 | // Refresh current page to use the updated HTML and other assets after SW has skiped waiting 38 | window.location.reload(true); 39 | return true; 40 | }; 41 | const key = `open${Date.now()}`; 42 | const btn = ( 43 | 52 | ); 53 | notification.open({ 54 | message: formatMessage({ id: 'app.pwa.serviceworker.updated' }), 55 | description: formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }), 56 | btn, 57 | key, 58 | onClose: async () => {}, 59 | }); 60 | }); 61 | } else if ('serviceWorker' in navigator) { 62 | // eslint-disable-next-line compat/compat 63 | navigator.serviceWorker.ready 64 | .then(registration => { 65 | registration.unregister(); 66 | return true; 67 | }) 68 | .catch(() => { 69 | console.log('serviceWorker unregister error'); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /ui/src/layouts/BlankLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Layout: React.FC = ({ children }) =>
{children}
; 4 | 5 | export default Layout; 6 | -------------------------------------------------------------------------------- /ui/src/layouts/UserLayout.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .container { 4 | display: flex; 5 | flex-direction: column; 6 | height: 100vh; 7 | overflow: auto; 8 | background: @layout-body-background; 9 | } 10 | 11 | .lang { 12 | width: 100%; 13 | height: 40px; 14 | line-height: 44px; 15 | text-align: right; 16 | :global(.ant-dropdown-trigger) { 17 | margin-right: 24px; 18 | } 19 | } 20 | 21 | .content { 22 | flex: 1; 23 | padding: 32px 0; 24 | } 25 | 26 | @media (min-width: @screen-md-min) { 27 | .container { 28 | background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg'); 29 | background-repeat: no-repeat; 30 | background-position: center 110px; 31 | background-size: 100%; 32 | } 33 | 34 | .content { 35 | padding: 32px 0 24px; 36 | } 37 | } 38 | 39 | .top { 40 | text-align: center; 41 | } 42 | 43 | .header { 44 | height: 44px; 45 | line-height: 44px; 46 | a { 47 | text-decoration: none; 48 | } 49 | } 50 | 51 | .logo { 52 | height: 44px; 53 | margin-right: 16px; 54 | vertical-align: top; 55 | } 56 | 57 | .title { 58 | position: relative; 59 | top: 2px; 60 | color: @heading-color; 61 | font-weight: 600; 62 | font-size: 33px; 63 | font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif; 64 | } 65 | 66 | .desc { 67 | margin-top: 12px; 68 | margin-bottom: 40px; 69 | color: @text-color-secondary; 70 | font-size: @font-size-base; 71 | } 72 | -------------------------------------------------------------------------------- /ui/src/layouts/UserLayout.tsx: -------------------------------------------------------------------------------- 1 | import { DefaultFooter, MenuDataItem, getMenuData, getPageTitle } from '@ant-design/pro-layout'; 2 | import DocumentTitle from 'react-document-title'; 3 | import Link from 'umi/link'; 4 | import React from 'react'; 5 | import { connect } from 'dva'; 6 | import { formatMessage } from 'umi-plugin-react/locale'; 7 | 8 | import SelectLang from '@/components/SelectLang'; 9 | import { ConnectProps, ConnectState } from '@/models/connect'; 10 | import logo from '../assets/logo.svg'; 11 | import styles from './UserLayout.less'; 12 | 13 | export interface UserLayoutProps extends ConnectProps { 14 | breadcrumbNameMap: { [path: string]: MenuDataItem }; 15 | } 16 | 17 | const UserLayout: React.SFC = props => { 18 | const { 19 | route = { 20 | routes: [], 21 | }, 22 | } = props; 23 | const { routes = [] } = route; 24 | const { 25 | children, 26 | location = { 27 | pathname: '', 28 | }, 29 | } = props; 30 | const { breadcrumb } = getMenuData(routes); 31 | 32 | return ( 33 | 41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 | 49 | logo 50 | Ant Design 51 | 52 |
53 |
Ant Design 是西湖区最具影响力的 Web 设计规范
54 |
55 | {children} 56 |
57 | 58 |
59 |
60 | ); 61 | }; 62 | 63 | export default connect(({ settings }: ConnectState) => ({ 64 | ...settings, 65 | }))(UserLayout); 66 | -------------------------------------------------------------------------------- /ui/src/locales/en-US.ts: -------------------------------------------------------------------------------- 1 | import component from './en-US/component'; 2 | import globalHeader from './en-US/globalHeader'; 3 | import menu from './en-US/menu'; 4 | import pwa from './en-US/pwa'; 5 | import settingDrawer from './en-US/settingDrawer'; 6 | import settings from './en-US/settings'; 7 | 8 | export default { 9 | 'navBar.lang': 'Languages', 10 | 'layout.user.link.help': 'Help', 11 | 'layout.user.link.privacy': 'Privacy', 12 | 'layout.user.link.terms': 'Terms', 13 | 'app.preview.down.block': 'Download this page to your local project', 14 | ...globalHeader, 15 | ...menu, 16 | ...settingDrawer, 17 | ...settings, 18 | ...pwa, 19 | ...component, 20 | }; 21 | -------------------------------------------------------------------------------- /ui/src/locales/en-US/component.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': 'Expand', 3 | 'component.tagSelect.collapse': 'Collapse', 4 | 'component.tagSelect.all': 'All', 5 | }; 6 | -------------------------------------------------------------------------------- /ui/src/locales/en-US/globalHeader.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.globalHeader.search': 'Search', 3 | 'component.globalHeader.search.example1': 'Search example 1', 4 | 'component.globalHeader.search.example2': 'Search example 2', 5 | 'component.globalHeader.search.example3': 'Search example 3', 6 | 'component.globalHeader.help': 'Help', 7 | 'component.globalHeader.notification': 'Notification', 8 | 'component.globalHeader.notification.empty': 'You have viewed all notifications.', 9 | 'component.globalHeader.message': 'Message', 10 | 'component.globalHeader.message.empty': 'You have viewed all messsages.', 11 | 'component.globalHeader.event': 'Event', 12 | 'component.globalHeader.event.empty': 'You have viewed all events.', 13 | 'component.noticeIcon.clear': 'Clear', 14 | 'component.noticeIcon.cleared': 'Cleared', 15 | 'component.noticeIcon.empty': 'No notifications', 16 | 'component.noticeIcon.view-more': 'View more', 17 | }; 18 | -------------------------------------------------------------------------------- /ui/src/locales/en-US/menu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.welcome': 'Welcome', 3 | 'menu.more-blocks': 'More Blocks', 4 | 'menu.home': 'Home', 5 | 'menu.login': 'Login', 6 | 'menu.register': 'Register', 7 | 'menu.register.result': 'Register Result', 8 | 'menu.dashboard': 'Dashboard', 9 | 'menu.dashboard.analysis': 'Analysis', 10 | 'menu.dashboard.monitor': 'Monitor', 11 | 'menu.dashboard.workplace': 'Workplace', 12 | 'menu.exception.403': '403', 13 | 'menu.exception.404': '404', 14 | 'menu.exception.500': '500', 15 | 'menu.form': 'Form', 16 | 'menu.form.basic-form': 'Basic Form', 17 | 'menu.form.step-form': 'Step Form', 18 | 'menu.form.step-form.info': 'Step Form(write transfer information)', 19 | 'menu.form.step-form.confirm': 'Step Form(confirm transfer information)', 20 | 'menu.form.step-form.result': 'Step Form(finished)', 21 | 'menu.form.advanced-form': 'Advanced Form', 22 | 'menu.list': 'List', 23 | 'menu.list.table-list': 'Search Table', 24 | 'menu.list.basic-list': 'Basic List', 25 | 'menu.list.card-list': 'Card List', 26 | 'menu.list.search-list': 'Search List', 27 | 'menu.list.search-list.articles': 'Search List(articles)', 28 | 'menu.list.search-list.projects': 'Search List(projects)', 29 | 'menu.list.search-list.applications': 'Search List(applications)', 30 | 'menu.profile': 'Profile', 31 | 'menu.profile.basic': 'Basic Profile', 32 | 'menu.profile.advanced': 'Advanced Profile', 33 | 'menu.result': 'Result', 34 | 'menu.result.success': 'Success', 35 | 'menu.result.fail': 'Fail', 36 | 'menu.exception': 'Exception', 37 | 'menu.exception.not-permission': '403', 38 | 'menu.exception.not-find': '404', 39 | 'menu.exception.server-error': '500', 40 | 'menu.exception.trigger': 'Trigger', 41 | 'menu.account': 'Account', 42 | 'menu.account.center': 'Account Center', 43 | 'menu.account.settings': 'Account Settings', 44 | 'menu.account.trigger': 'Trigger Error', 45 | 'menu.account.logout': 'Logout', 46 | 'menu.editor': 'Graphic Editor', 47 | 'menu.editor.flow': 'Flow Editor', 48 | 'menu.editor.mind': 'Mind Editor', 49 | 'menu.editor.koni': 'Koni Editor', 50 | }; 51 | -------------------------------------------------------------------------------- /ui/src/locales/en-US/pwa.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.pwa.offline': 'You are offline now', 3 | 'app.pwa.serviceworker.updated': 'New content is available', 4 | 'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page', 5 | 'app.pwa.serviceworker.updated.ok': 'Refresh', 6 | }; 7 | -------------------------------------------------------------------------------- /ui/src/locales/en-US/settingDrawer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': 'Page style setting', 3 | 'app.setting.pagestyle.dark': 'Dark style', 4 | 'app.setting.pagestyle.light': 'Light style', 5 | 'app.setting.content-width': 'Content Width', 6 | 'app.setting.content-width.fixed': 'Fixed', 7 | 'app.setting.content-width.fluid': 'Fluid', 8 | 'app.setting.themecolor': 'Theme Color', 9 | 'app.setting.themecolor.dust': 'Dust Red', 10 | 'app.setting.themecolor.volcano': 'Volcano', 11 | 'app.setting.themecolor.sunset': 'Sunset Orange', 12 | 'app.setting.themecolor.cyan': 'Cyan', 13 | 'app.setting.themecolor.green': 'Polar Green', 14 | 'app.setting.themecolor.daybreak': 'Daybreak Blue (default)', 15 | 'app.setting.themecolor.geekblue': 'Geek Glue', 16 | 'app.setting.themecolor.purple': 'Golden Purple', 17 | 'app.setting.navigationmode': 'Navigation Mode', 18 | 'app.setting.sidemenu': 'Side Menu Layout', 19 | 'app.setting.topmenu': 'Top Menu Layout', 20 | 'app.setting.fixedheader': 'Fixed Header', 21 | 'app.setting.fixedsidebar': 'Fixed Sidebar', 22 | 'app.setting.fixedsidebar.hint': 'Works on Side Menu Layout', 23 | 'app.setting.hideheader': 'Hidden Header when scrolling', 24 | 'app.setting.hideheader.hint': 'Works when Hidden Header is enabled', 25 | 'app.setting.othersettings': 'Other Settings', 26 | 'app.setting.weakmode': 'Weak Mode', 27 | 'app.setting.copy': 'Copy Setting', 28 | 'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/models/setting.js', 29 | 'app.setting.production.hint': 30 | 'Setting panel shows in development environment only, please manually modify', 31 | }; 32 | -------------------------------------------------------------------------------- /ui/src/locales/en-US/settings.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.settings.menuMap.basic': 'Basic Settings', 3 | 'app.settings.menuMap.security': 'Security Settings', 4 | 'app.settings.menuMap.binding': 'Account Binding', 5 | 'app.settings.menuMap.notification': 'New Message Notification', 6 | 'app.settings.basic.avatar': 'Avatar', 7 | 'app.settings.basic.change-avatar': 'Change avatar', 8 | 'app.settings.basic.email': 'Email', 9 | 'app.settings.basic.email-message': 'Please input your email!', 10 | 'app.settings.basic.nickname': 'Nickname', 11 | 'app.settings.basic.nickname-message': 'Please input your Nickname!', 12 | 'app.settings.basic.profile': 'Personal profile', 13 | 'app.settings.basic.profile-message': 'Please input your personal profile!', 14 | 'app.settings.basic.profile-placeholder': 'Brief introduction to yourself', 15 | 'app.settings.basic.country': 'Country/Region', 16 | 'app.settings.basic.country-message': 'Please input your country!', 17 | 'app.settings.basic.geographic': 'Province or city', 18 | 'app.settings.basic.geographic-message': 'Please input your geographic info!', 19 | 'app.settings.basic.address': 'Street Address', 20 | 'app.settings.basic.address-message': 'Please input your address!', 21 | 'app.settings.basic.phone': 'Phone Number', 22 | 'app.settings.basic.phone-message': 'Please input your phone!', 23 | 'app.settings.basic.update': 'Update Information', 24 | 'app.settings.security.strong': 'Strong', 25 | 'app.settings.security.medium': 'Medium', 26 | 'app.settings.security.weak': 'Weak', 27 | 'app.settings.security.password': 'Account Password', 28 | 'app.settings.security.password-description': 'Current password strength', 29 | 'app.settings.security.phone': 'Security Phone', 30 | 'app.settings.security.phone-description': 'Bound phone', 31 | 'app.settings.security.question': 'Security Question', 32 | 'app.settings.security.question-description': 33 | 'The security question is not set, and the security policy can effectively protect the account security', 34 | 'app.settings.security.email': 'Backup Email', 35 | 'app.settings.security.email-description': 'Bound Email', 36 | 'app.settings.security.mfa': 'MFA Device', 37 | 'app.settings.security.mfa-description': 38 | 'Unbound MFA device, after binding, can be confirmed twice', 39 | 'app.settings.security.modify': 'Modify', 40 | 'app.settings.security.set': 'Set', 41 | 'app.settings.security.bind': 'Bind', 42 | 'app.settings.binding.taobao': 'Binding Taobao', 43 | 'app.settings.binding.taobao-description': 'Currently unbound Taobao account', 44 | 'app.settings.binding.alipay': 'Binding Alipay', 45 | 'app.settings.binding.alipay-description': 'Currently unbound Alipay account', 46 | 'app.settings.binding.dingding': 'Binding DingTalk', 47 | 'app.settings.binding.dingding-description': 'Currently unbound DingTalk account', 48 | 'app.settings.binding.bind': 'Bind', 49 | 'app.settings.notification.password': 'Account Password', 50 | 'app.settings.notification.password-description': 51 | 'Messages from other users will be notified in the form of a station letter', 52 | 'app.settings.notification.messages': 'System Messages', 53 | 'app.settings.notification.messages-description': 54 | 'System messages will be notified in the form of a station letter', 55 | 'app.settings.notification.todo': 'To-do Notification', 56 | 'app.settings.notification.todo-description': 57 | 'The to-do list will be notified in the form of a letter from the station', 58 | 'app.settings.open': 'Open', 59 | 'app.settings.close': 'Close', 60 | }; 61 | -------------------------------------------------------------------------------- /ui/src/locales/pt-BR.ts: -------------------------------------------------------------------------------- 1 | import component from './pt-BR/component'; 2 | import globalHeader from './pt-BR/globalHeader'; 3 | import menu from './pt-BR/menu'; 4 | import pwa from './pt-BR/pwa'; 5 | import settingDrawer from './pt-BR/settingDrawer'; 6 | import settings from './pt-BR/settings'; 7 | 8 | export default { 9 | 'navBar.lang': 'Idiomas', 10 | 'layout.user.link.help': 'ajuda', 11 | 'layout.user.link.privacy': 'política de privacidade', 12 | 'layout.user.link.terms': 'termos de serviços', 13 | 'app.preview.down.block': 'Download this page to your local project', 14 | ...globalHeader, 15 | ...menu, 16 | ...settingDrawer, 17 | ...settings, 18 | ...pwa, 19 | ...component, 20 | }; 21 | -------------------------------------------------------------------------------- /ui/src/locales/pt-BR/component.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': 'Expandir', 3 | 'component.tagSelect.collapse': 'Diminuir', 4 | 'component.tagSelect.all': 'Todas', 5 | }; 6 | -------------------------------------------------------------------------------- /ui/src/locales/pt-BR/globalHeader.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.globalHeader.search': 'Busca', 3 | 'component.globalHeader.search.example1': 'Exemplo de busca 1', 4 | 'component.globalHeader.search.example2': 'Exemplo de busca 2', 5 | 'component.globalHeader.search.example3': 'Exemplo de busca 3', 6 | 'component.globalHeader.help': 'Ajuda', 7 | 'component.globalHeader.notification': 'Notificação', 8 | 'component.globalHeader.notification.empty': 'Você visualizou todas as notificações.', 9 | 'component.globalHeader.message': 'Mensagem', 10 | 'component.globalHeader.message.empty': 'Você visualizou todas as mensagens.', 11 | 'component.globalHeader.event': 'Evento', 12 | 'component.globalHeader.event.empty': 'Você visualizou todos os eventos.', 13 | 'component.noticeIcon.clear': 'Limpar', 14 | 'component.noticeIcon.cleared': 'Limpo', 15 | 'component.noticeIcon.empty': 'Sem notificações', 16 | 'component.noticeIcon.loaded': 'Carregado', 17 | 'component.noticeIcon.view-more': 'Veja mais', 18 | }; 19 | -------------------------------------------------------------------------------- /ui/src/locales/pt-BR/menu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.welcome': 'Welcome', 3 | 'menu.more-blocks': 'More Blocks', 4 | 5 | 'menu.home': 'Início', 6 | 'menu.login': 'Login', 7 | 'menu.register': 'Registro', 8 | 'menu.register.result': 'Resultado de registro', 9 | 'menu.dashboard': 'Dashboard', 10 | 'menu.dashboard.analysis': 'Análise', 11 | 'menu.dashboard.monitor': 'Monitor', 12 | 'menu.dashboard.workplace': 'Ambiente de Trabalho', 13 | 'menu.exception.403': '403', 14 | 'menu.exception.404': '404', 15 | 'menu.exception.500': '500', 16 | 'menu.form': 'Formulário', 17 | 'menu.form.basic-form': 'Formulário Básico', 18 | 'menu.form.step-form': 'Formulário Assistido', 19 | 'menu.form.step-form.info': 'Formulário Assistido(gravar informações de transferência)', 20 | 'menu.form.step-form.confirm': 'Formulário Assistido(confirmar informações de transferência)', 21 | 'menu.form.step-form.result': 'Formulário Assistido(finalizado)', 22 | 'menu.form.advanced-form': 'Formulário Avançado', 23 | 'menu.list': 'Lista', 24 | 'menu.list.table-list': 'Tabela de Busca', 25 | 'menu.list.basic-list': 'Lista Básica', 26 | 'menu.list.card-list': 'Lista de Card', 27 | 'menu.list.search-list': 'Lista de Busca', 28 | 'menu.list.search-list.articles': 'Lista de Busca(artigos)', 29 | 'menu.list.search-list.projects': 'Lista de Busca(projetos)', 30 | 'menu.list.search-list.applications': 'Lista de Busca(aplicações)', 31 | 'menu.profile': 'Perfil', 32 | 'menu.profile.basic': 'Perfil Básico', 33 | 'menu.profile.advanced': 'Perfil Avançado', 34 | 'menu.result': 'Resultado', 35 | 'menu.result.success': 'Sucesso', 36 | 'menu.result.fail': 'Falha', 37 | 'menu.exception': 'Exceção', 38 | 'menu.exception.not-permission': '403', 39 | 'menu.exception.not-find': '404', 40 | 'menu.exception.server-error': '500', 41 | 'menu.exception.trigger': 'Disparar', 42 | 'menu.account': 'Conta', 43 | 'menu.account.center': 'Central da Conta', 44 | 'menu.account.settings': 'Configurar Conta', 45 | 'menu.account.trigger': 'Disparar Erro', 46 | 'menu.account.logout': 'Sair', 47 | 'menu.editor': 'Graphic Editor', 48 | 'menu.editor.flow': 'Flow Editor', 49 | 'menu.editor.mind': 'Mind Editor', 50 | 'menu.editor.koni': 'Koni Editor', 51 | }; 52 | -------------------------------------------------------------------------------- /ui/src/locales/pt-BR/pwa.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.pwa.offline': 'Você está offline agora', 3 | 'app.pwa.serviceworker.updated': 'Novo conteúdo está disponível', 4 | 'app.pwa.serviceworker.updated.hint': 5 | 'Por favor, pressione o botão "Atualizar" para recarregar a página atual', 6 | 'app.pwa.serviceworker.updated.ok': 'Atualizar', 7 | }; 8 | -------------------------------------------------------------------------------- /ui/src/locales/pt-BR/settingDrawer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': 'Configuração de estilo da página', 3 | 'app.setting.pagestyle.dark': 'Dark style', 4 | 'app.setting.pagestyle.light': 'Light style', 5 | 'app.setting.content-width': 'Largura do conteúdo', 6 | 'app.setting.content-width.fixed': 'Fixo', 7 | 'app.setting.content-width.fluid': 'Fluido', 8 | 'app.setting.themecolor': 'Cor do Tema', 9 | 'app.setting.themecolor.dust': 'Dust Red', 10 | 'app.setting.themecolor.volcano': 'Volcano', 11 | 'app.setting.themecolor.sunset': 'Sunset Orange', 12 | 'app.setting.themecolor.cyan': 'Cyan', 13 | 'app.setting.themecolor.green': 'Polar Green', 14 | 'app.setting.themecolor.daybreak': 'Daybreak Blue (default)', 15 | 'app.setting.themecolor.geekblue': 'Geek Glue', 16 | 'app.setting.themecolor.purple': 'Golden Purple', 17 | 'app.setting.navigationmode': 'Modo de Navegação', 18 | 'app.setting.sidemenu': 'Layout do Menu Lateral', 19 | 'app.setting.topmenu': 'Layout do Menu Superior', 20 | 'app.setting.fixedheader': 'Cabeçalho fixo', 21 | 'app.setting.fixedsidebar': 'Barra lateral fixa', 22 | 'app.setting.fixedsidebar.hint': 'Funciona no layout do menu lateral', 23 | 'app.setting.hideheader': 'Esconder o cabeçalho quando rolar', 24 | 'app.setting.hideheader.hint': 'Funciona quando o esconder cabeçalho está abilitado', 25 | 'app.setting.othersettings': 'Outras configurações', 26 | 'app.setting.weakmode': 'Weak Mode', 27 | 'app.setting.copy': 'Copiar Configuração', 28 | 'app.setting.copyinfo': 29 | 'copiado com sucesso,por favor trocar o defaultSettings em src/models/setting.js', 30 | 'app.setting.production.hint': 31 | 'O painel de configuração apenas é exibido no ambiente de desenvolvimento, por favor modifique manualmente o', 32 | }; 33 | -------------------------------------------------------------------------------- /ui/src/locales/pt-BR/settings.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.settings.menuMap.basic': 'Configurações Básicas', 3 | 'app.settings.menuMap.security': 'Configurações de Segurança', 4 | 'app.settings.menuMap.binding': 'Vinculação de Conta', 5 | 'app.settings.menuMap.notification': 'Mensagens de Notificação', 6 | 'app.settings.basic.avatar': 'Avatar', 7 | 'app.settings.basic.change-avatar': 'Alterar avatar', 8 | 'app.settings.basic.email': 'Email', 9 | 'app.settings.basic.email-message': 'Por favor insira seu email!', 10 | 'app.settings.basic.nickname': 'Nome de usuário', 11 | 'app.settings.basic.nickname-message': 'Por favor insira seu nome de usuário!', 12 | 'app.settings.basic.profile': 'Perfil pessoal', 13 | 'app.settings.basic.profile-message': 'Por favor insira seu perfil pessoal!', 14 | 'app.settings.basic.profile-placeholder': 'Breve introdução sua', 15 | 'app.settings.basic.country': 'País/Região', 16 | 'app.settings.basic.country-message': 'Por favor insira país!', 17 | 'app.settings.basic.geographic': 'Província, estado ou cidade', 18 | 'app.settings.basic.geographic-message': 'Por favor insira suas informações geográficas!', 19 | 'app.settings.basic.address': 'Endereço', 20 | 'app.settings.basic.address-message': 'Por favor insira seu endereço!', 21 | 'app.settings.basic.phone': 'Número de telefone', 22 | 'app.settings.basic.phone-message': 'Por favor insira seu número de telefone!', 23 | 'app.settings.basic.update': 'Atualizar Informações', 24 | 'app.settings.security.strong': 'Forte', 25 | 'app.settings.security.medium': 'Média', 26 | 'app.settings.security.weak': 'Fraca', 27 | 'app.settings.security.password': 'Senha da Conta', 28 | 'app.settings.security.password-description': 'Força da senha', 29 | 'app.settings.security.phone': 'Telefone de Seguraça', 30 | 'app.settings.security.phone-description': 'Telefone vinculado', 31 | 'app.settings.security.question': 'Pergunta de Segurança', 32 | 'app.settings.security.question-description': 33 | 'A pergunta de segurança não está definida e a política de segurança pode proteger efetivamente a segurança da conta', 34 | 'app.settings.security.email': 'Email de Backup', 35 | 'app.settings.security.email-description': 'Email vinculado', 36 | 'app.settings.security.mfa': 'Dispositivo MFA', 37 | 'app.settings.security.mfa-description': 38 | 'O dispositivo MFA não vinculado, após a vinculação, pode ser confirmado duas vezes', 39 | 'app.settings.security.modify': 'Modificar', 40 | 'app.settings.security.set': 'Atribuir', 41 | 'app.settings.security.bind': 'Vincular', 42 | 'app.settings.binding.taobao': 'Vincular Taobao', 43 | 'app.settings.binding.taobao-description': 'Atualmente não vinculado à conta Taobao', 44 | 'app.settings.binding.alipay': 'Vincular Alipay', 45 | 'app.settings.binding.alipay-description': 'Atualmente não vinculado à conta Alipay', 46 | 'app.settings.binding.dingding': 'Vincular DingTalk', 47 | 'app.settings.binding.dingding-description': 'Atualmente não vinculado à conta DingTalk', 48 | 'app.settings.binding.bind': 'Vincular', 49 | 'app.settings.notification.password': 'Senha da Conta', 50 | 'app.settings.notification.password-description': 51 | 'Mensagens de outros usuários serão notificadas na forma de uma estação de letra', 52 | 'app.settings.notification.messages': 'Mensagens de Sistema', 53 | 'app.settings.notification.messages-description': 54 | 'Mensagens de sistema serão notificadas na forma de uma estação de letra', 55 | 'app.settings.notification.todo': 'Notificação de To-do', 56 | 'app.settings.notification.todo-description': 57 | 'A lista de to-do será notificada na forma de uma estação de letra', 58 | 'app.settings.open': 'Aberto', 59 | 'app.settings.close': 'Fechado', 60 | }; 61 | -------------------------------------------------------------------------------- /ui/src/locales/zh-CN.ts: -------------------------------------------------------------------------------- 1 | import component from './zh-CN/component'; 2 | import globalHeader from './zh-CN/globalHeader'; 3 | import menu from './zh-CN/menu'; 4 | import pwa from './zh-CN/pwa'; 5 | import settingDrawer from './zh-CN/settingDrawer'; 6 | import settings from './zh-CN/settings'; 7 | 8 | export default { 9 | 'navBar.lang': '语言', 10 | 'layout.user.link.help': '帮助', 11 | 'layout.user.link.privacy': '隐私', 12 | 'layout.user.link.terms': '条款', 13 | 'app.preview.down.block': '下载此页面到本地项目', 14 | ...globalHeader, 15 | ...menu, 16 | ...settingDrawer, 17 | ...settings, 18 | ...pwa, 19 | ...component, 20 | }; 21 | -------------------------------------------------------------------------------- /ui/src/locales/zh-CN/component.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': '展开', 3 | 'component.tagSelect.collapse': '收起', 4 | 'component.tagSelect.all': '全部', 5 | }; 6 | -------------------------------------------------------------------------------- /ui/src/locales/zh-CN/globalHeader.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.globalHeader.search': '站内搜索', 3 | 'component.globalHeader.search.example1': '搜索提示一', 4 | 'component.globalHeader.search.example2': '搜索提示二', 5 | 'component.globalHeader.search.example3': '搜索提示三', 6 | 'component.globalHeader.help': '使用文档', 7 | 'component.globalHeader.notification': '通知', 8 | 'component.globalHeader.notification.empty': '你已查看所有通知', 9 | 'component.globalHeader.message': '消息', 10 | 'component.globalHeader.message.empty': '您已读完所有消息', 11 | 'component.globalHeader.event': '待办', 12 | 'component.globalHeader.event.empty': '你已完成所有待办', 13 | 'component.noticeIcon.clear': '清空', 14 | 'component.noticeIcon.cleared': '清空了', 15 | 'component.noticeIcon.empty': '暂无数据', 16 | 'component.noticeIcon.view-more': '查看更多', 17 | }; 18 | -------------------------------------------------------------------------------- /ui/src/locales/zh-CN/menu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.welcome': '欢迎', 3 | 'menu.more-blocks': '更多区块', 4 | 'menu.home': '首页', 5 | 'menu.login': '登录', 6 | 'menu.register': '注册', 7 | 'menu.register.result': '注册结果', 8 | 'menu.dashboard': 'Dashboard', 9 | 'menu.dashboard.analysis': '分析页', 10 | 'menu.dashboard.monitor': '监控页', 11 | 'menu.dashboard.workplace': '工作台', 12 | 'menu.exception.403': '403', 13 | 'menu.exception.404': '404', 14 | 'menu.exception.500': '500', 15 | 'menu.form': '表单页', 16 | 'menu.form.basic-form': '基础表单', 17 | 'menu.form.step-form': '分步表单', 18 | 'menu.form.step-form.info': '分步表单(填写转账信息)', 19 | 'menu.form.step-form.confirm': '分步表单(确认转账信息)', 20 | 'menu.form.step-form.result': '分步表单(完成)', 21 | 'menu.form.advanced-form': '高级表单', 22 | 'menu.list': '列表页', 23 | 'menu.list.table-list': '查询表格', 24 | 'menu.list.basic-list': '标准列表', 25 | 'menu.list.card-list': '卡片列表', 26 | 'menu.list.search-list': '搜索列表', 27 | 'menu.list.search-list.articles': '搜索列表(文章)', 28 | 'menu.list.search-list.projects': '搜索列表(项目)', 29 | 'menu.list.search-list.applications': '搜索列表(应用)', 30 | 'menu.profile': '详情页', 31 | 'menu.profile.basic': '基础详情页', 32 | 'menu.profile.advanced': '高级详情页', 33 | 'menu.result': '结果页', 34 | 'menu.result.success': '成功页', 35 | 'menu.result.fail': '失败页', 36 | 'menu.exception': '异常页', 37 | 'menu.exception.not-permission': '403', 38 | 'menu.exception.not-find': '404', 39 | 'menu.exception.server-error': '500', 40 | 'menu.exception.trigger': '触发错误', 41 | 'menu.account': '个人页', 42 | 'menu.account.center': '个人中心', 43 | 'menu.account.settings': '个人设置', 44 | 'menu.account.trigger': '触发报错', 45 | 'menu.account.logout': '退出登录', 46 | 'menu.editor': '图形编辑器', 47 | 'menu.editor.flow': '流程编辑器', 48 | 'menu.editor.mind': '脑图编辑器', 49 | 'menu.editor.koni': '拓扑编辑器', 50 | }; 51 | -------------------------------------------------------------------------------- /ui/src/locales/zh-CN/pwa.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.pwa.offline': '当前处于离线状态', 3 | 'app.pwa.serviceworker.updated': '有新内容', 4 | 'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面', 5 | 'app.pwa.serviceworker.updated.ok': '刷新', 6 | }; 7 | -------------------------------------------------------------------------------- /ui/src/locales/zh-CN/settingDrawer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': '整体风格设置', 3 | 'app.setting.pagestyle.dark': '暗色菜单风格', 4 | 'app.setting.pagestyle.light': '亮色菜单风格', 5 | 'app.setting.content-width': '内容区域宽度', 6 | 'app.setting.content-width.fixed': '定宽', 7 | 'app.setting.content-width.fluid': '流式', 8 | 'app.setting.themecolor': '主题色', 9 | 'app.setting.themecolor.dust': '薄暮', 10 | 'app.setting.themecolor.volcano': '火山', 11 | 'app.setting.themecolor.sunset': '日暮', 12 | 'app.setting.themecolor.cyan': '明青', 13 | 'app.setting.themecolor.green': '极光绿', 14 | 'app.setting.themecolor.daybreak': '拂晓蓝(默认)', 15 | 'app.setting.themecolor.geekblue': '极客蓝', 16 | 'app.setting.themecolor.purple': '酱紫', 17 | 'app.setting.navigationmode': '导航模式', 18 | 'app.setting.sidemenu': '侧边菜单布局', 19 | 'app.setting.topmenu': '顶部菜单布局', 20 | 'app.setting.fixedheader': '固定 Header', 21 | 'app.setting.fixedsidebar': '固定侧边菜单', 22 | 'app.setting.fixedsidebar.hint': '侧边菜单布局时可配置', 23 | 'app.setting.hideheader': '下滑时隐藏 Header', 24 | 'app.setting.hideheader.hint': '固定 Header 时可配置', 25 | 'app.setting.othersettings': '其他设置', 26 | 'app.setting.weakmode': '色弱模式', 27 | 'app.setting.copy': '拷贝设置', 28 | 'app.setting.copyinfo': '拷贝成功,请到 src/defaultSettings.js 中替换默认配置', 29 | 'app.setting.production.hint': 30 | '配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件', 31 | }; 32 | -------------------------------------------------------------------------------- /ui/src/locales/zh-CN/settings.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.settings.menuMap.basic': '基本设置', 3 | 'app.settings.menuMap.security': '安全设置', 4 | 'app.settings.menuMap.binding': '账号绑定', 5 | 'app.settings.menuMap.notification': '新消息通知', 6 | 'app.settings.basic.avatar': '头像', 7 | 'app.settings.basic.change-avatar': '更换头像', 8 | 'app.settings.basic.email': '邮箱', 9 | 'app.settings.basic.email-message': '请输入您的邮箱!', 10 | 'app.settings.basic.nickname': '昵称', 11 | 'app.settings.basic.nickname-message': '请输入您的昵称!', 12 | 'app.settings.basic.profile': '个人简介', 13 | 'app.settings.basic.profile-message': '请输入个人简介!', 14 | 'app.settings.basic.profile-placeholder': '个人简介', 15 | 'app.settings.basic.country': '国家/地区', 16 | 'app.settings.basic.country-message': '请输入您的国家或地区!', 17 | 'app.settings.basic.geographic': '所在省市', 18 | 'app.settings.basic.geographic-message': '请输入您的所在省市!', 19 | 'app.settings.basic.address': '街道地址', 20 | 'app.settings.basic.address-message': '请输入您的街道地址!', 21 | 'app.settings.basic.phone': '联系电话', 22 | 'app.settings.basic.phone-message': '请输入您的联系电话!', 23 | 'app.settings.basic.update': '更新基本信息', 24 | 'app.settings.security.strong': '强', 25 | 'app.settings.security.medium': '中', 26 | 'app.settings.security.weak': '弱', 27 | 'app.settings.security.password': '账户密码', 28 | 'app.settings.security.password-description': '当前密码强度', 29 | 'app.settings.security.phone': '密保手机', 30 | 'app.settings.security.phone-description': '已绑定手机', 31 | 'app.settings.security.question': '密保问题', 32 | 'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全', 33 | 'app.settings.security.email': '备用邮箱', 34 | 'app.settings.security.email-description': '已绑定邮箱', 35 | 'app.settings.security.mfa': 'MFA 设备', 36 | 'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认', 37 | 'app.settings.security.modify': '修改', 38 | 'app.settings.security.set': '设置', 39 | 'app.settings.security.bind': '绑定', 40 | 'app.settings.binding.taobao': '绑定淘宝', 41 | 'app.settings.binding.taobao-description': '当前未绑定淘宝账号', 42 | 'app.settings.binding.alipay': '绑定支付宝', 43 | 'app.settings.binding.alipay-description': '当前未绑定支付宝账号', 44 | 'app.settings.binding.dingding': '绑定钉钉', 45 | 'app.settings.binding.dingding-description': '当前未绑定钉钉账号', 46 | 'app.settings.binding.bind': '绑定', 47 | 'app.settings.notification.password': '账户密码', 48 | 'app.settings.notification.password-description': '其他用户的消息将以站内信的形式通知', 49 | 'app.settings.notification.messages': '系统消息', 50 | 'app.settings.notification.messages-description': '系统消息将以站内信的形式通知', 51 | 'app.settings.notification.todo': '待办任务', 52 | 'app.settings.notification.todo-description': '待办任务将以站内信的形式通知', 53 | 'app.settings.open': '开', 54 | 'app.settings.close': '关', 55 | }; 56 | -------------------------------------------------------------------------------- /ui/src/locales/zh-TW.ts: -------------------------------------------------------------------------------- 1 | import component from './zh-TW/component'; 2 | import globalHeader from './zh-TW/globalHeader'; 3 | import menu from './zh-TW/menu'; 4 | import pwa from './zh-TW/pwa'; 5 | import settingDrawer from './zh-TW/settingDrawer'; 6 | import settings from './zh-TW/settings'; 7 | 8 | export default { 9 | 'navBar.lang': '語言', 10 | 'layout.user.link.help': '幫助', 11 | 'layout.user.link.privacy': '隱私', 12 | 'layout.user.link.terms': '條款', 13 | 'app.preview.down.block': '下載此頁面到本地項目', 14 | ...globalHeader, 15 | ...menu, 16 | ...settingDrawer, 17 | ...settings, 18 | ...pwa, 19 | ...component, 20 | }; 21 | -------------------------------------------------------------------------------- /ui/src/locales/zh-TW/component.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': '展開', 3 | 'component.tagSelect.collapse': '收起', 4 | 'component.tagSelect.all': '全部', 5 | }; 6 | -------------------------------------------------------------------------------- /ui/src/locales/zh-TW/globalHeader.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.globalHeader.search': '站內搜索', 3 | 'component.globalHeader.search.example1': '搜索提示壹', 4 | 'component.globalHeader.search.example2': '搜索提示二', 5 | 'component.globalHeader.search.example3': '搜索提示三', 6 | 'component.globalHeader.help': '使用手冊', 7 | 'component.globalHeader.notification': '通知', 8 | 'component.globalHeader.notification.empty': '妳已查看所有通知', 9 | 'component.globalHeader.message': '消息', 10 | 'component.globalHeader.message.empty': '您已讀完所有消息', 11 | 'component.globalHeader.event': '待辦', 12 | 'component.globalHeader.event.empty': '妳已完成所有待辦', 13 | 'component.noticeIcon.clear': '清空', 14 | 'component.noticeIcon.cleared': '清空了', 15 | 'component.noticeIcon.empty': '暫無資料', 16 | 'component.noticeIcon.view-more': '查看更多', 17 | }; 18 | -------------------------------------------------------------------------------- /ui/src/locales/zh-TW/menu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.welcome': '歡迎', 3 | 'menu.more-blocks': '更多區塊', 4 | 5 | 'menu.home': '首頁', 6 | 'menu.login': '登錄', 7 | 'menu.exception.403': '403', 8 | 'menu.exception.404': '404', 9 | 'menu.exception.500': '500', 10 | 'menu.register': '註冊', 11 | 'menu.register.resultt': '註冊結果', 12 | 'menu.dashboard': 'Dashboard', 13 | 'menu.dashboard.analysis': '分析頁', 14 | 'menu.dashboard.monitor': '監控頁', 15 | 'menu.dashboard.workplace': '工作臺', 16 | 'menu.form': '表單頁', 17 | 'menu.form.basic-form': '基礎表單', 18 | 'menu.form.step-form': '分步表單', 19 | 'menu.form.step-form.info': '分步表單(填寫轉賬信息)', 20 | 'menu.form.step-form.confirm': '分步表單(確認轉賬信息)', 21 | 'menu.form.step-form.result': '分步表單(完成)', 22 | 'menu.form.advanced-form': '高級表單', 23 | 'menu.list': '列表頁', 24 | 'menu.list.table-list': '查詢表格', 25 | 'menu.list.basic-list': '標淮列表', 26 | 'menu.list.card-list': '卡片列表', 27 | 'menu.list.search-list': '搜索列表', 28 | 'menu.list.search-list.articles': '搜索列表(文章)', 29 | 'menu.list.search-list.projects': '搜索列表(項目)', 30 | 'menu.list.search-list.applications': '搜索列表(應用)', 31 | 'menu.profile': '詳情頁', 32 | 'menu.profile.basic': '基礎詳情頁', 33 | 'menu.profile.advanced': '高級詳情頁', 34 | 'menu.result': '結果頁', 35 | 'menu.result.success': '成功頁', 36 | 'menu.result.fail': '失敗頁', 37 | 'menu.account': '個人頁', 38 | 'menu.account.center': '個人中心', 39 | 'menu.account.settings': '個人設置', 40 | 'menu.account.trigger': '觸發報錯', 41 | 'menu.account.logout': '退出登錄', 42 | 'menu.exception': '异常页', 43 | 'menu.exception.not-permission': '403', 44 | 'menu.exception.not-find': '404', 45 | 'menu.exception.server-error': '500', 46 | 'menu.exception.trigger': '触发错误', 47 | 'menu.editor': '圖形編輯器', 48 | 'menu.editor.flow': '流程編輯器', 49 | 'menu.editor.mind': '腦圖編輯器', 50 | 'menu.editor.koni': '拓撲編輯器', 51 | }; 52 | -------------------------------------------------------------------------------- /ui/src/locales/zh-TW/pwa.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.pwa.offline': '當前處於離線狀態', 3 | 'app.pwa.serviceworker.updated': '有新內容', 4 | 'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面', 5 | 'app.pwa.serviceworker.updated.ok': '刷新', 6 | }; 7 | -------------------------------------------------------------------------------- /ui/src/locales/zh-TW/settingDrawer.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': '整體風格設置', 3 | 'app.setting.pagestyle.dark': '暗色菜單風格', 4 | 'app.setting.pagestyle.light': '亮色菜單風格', 5 | 'app.setting.content-width': '內容區域寬度', 6 | 'app.setting.content-width.fixed': '定寬', 7 | 'app.setting.content-width.fluid': '流式', 8 | 'app.setting.themecolor': '主題色', 9 | 'app.setting.themecolor.dust': '薄暮', 10 | 'app.setting.themecolor.volcano': '火山', 11 | 'app.setting.themecolor.sunset': '日暮', 12 | 'app.setting.themecolor.cyan': '明青', 13 | 'app.setting.themecolor.green': '極光綠', 14 | 'app.setting.themecolor.daybreak': '拂曉藍(默認)', 15 | 'app.setting.themecolor.geekblue': '極客藍', 16 | 'app.setting.themecolor.purple': '醬紫', 17 | 'app.setting.navigationmode': '導航模式', 18 | 'app.setting.sidemenu': '側邊菜單布局', 19 | 'app.setting.topmenu': '頂部菜單布局', 20 | 'app.setting.fixedheader': '固定 Header', 21 | 'app.setting.fixedsidebar': '固定側邊菜單', 22 | 'app.setting.fixedsidebar.hint': '側邊菜單布局時可配置', 23 | 'app.setting.hideheader': '下滑時隱藏 Header', 24 | 'app.setting.hideheader.hint': '固定 Header 時可配置', 25 | 'app.setting.othersettings': '其他設置', 26 | 'app.setting.weakmode': '色弱模式', 27 | 'app.setting.copy': '拷貝設置', 28 | 'app.setting.copyinfo': '拷貝成功,請到 src/defaultSettings.js 中替換默認配置', 29 | 'app.setting.production.hint': 30 | '配置欄只在開發環境用於預覽,生產環境不會展現,請拷貝後手動修改配置文件', 31 | }; 32 | -------------------------------------------------------------------------------- /ui/src/locales/zh-TW/settings.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.settings.menuMap.basic': '基本設置', 3 | 'app.settings.menuMap.security': '安全設置', 4 | 'app.settings.menuMap.binding': '賬號綁定', 5 | 'app.settings.menuMap.notification': '新消息通知', 6 | 'app.settings.basic.avatar': '頭像', 7 | 'app.settings.basic.change-avatar': '更換頭像', 8 | 'app.settings.basic.email': '郵箱', 9 | 'app.settings.basic.email-message': '請輸入您的郵箱!', 10 | 'app.settings.basic.nickname': '昵稱', 11 | 'app.settings.basic.nickname-message': '請輸入您的昵稱!', 12 | 'app.settings.basic.profile': '個人簡介', 13 | 'app.settings.basic.profile-message': '請輸入個人簡介!', 14 | 'app.settings.basic.profile-placeholder': '個人簡介', 15 | 'app.settings.basic.country': '國家/地區', 16 | 'app.settings.basic.country-message': '請輸入您的國家或地區!', 17 | 'app.settings.basic.geographic': '所在省市', 18 | 'app.settings.basic.geographic-message': '請輸入您的所在省市!', 19 | 'app.settings.basic.address': '街道地址', 20 | 'app.settings.basic.address-message': '請輸入您的街道地址!', 21 | 'app.settings.basic.phone': '聯系電話', 22 | 'app.settings.basic.phone-message': '請輸入您的聯系電話!', 23 | 'app.settings.basic.update': '更新基本信息', 24 | 'app.settings.security.strong': '強', 25 | 'app.settings.security.medium': '中', 26 | 'app.settings.security.weak': '弱', 27 | 'app.settings.security.password': '賬戶密碼', 28 | 'app.settings.security.password-description': '當前密碼強度', 29 | 'app.settings.security.phone': '密保手機', 30 | 'app.settings.security.phone-description': '已綁定手機', 31 | 'app.settings.security.question': '密保問題', 32 | 'app.settings.security.question-description': '未設置密保問題,密保問題可有效保護賬戶安全', 33 | 'app.settings.security.email': '備用郵箱', 34 | 'app.settings.security.email-description': '已綁定郵箱', 35 | 'app.settings.security.mfa': 'MFA 設備', 36 | 'app.settings.security.mfa-description': '未綁定 MFA 設備,綁定後,可以進行二次確認', 37 | 'app.settings.security.modify': '修改', 38 | 'app.settings.security.set': '設置', 39 | 'app.settings.security.bind': '綁定', 40 | 'app.settings.binding.taobao': '綁定淘寶', 41 | 'app.settings.binding.taobao-description': '當前未綁定淘寶賬號', 42 | 'app.settings.binding.alipay': '綁定支付寶', 43 | 'app.settings.binding.alipay-description': '當前未綁定支付寶賬號', 44 | 'app.settings.binding.dingding': '綁定釘釘', 45 | 'app.settings.binding.dingding-description': '當前未綁定釘釘賬號', 46 | 'app.settings.binding.bind': '綁定', 47 | 'app.settings.notification.password': '賬戶密碼', 48 | 'app.settings.notification.password-description': '其他用戶的消息將以站內信的形式通知', 49 | 'app.settings.notification.messages': '系統消息', 50 | 'app.settings.notification.messages-description': '系統消息將以站內信的形式通知', 51 | 'app.settings.notification.todo': '待辦任務', 52 | 'app.settings.notification.todo-description': '待辦任務將以站內信的形式通知', 53 | 'app.settings.open': '開', 54 | 'app.settings.close': '關', 55 | }; 56 | -------------------------------------------------------------------------------- /ui/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ant Design Pro", 3 | "short_name": "Ant Design Pro", 4 | "display": "standalone", 5 | "start_url": "./?utm_source=homescreen", 6 | "theme_color": "#002140", 7 | "background_color": "#001529", 8 | "icons": [ 9 | { 10 | "src": "icons/icon-192x192.png", 11 | "sizes": "192x192" 12 | }, 13 | { 14 | "src": "icons/icon-128x128.png", 15 | "sizes": "128x128" 16 | }, 17 | { 18 | "src": "icons/icon-512x512.png", 19 | "sizes": "512x512" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /ui/src/models/connect.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from 'redux'; 2 | import { EffectsCommandMap } from 'dva'; 3 | import { MenuDataItem } from '@ant-design/pro-layout'; 4 | import { RouterTypes } from 'umi'; 5 | import { GlobalModelState } from './global'; 6 | import { DefaultSettings as SettingModelState } from '../../config/defaultSettings'; 7 | import { UserModelState } from './user'; 8 | 9 | export { GlobalModelState, SettingModelState, UserModelState }; 10 | 11 | export interface Loading { 12 | global: boolean; 13 | effects: { [key: string]: boolean | undefined }; 14 | models: { 15 | global?: boolean; 16 | menu?: boolean; 17 | setting?: boolean; 18 | user?: boolean; 19 | }; 20 | } 21 | 22 | export interface ConnectState { 23 | global: GlobalModelState; 24 | loading: Loading; 25 | settings: SettingModelState; 26 | user: UserModelState; 27 | } 28 | 29 | export type Effect = ( 30 | action: AnyAction, 31 | effects: EffectsCommandMap & { select: (func: (state: ConnectState) => T) => T }, 32 | ) => void; 33 | 34 | /** 35 | * @type P: Type of payload 36 | * @type C: Type of callback 37 | */ 38 | export type Dispatch =

void>(action: { 39 | type: string; 40 | payload?: P; 41 | callback?: C; 42 | [key: string]: any; 43 | }) => any; 44 | 45 | export interface Route extends MenuDataItem { 46 | routes?: Route[]; 47 | } 48 | 49 | /** 50 | * @type T: Params matched in dynamic routing 51 | */ 52 | export interface ConnectProps extends Partial> { 53 | dispatch?: Dispatch; 54 | } 55 | -------------------------------------------------------------------------------- /ui/src/models/global.ts: -------------------------------------------------------------------------------- 1 | import { Reducer } from 'redux'; 2 | import { Subscription } from 'dva'; 3 | 4 | import { Effect } from './connect.d'; 5 | import { NoticeIconData } from '@/components/NoticeIcon'; 6 | import { queryNotices } from '@/services/user'; 7 | 8 | export interface NoticeItem extends NoticeIconData { 9 | id: string; 10 | type: string; 11 | status: string; 12 | } 13 | 14 | export interface GlobalModelState { 15 | collapsed: boolean; 16 | notices: NoticeItem[]; 17 | } 18 | 19 | export interface GlobalModelType { 20 | namespace: 'global'; 21 | state: GlobalModelState; 22 | effects: { 23 | fetchNotices: Effect; 24 | clearNotices: Effect; 25 | changeNoticeReadState: Effect; 26 | }; 27 | reducers: { 28 | changeLayoutCollapsed: Reducer; 29 | saveNotices: Reducer; 30 | saveClearedNotices: Reducer; 31 | }; 32 | subscriptions: { setup: Subscription }; 33 | } 34 | 35 | const GlobalModel: GlobalModelType = { 36 | namespace: 'global', 37 | 38 | state: { 39 | collapsed: false, 40 | notices: [], 41 | }, 42 | 43 | effects: { 44 | *fetchNotices(_, { call, put, select }) { 45 | const data = yield call(queryNotices); 46 | yield put({ 47 | type: 'saveNotices', 48 | payload: data, 49 | }); 50 | const unreadCount: number = yield select( 51 | state => state.global.notices.filter(item => !item.read).length, 52 | ); 53 | yield put({ 54 | type: 'user/changeNotifyCount', 55 | payload: { 56 | totalCount: data.length, 57 | unreadCount, 58 | }, 59 | }); 60 | }, 61 | *clearNotices({ payload }, { put, select }) { 62 | yield put({ 63 | type: 'saveClearedNotices', 64 | payload, 65 | }); 66 | const count: number = yield select(state => state.global.notices.length); 67 | const unreadCount: number = yield select( 68 | state => state.global.notices.filter(item => !item.read).length, 69 | ); 70 | yield put({ 71 | type: 'user/changeNotifyCount', 72 | payload: { 73 | totalCount: count, 74 | unreadCount, 75 | }, 76 | }); 77 | }, 78 | *changeNoticeReadState({ payload }, { put, select }) { 79 | const notices: NoticeItem[] = yield select(state => 80 | state.global.notices.map(item => { 81 | const notice = { ...item }; 82 | if (notice.id === payload) { 83 | notice.read = true; 84 | } 85 | return notice; 86 | }), 87 | ); 88 | 89 | yield put({ 90 | type: 'saveNotices', 91 | payload: notices, 92 | }); 93 | 94 | yield put({ 95 | type: 'user/changeNotifyCount', 96 | payload: { 97 | totalCount: notices.length, 98 | unreadCount: notices.filter(item => !item.read).length, 99 | }, 100 | }); 101 | }, 102 | }, 103 | 104 | reducers: { 105 | changeLayoutCollapsed(state = { notices: [], collapsed: true }, { payload }): GlobalModelState { 106 | return { 107 | ...state, 108 | collapsed: payload, 109 | }; 110 | }, 111 | saveNotices(state, { payload }): GlobalModelState { 112 | return { 113 | collapsed: false, 114 | ...state, 115 | notices: payload, 116 | }; 117 | }, 118 | saveClearedNotices(state = { notices: [], collapsed: true }, { payload }): GlobalModelState { 119 | return { 120 | collapsed: false, 121 | ...state, 122 | notices: state.notices.filter((item): boolean => item.type !== payload), 123 | }; 124 | }, 125 | }, 126 | 127 | subscriptions: { 128 | setup({ history }): void { 129 | // Subscribe history(url) change, trigger `load` action if pathname is `/` 130 | history.listen(({ pathname, search }): void => { 131 | if (typeof window.ga !== 'undefined') { 132 | window.ga('send', 'pageview', pathname + search); 133 | } 134 | }); 135 | }, 136 | }, 137 | }; 138 | 139 | export default GlobalModel; 140 | -------------------------------------------------------------------------------- /ui/src/models/login.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction, Reducer } from 'redux'; 2 | import { parse, stringify } from 'qs'; 3 | 4 | import { EffectsCommandMap } from 'dva'; 5 | import { routerRedux } from 'dva/router'; 6 | 7 | export function getPageQuery(): { 8 | [key: string]: string; 9 | } { 10 | return parse(window.location.href.split('?')[1]); 11 | } 12 | 13 | export type Effect = ( 14 | action: AnyAction, 15 | effects: EffectsCommandMap & { select: (func: (state: {}) => T) => T }, 16 | ) => void; 17 | 18 | export interface ModelType { 19 | namespace: string; 20 | state: {}; 21 | effects: { 22 | logout: Effect; 23 | }; 24 | reducers: { 25 | changeLoginStatus: Reducer<{}>; 26 | }; 27 | } 28 | 29 | const Model: ModelType = { 30 | namespace: 'login', 31 | 32 | state: { 33 | status: undefined, 34 | }, 35 | 36 | effects: { 37 | *logout(_, { put }) { 38 | const { redirect } = getPageQuery(); 39 | // redirect 40 | if (window.location.pathname !== '/user/login' && !redirect) { 41 | yield put( 42 | routerRedux.replace({ 43 | pathname: '/user/login', 44 | search: stringify({ 45 | redirect: window.location.href, 46 | }), 47 | }), 48 | ); 49 | } 50 | }, 51 | }, 52 | 53 | reducers: { 54 | changeLoginStatus(state, { payload }) { 55 | return { 56 | ...state, 57 | status: payload.status, 58 | type: payload.type, 59 | }; 60 | }, 61 | }, 62 | }; 63 | 64 | export default Model; 65 | -------------------------------------------------------------------------------- /ui/src/models/setting.ts: -------------------------------------------------------------------------------- 1 | import { Reducer } from 'redux'; 2 | import { message } from 'antd'; 3 | import defaultSettings, { DefaultSettings } from '../../config/defaultSettings'; 4 | import themeColorClient from '../components/SettingDrawer/themeColorClient'; 5 | 6 | export interface SettingModelType { 7 | namespace: 'settings'; 8 | state: DefaultSettings; 9 | reducers: { 10 | getSetting: Reducer; 11 | changeSetting: Reducer; 12 | }; 13 | } 14 | 15 | const updateTheme = (newPrimaryColor?: string) => { 16 | if (newPrimaryColor) { 17 | const timeOut = 0; 18 | const hideMessage = message.loading('正在切换主题!', timeOut); 19 | themeColorClient.changeColor(newPrimaryColor).finally(() => hideMessage()); 20 | } 21 | }; 22 | 23 | const updateColorWeak: (colorWeak: boolean) => void = colorWeak => { 24 | const root = document.getElementById('root'); 25 | if (root) { 26 | root.className = colorWeak ? 'colorWeak' : ''; 27 | } 28 | }; 29 | 30 | const SettingModel: SettingModelType = { 31 | namespace: 'settings', 32 | state: defaultSettings, 33 | reducers: { 34 | getSetting(state = defaultSettings) { 35 | const setting: Partial = {}; 36 | const urlParams = new URL(window.location.href); 37 | Object.keys(state).forEach(key => { 38 | if (urlParams.searchParams.has(key)) { 39 | const value = urlParams.searchParams.get(key); 40 | setting[key] = value === '1' ? true : value; 41 | } 42 | }); 43 | const { primaryColor, colorWeak } = setting; 44 | 45 | if (primaryColor && state.primaryColor !== primaryColor) { 46 | updateTheme(primaryColor); 47 | } 48 | updateColorWeak(!!colorWeak); 49 | return { 50 | ...state, 51 | ...setting, 52 | }; 53 | }, 54 | changeSetting(state = defaultSettings, { payload }) { 55 | const urlParams = new URL(window.location.href); 56 | Object.keys(defaultSettings).forEach(key => { 57 | if (urlParams.searchParams.has(key)) { 58 | urlParams.searchParams.delete(key); 59 | } 60 | }); 61 | Object.keys(payload).forEach(key => { 62 | if (key === 'collapse') { 63 | return; 64 | } 65 | let value = payload[key]; 66 | if (value === true) { 67 | value = 1; 68 | } 69 | if (defaultSettings[key] !== value) { 70 | urlParams.searchParams.set(key, value); 71 | } 72 | }); 73 | const { primaryColor, colorWeak, contentWidth } = payload; 74 | if (primaryColor && state.primaryColor !== primaryColor) { 75 | updateTheme(primaryColor); 76 | } 77 | if (state.contentWidth !== contentWidth && window.dispatchEvent) { 78 | window.dispatchEvent(new Event('resize')); 79 | } 80 | updateColorWeak(!!colorWeak); 81 | window.history.replaceState(null, 'setting', urlParams.href); 82 | return { 83 | ...state, 84 | ...payload, 85 | }; 86 | }, 87 | }, 88 | }; 89 | export default SettingModel; 90 | -------------------------------------------------------------------------------- /ui/src/models/user.ts: -------------------------------------------------------------------------------- 1 | import { Effect } from 'dva'; 2 | import { Reducer } from 'redux'; 3 | 4 | import { queryCurrent, query as queryUsers } from '@/services/user'; 5 | 6 | export interface CurrentUser { 7 | avatar?: string; 8 | name?: string; 9 | title?: string; 10 | group?: string; 11 | signature?: string; 12 | tags?: { 13 | key: string; 14 | label: string; 15 | }[]; 16 | unreadCount?: number; 17 | } 18 | 19 | export interface UserModelState { 20 | currentUser?: CurrentUser; 21 | } 22 | 23 | export interface UserModelType { 24 | namespace: 'user'; 25 | state: UserModelState; 26 | effects: { 27 | fetch: Effect; 28 | fetchCurrent: Effect; 29 | }; 30 | reducers: { 31 | saveCurrentUser: Reducer; 32 | changeNotifyCount: Reducer; 33 | }; 34 | } 35 | 36 | const UserModel: UserModelType = { 37 | namespace: 'user', 38 | 39 | state: { 40 | currentUser: {}, 41 | }, 42 | 43 | effects: { 44 | *fetch(_, { call, put }) { 45 | const response = yield call(queryUsers); 46 | yield put({ 47 | type: 'save', 48 | payload: response, 49 | }); 50 | }, 51 | *fetchCurrent(_, { call, put }) { 52 | const response = yield call(queryCurrent); 53 | yield put({ 54 | type: 'saveCurrentUser', 55 | payload: response, 56 | }); 57 | }, 58 | }, 59 | 60 | reducers: { 61 | saveCurrentUser(state, action) { 62 | return { 63 | ...state, 64 | currentUser: action.payload || {}, 65 | }; 66 | }, 67 | changeNotifyCount( 68 | state = { 69 | currentUser: {}, 70 | }, 71 | action, 72 | ) { 73 | return { 74 | ...state, 75 | currentUser: { 76 | ...state.currentUser, 77 | notifyCount: action.payload.totalCount, 78 | unreadCount: action.payload.unreadCount, 79 | }, 80 | }; 81 | }, 82 | }, 83 | }; 84 | 85 | export default UserModel; 86 | -------------------------------------------------------------------------------- /ui/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Result } from 'antd'; 2 | import React from 'react'; 3 | import router from 'umi/router'; 4 | 5 | // 这里应该使用 antd 的 404 result 组件, 6 | // 但是还没发布,先来个简单的。 7 | 8 | const NoFoundPage: React.FC<{}> = () => ( 9 | router.push('/')}> 15 | Back Home 16 | 17 | } 18 | > 19 | ); 20 | 21 | export default NoFoundPage; 22 | -------------------------------------------------------------------------------- /ui/src/pages/Authorized.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Redirect from 'umi/redirect'; 3 | import { connect } from 'dva'; 4 | import pathToRegexp from 'path-to-regexp'; 5 | import Authorized from '@/utils/Authorized'; 6 | import { ConnectProps, ConnectState, Route, UserModelState } from '@/models/connect'; 7 | 8 | interface AuthComponentProps extends ConnectProps { 9 | user: UserModelState; 10 | } 11 | 12 | const getRouteAuthority = (path: string, routeData: Route[]) => { 13 | let authorities: string[] | string | undefined; 14 | routeData.forEach(route => { 15 | // match prefix 16 | if (pathToRegexp(`${route.path}(.*)`).test(path)) { 17 | // exact match 18 | if (route.path === path) { 19 | authorities = route.authority || authorities; 20 | } 21 | // get children authority recursively 22 | if (route.routes) { 23 | authorities = getRouteAuthority(path, route.routes) || authorities; 24 | } 25 | } 26 | }); 27 | return authorities; 28 | }; 29 | 30 | const AuthComponent: React.FC = ({ 31 | children, 32 | route = { 33 | routes: [], 34 | }, 35 | location = { 36 | pathname: '', 37 | }, 38 | user, 39 | }) => { 40 | const { currentUser } = user; 41 | const { routes = [] } = route; 42 | const isLogin = currentUser && currentUser.name; 43 | return ( 44 | : } 47 | > 48 | {children} 49 | 50 | ); 51 | }; 52 | 53 | export default connect(({ user }: ConnectState) => ({ 54 | user, 55 | }))(AuthComponent); 56 | -------------------------------------------------------------------------------- /ui/src/pages/Welcome.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PageHeaderWrapper } from '@ant-design/pro-layout'; 3 | 4 | export default (): React.ReactNode => ( 5 | 6 |

7 | Want to add more pages? Please refer to{' '} 8 | 9 | use block 10 | 11 | 。 12 |

13 | 14 | ); 15 | -------------------------------------------------------------------------------- /ui/src/service-worker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable eslint-comments/disable-enable-pair */ 2 | /* eslint-disable no-restricted-globals */ 3 | /* eslint-disable no-underscore-dangle */ 4 | /* globals workbox */ 5 | workbox.core.setCacheNameDetails({ 6 | prefix: 'antd-pro', 7 | suffix: 'v1', 8 | }); 9 | // Control all opened tabs ASAP 10 | workbox.clientsClaim(); 11 | 12 | /** 13 | * Use precaching list generated by workbox in build process. 14 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching 15 | */ 16 | workbox.precaching.precacheAndRoute(self.__precacheManifest || []); 17 | 18 | /** 19 | * Register a navigation route. 20 | * https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route 21 | */ 22 | workbox.routing.registerNavigationRoute('/index.html'); 23 | 24 | /** 25 | * Use runtime cache: 26 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute 27 | * 28 | * Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc. 29 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies 30 | */ 31 | 32 | /** 33 | * Handle API requests 34 | */ 35 | workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst()); 36 | 37 | /** 38 | * Handle third party requests 39 | */ 40 | workbox.routing.registerRoute( 41 | /^https:\/\/gw.alipayobjects.com\//, 42 | workbox.strategies.networkFirst(), 43 | ); 44 | workbox.routing.registerRoute( 45 | /^https:\/\/cdnjs.cloudflare.com\//, 46 | workbox.strategies.networkFirst(), 47 | ); 48 | workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst()); 49 | 50 | /** 51 | * Response to client after skipping waiting with MessageChannel 52 | */ 53 | addEventListener('message', event => { 54 | const replyPort = event.ports[0]; 55 | const message = event.data; 56 | if (replyPort && message && message.type === 'skip-waiting') { 57 | event.waitUntil( 58 | self 59 | .skipWaiting() 60 | .then( 61 | () => replyPort.postMessage({ error: null }), 62 | error => replyPort.postMessage({ error }), 63 | ), 64 | ); 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /ui/src/services/user.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export async function query(): Promise { 4 | return request('/api/users'); 5 | } 6 | 7 | export async function queryCurrent(): Promise { 8 | return request('/api/currentUser'); 9 | } 10 | 11 | export async function queryNotices(): Promise { 12 | return request('/api/notices'); 13 | } 14 | -------------------------------------------------------------------------------- /ui/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'slash2'; 2 | declare module 'antd-theme-webpack-plugin'; 3 | 4 | declare module '*.css'; 5 | declare module '*.less'; 6 | declare module '*.scss'; 7 | declare module '*.sass'; 8 | declare module '*.svg'; 9 | declare module '*.png'; 10 | declare module '*.jpg'; 11 | declare module '*.jpeg'; 12 | declare module '*.gif'; 13 | declare module '*.bmp'; 14 | declare module '*.tiff'; 15 | declare module 'omit.js'; 16 | declare module 'react-copy-to-clipboard'; 17 | declare module 'react-fittext'; 18 | declare module '@antv/data-set'; 19 | declare module 'nzh/cn'; 20 | declare module 'webpack-theme-color-replacer'; 21 | declare module 'webpack-theme-color-replacer/client'; 22 | 23 | // google analytics interface 24 | interface GAFieldsObject { 25 | eventCategory: string; 26 | eventAction: string; 27 | eventLabel?: string; 28 | eventValue?: number; 29 | nonInteraction?: boolean; 30 | } 31 | interface Window { 32 | ga: ( 33 | command: 'send', 34 | hitType: 'event' | 'pageview', 35 | fieldsObject: GAFieldsObject | string, 36 | ) => void; 37 | } 38 | 39 | declare let ga: Function; 40 | 41 | // preview.pro.ant.design only do not use in your production ; 42 | // preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。 43 | declare let ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: 'site' | undefined; 44 | -------------------------------------------------------------------------------- /ui/src/utils/Authorized.ts: -------------------------------------------------------------------------------- 1 | import RenderAuthorize from '@/components/Authorized'; 2 | import { getAuthority } from './authority'; 3 | /* eslint-disable eslint-comments/disable-enable-pair */ 4 | /* eslint-disable import/no-mutable-exports */ 5 | let Authorized = RenderAuthorize(getAuthority()); 6 | 7 | // Reload the rights component 8 | const reloadAuthorized = (): void => { 9 | Authorized = RenderAuthorize(getAuthority()); 10 | }; 11 | 12 | export { reloadAuthorized }; 13 | export default Authorized; 14 | -------------------------------------------------------------------------------- /ui/src/utils/authority.test.ts: -------------------------------------------------------------------------------- 1 | import { getAuthority } from './authority'; 2 | 3 | describe('getAuthority should be strong', () => { 4 | it('string', () => { 5 | expect(getAuthority('admin')).toEqual(['admin']); 6 | }); 7 | it('array with double quotes', () => { 8 | expect(getAuthority('"admin"')).toEqual(['admin']); 9 | }); 10 | it('array with single item', () => { 11 | expect(getAuthority('["admin"]')).toEqual(['admin']); 12 | }); 13 | it('array with multiple items', () => { 14 | expect(getAuthority('["admin", "guest"]')).toEqual(['admin', 'guest']); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /ui/src/utils/authority.ts: -------------------------------------------------------------------------------- 1 | // use localStorage to store the authority info, which might be sent from server in actual project. 2 | export function getAuthority(str?: string): string | string[] { 3 | // return localStorage.getItem('antd-pro-authority') || ['admin', 'user']; 4 | const authorityString = 5 | typeof str === 'undefined' ? localStorage.getItem('antd-pro-authority') : str; 6 | // authorityString could be admin, "admin", ["admin"] 7 | let authority; 8 | try { 9 | if (authorityString) { 10 | authority = JSON.parse(authorityString); 11 | } 12 | } catch (e) { 13 | authority = authorityString; 14 | } 15 | if (typeof authority === 'string') { 16 | return [authority]; 17 | } 18 | // preview.pro.ant.design only do not use in your production. 19 | // preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。 20 | if (!authority && ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') { 21 | return ['admin']; 22 | } 23 | return authority; 24 | } 25 | 26 | export function setAuthority(authority: string | string[]): void { 27 | const proAuthority = typeof authority === 'string' ? [authority] : authority; 28 | return localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority)); 29 | } 30 | -------------------------------------------------------------------------------- /ui/src/utils/request.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * request 网络请求工具 3 | * 更详细的 api 文档: https://github.com/umijs/umi-request 4 | */ 5 | import { extend } from 'umi-request'; 6 | import { notification } from 'antd'; 7 | 8 | const codeMessage = { 9 | 200: '服务器成功返回请求的数据。', 10 | 201: '新建或修改数据成功。', 11 | 202: '一个请求已经进入后台排队(异步任务)。', 12 | 204: '删除数据成功。', 13 | 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', 14 | 401: '用户没有权限(令牌、用户名、密码错误)。', 15 | 403: '用户得到授权,但是访问是被禁止的。', 16 | 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。', 17 | 406: '请求的格式不可得。', 18 | 410: '请求的资源被永久删除,且不会再得到的。', 19 | 422: '当创建一个对象时,发生一个验证错误。', 20 | 500: '服务器发生错误,请检查服务器。', 21 | 502: '网关错误。', 22 | 503: '服务不可用,服务器暂时过载或维护。', 23 | 504: '网关超时。', 24 | }; 25 | 26 | /** 27 | * 异常处理程序 28 | */ 29 | const errorHandler = (error: { response: Response }): Response => { 30 | const { response } = error; 31 | if (response && response.status) { 32 | const errorText = codeMessage[response.status] || response.statusText; 33 | const { status, url } = response; 34 | 35 | notification.error({ 36 | message: `请求错误 ${status}: ${url}`, 37 | description: errorText, 38 | }); 39 | } 40 | return response; 41 | }; 42 | 43 | /** 44 | * 配置request请求时的默认参数 45 | */ 46 | const request = extend({ 47 | errorHandler, // 默认错误处理 48 | credentials: 'include', // 默认请求是否带上cookie 49 | }); 50 | 51 | export default request; 52 | -------------------------------------------------------------------------------- /ui/src/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | white-space: nowrap; 4 | text-overflow: ellipsis; 5 | word-break: break-all; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | position: relative; 10 | max-height: @line * 1.5em; 11 | margin-right: -1em; 12 | padding-right: 1em; 13 | overflow: hidden; 14 | line-height: 1.5em; 15 | text-align: justify; 16 | &::before { 17 | position: absolute; 18 | right: 14px; 19 | bottom: 0; 20 | padding: 0 1px; 21 | background: @bg; 22 | content: '...'; 23 | } 24 | &::after { 25 | position: absolute; 26 | right: 14px; 27 | width: 1em; 28 | height: 1em; 29 | margin-top: 0.2em; 30 | background: white; 31 | content: ''; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &::before, 40 | &::after { 41 | display: table; 42 | content: ' '; 43 | } 44 | &::after { 45 | clear: both; 46 | height: 0; 47 | font-size: 0; 48 | visibility: hidden; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ui/src/utils/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { isUrl } from './utils'; 2 | 3 | describe('isUrl tests', (): void => { 4 | it('should return false for invalid and corner case inputs', (): void => { 5 | expect(isUrl([] as any)).toBeFalsy(); 6 | expect(isUrl({} as any)).toBeFalsy(); 7 | expect(isUrl(false as any)).toBeFalsy(); 8 | expect(isUrl(true as any)).toBeFalsy(); 9 | expect(isUrl(NaN as any)).toBeFalsy(); 10 | expect(isUrl(null as any)).toBeFalsy(); 11 | expect(isUrl(undefined as any)).toBeFalsy(); 12 | expect(isUrl('')).toBeFalsy(); 13 | }); 14 | 15 | it('should return false for invalid URLs', (): void => { 16 | expect(isUrl('foo')).toBeFalsy(); 17 | expect(isUrl('bar')).toBeFalsy(); 18 | expect(isUrl('bar/test')).toBeFalsy(); 19 | expect(isUrl('http:/example.com/')).toBeFalsy(); 20 | expect(isUrl('ttp://example.com/')).toBeFalsy(); 21 | }); 22 | 23 | it('should return true for valid URLs', (): void => { 24 | expect(isUrl('http://example.com/')).toBeTruthy(); 25 | expect(isUrl('https://example.com/')).toBeTruthy(); 26 | expect(isUrl('http://example.com/test/123')).toBeTruthy(); 27 | expect(isUrl('https://example.com/test/123')).toBeTruthy(); 28 | expect(isUrl('http://example.com/test/123?foo=bar')).toBeTruthy(); 29 | expect(isUrl('https://example.com/test/123?foo=bar')).toBeTruthy(); 30 | expect(isUrl('http://www.example.com/')).toBeTruthy(); 31 | expect(isUrl('https://www.example.com/')).toBeTruthy(); 32 | expect(isUrl('http://www.example.com/test/123')).toBeTruthy(); 33 | expect(isUrl('https://www.example.com/test/123')).toBeTruthy(); 34 | expect(isUrl('http://www.example.com/test/123?foo=bar')).toBeTruthy(); 35 | expect(isUrl('https://www.example.com/test/123?foo=bar')).toBeTruthy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /ui/src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | /* eslint no-useless-escape:0 import/prefer-default-export:0 */ 2 | const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/; 3 | 4 | const isUrl = (path: string): boolean => reg.test(path); 5 | 6 | const isAntDesignPro = (): boolean => { 7 | if (ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') { 8 | return true; 9 | } 10 | return window.location.hostname === 'preview.pro.ant.design'; 11 | }; 12 | 13 | // 给官方演示站点用,用于关闭真实开发环境不需要使用的特性 14 | const isAntDesignProOrDev = (): boolean => { 15 | const { NODE_ENV } = process.env; 16 | if (NODE_ENV === 'development') { 17 | return true; 18 | } 19 | return isAntDesignPro(); 20 | }; 21 | 22 | export { isAntDesignProOrDev, isAntDesignPro, isUrl }; 23 | -------------------------------------------------------------------------------- /ui/tests/run-tests.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable eslint-comments/disable-enable-pair */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | /* eslint-disable eslint-comments/no-unlimited-disable */ 4 | const { spawn } = require('child_process'); 5 | const { kill } = require('cross-port-killer'); 6 | 7 | const env = Object.create(process.env); 8 | env.BROWSER = 'none'; 9 | env.TEST = true; 10 | // flag to prevent multiple test 11 | let once = false; 12 | 13 | const startServer = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['start'], { 14 | env, 15 | }); 16 | 17 | startServer.stderr.on('data', data => { 18 | // eslint-disable-next-line 19 | console.log(data.toString()); 20 | }); 21 | 22 | startServer.on('exit', () => { 23 | kill(process.env.PORT || 8000); 24 | }); 25 | 26 | console.log('Starting development server for e2e tests...'); 27 | startServer.stdout.on('data', data => { 28 | console.log(data.toString()); 29 | // hack code , wait umi 30 | if ( 31 | (!once && data.toString().indexOf('Compiled successfully') >= 0) || 32 | data.toString().indexOf('Theme generated successfully') >= 0 33 | ) { 34 | // eslint-disable-next-line 35 | once = true; 36 | console.log('Development server is started, ready to run tests.'); 37 | const testCmd = spawn( 38 | /^win/.test(process.platform) ? 'npm.cmd' : 'npm', 39 | ['test', '--', '--maxWorkers=1', '--runInBand'], 40 | { 41 | stdio: 'inherit', 42 | }, 43 | ); 44 | testCmd.on('exit', code => { 45 | startServer.kill(); 46 | process.exit(code); 47 | }); 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build/dist", 4 | "module": "esnext", 5 | "target": "esnext", 6 | "lib": ["esnext", "dom"], 7 | "sourceMap": true, 8 | "baseUrl": ".", 9 | "jsx": "react", 10 | "allowSyntheticDefaultImports": true, 11 | "moduleResolution": "node", 12 | "forceConsistentCasingInFileNames": true, 13 | "noImplicitReturns": true, 14 | "suppressImplicitAnyIndexErrors": true, 15 | "noUnusedLocals": true, 16 | "allowJs": true, 17 | "experimentalDecorators": true, 18 | "strict": true, 19 | "paths": { 20 | "@/*": ["./src/*"] 21 | } 22 | }, 23 | "exclude": [ 24 | "node_modules", 25 | "build", 26 | "scripts", 27 | "acceptance-tests", 28 | "webpack", 29 | "jest", 30 | "src/setupTests.ts", 31 | "tslint:latest", 32 | "tslint-config-prettier" 33 | ] 34 | } 35 | --------------------------------------------------------------------------------