diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 3915579c..00000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "params-validate"] - path = params-validate - url = git@github.com:CodingSoldier/params-validate.git -[submodule "test-params-validate"] - path = test-params-validate - url = git@github.com:CodingSoldier/test-params-validate.git diff --git a/note/pom.xml b/note/pom.xml index 3e327630..aa87ab23 100644 --- a/note/pom.xml +++ b/note/pom.xml @@ -2,14 +2,21 @@ - - java-learn - org.cpq - 1.0-SNAPSHOT - + 4.0.0 + + com.example note + 0.0.1-SNAPSHOT + note + + + UTF-8 + 4.12 + 5.0.1.RELEASE + 2.3.0 + @@ -56,6 +63,140 @@ 15.0 + + + + junit + junit + ${junit.version} + + + + org.slf4j + slf4j-log4j12 + 1.7.25 + + + org.apache.logging.log4j + log4j-web + 2.9.1 + + + + org.apache.commons + commons-lang3 + 3.7 + + + commons-beanutils + commons-beanutils + 1.9.3 + + + commons-io + commons-io + 2.6 + + + commons-fileupload + commons-fileupload + 1.3 + + + + commons-io + commons-io + + + + + org.apache.commons + commons-collections4 + 4.1 + + + + org.dom4j + dom4j + 2.1.1 + + + + + org.springframework + spring-aspects + ${spring.version} + + + + org.springframework + spring-webmvc + ${spring.version} + + + + + org.springframework + spring-orm + ${spring.version} + + + + org.springframework + spring-context-support + ${spring.version} + + + + org.springframework + spring-test + ${spring.version} + + + + + + + javax.servlet + javax.servlet-api + 3.0.1 + provided + + + + com.fasterxml.jackson.core + jackson-databind + 2.9.2 + + + com.alibaba + fastjson + 1.2.41 + + + + + mysql + mysql-connector-java + 8.0.8-dmr + + + com.mchange + c3p0 + 0.9.5.2 + + + org.mybatis + mybatis + 3.4.5 + + + org.mybatis + mybatis-spring + 1.3.1 + + + diff --git a/note/src/main/java/com/datastructure/_5_BinarySearchTree.java b/note/src/main/java/com/datastructure/_5_BinarySearchTree.java index 5de6ad69..724ca2e8 100644 --- a/note/src/main/java/com/datastructure/_5_BinarySearchTree.java +++ b/note/src/main/java/com/datastructure/_5_BinarySearchTree.java @@ -245,16 +245,16 @@ public static void main(String[] args) { bst.add(num); } - // System.out.println("-------前序遍历------"); - // bst.preOrder(); - // System.out.println("------中序遍历-------"); - // bst.inOrder(); - // System.out.println("------后序遍历-------"); - // bst.postOrder(); - // System.out.println("------广度优先遍历-------"); - // bst.levelOrder(); - // - // + System.out.println("-------前序遍历------"); + bst.preOrder(); + System.out.println("------中序遍历-------"); + bst.inOrder(); + System.out.println("------后序遍历-------"); + bst.postOrder(); + System.out.println("------广度优先遍历-------"); + bst.levelOrder(); + + // ArrayList nums2 = new ArrayList<>(); //while(!bst.isEmpty()) @@ -266,7 +266,7 @@ public static void main(String[] args) { //System.out.println(nums2); // - bst.remove(16); + // bst.remove(16); //bst.preOrder(); } diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 1bc4e319..00000000 --- a/pom.xml +++ /dev/null @@ -1,256 +0,0 @@ - - - 4.0.0 - - - org.cpq - java-learn - pom - 1.0-SNAPSHOT - - - - - UTF-8 - 4.12 - 5.0.1.RELEASE - 2.3.0 - - - - - - - - - - - - - - - junit - junit - ${junit.version} - - - - org.slf4j - slf4j-log4j12 - 1.7.25 - - - org.apache.logging.log4j - log4j-web - 2.9.1 - - - - org.apache.commons - commons-lang3 - 3.7 - - - commons-beanutils - commons-beanutils - 1.9.3 - - - commons-io - commons-io - 2.6 - - - commons-fileupload - commons-fileupload - 1.3 - - - - commons-io - commons-io - - - - - org.apache.commons - commons-collections4 - 4.1 - - - - org.dom4j - dom4j - 2.1.1 - - - - - org.springframework - spring-aspects - ${spring.version} - - - - org.springframework - spring-webmvc - ${spring.version} - - - - - org.springframework - spring-orm - ${spring.version} - - - - org.springframework - spring-context-support - ${spring.version} - - - - org.springframework - spring-test - ${spring.version} - - - - - - - javax.servlet - javax.servlet-api - 3.0.1 - provided - - - - com.fasterxml.jackson.core - jackson-databind - 2.9.2 - - - com.alibaba - fastjson - 1.2.41 - - - - - mysql - mysql-connector-java - 8.0.8-dmr - - - com.mchange - c3p0 - 0.9.5.2 - - - org.mybatis - mybatis - 3.4.5 - - - org.mybatis - mybatis-spring - 1.3.1 - - - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - 1.8 - 1.8 - UTF-8 - - - - - - org.mybatis.generator - mybatis-generator-maven-plugin - 1.3.6 - - false - true - ${basedir}/src/main/resources/mybatis-generator/generatorConfig.xml - - - - - - mysql - mysql-connector-java - - 5.1.44 - - - - - - - org.eclipse.jetty - jetty-maven-plugin - 9.4.8.v20171121 - - - - - - - - - - note - - - \ No newline at end of file diff --git a/spring-cloud-2/hystrix-dashboard/.gitignore b/project/apollo/apollo-01/.gitignore similarity index 76% rename from spring-cloud-2/hystrix-dashboard/.gitignore rename to project/apollo/apollo-01/.gitignore index a2a3040a..549e00a2 100644 --- a/spring-cloud-2/hystrix-dashboard/.gitignore +++ b/project/apollo/apollo-01/.gitignore @@ -1,8 +1,8 @@ HELP.md target/ !.mvn/wrapper/maven-wrapper.jar -!**/src/main/** -!**/src/test/** +!**/src/main/**/target/ +!**/src/test/**/target/ ### STS ### .apt_generated @@ -26,6 +26,8 @@ target/ /nbdist/ /.nb-gradle/ build/ +!**/src/main/**/build/ +!**/src/test/**/build/ ### VS Code ### .vscode/ diff --git a/project/apollo/apollo-01/README.md b/project/apollo/apollo-01/README.md new file mode 100644 index 00000000..7e6f0fd1 --- /dev/null +++ b/project/apollo/apollo-01/README.md @@ -0,0 +1,46 @@ +## 官方文档 +https://www.apolloconfig.com/#/zh/deployment/quick-start + +## spring-boot整合方式 +1、导入依赖 +```xml + + com.ctrip.framework.apollo + apollo-client + 2.1.0 + +``` +2、添加配置文件 +```yaml +app: + # 应用id + id: SampleApp +apollo: + # 服务端地址 + meta: http://127.0.0.1:8080 + bootstrap: + enabled: true + eagerLoad: + enabled: true +``` +3、启动类Apollo01Application.java添加@EnableApolloConfig + +4、apollo服务端新增配置项:test.value01 +![](./file/服务端.png) + +5、代码获取配置项 +```java + @Value("${test.value01}") + private String value01; +``` + +## 使用方式补充 +1、只有使用分布式部署方式才能在配置文件中配置环境。web管理页面无法新建环境,Quick Start方式部署也不支持配置环境。 + +2、如果apollo服务端有多环境,可以加上JVM参数-Denv指定环境。例如: +```shell +java -jar test.jar -Denv=DEV +``` + + + diff --git "a/project/apollo/apollo-01/file/\346\234\215\345\212\241\347\253\257.png" "b/project/apollo/apollo-01/file/\346\234\215\345\212\241\347\253\257.png" new file mode 100644 index 00000000..1b064015 Binary files /dev/null and "b/project/apollo/apollo-01/file/\346\234\215\345\212\241\347\253\257.png" differ diff --git a/project/apollo/apollo-01/pom.xml b/project/apollo/apollo-01/pom.xml new file mode 100644 index 00000000..052490d0 --- /dev/null +++ b/project/apollo/apollo-01/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.16 + + + com.example + apollo-01 + 0.0.1-SNAPSHOT + apollo-01 + apollo-01 + + 1.8 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.ctrip.framework.apollo + apollo-client + 2.1.0 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/project/apollo/apollo-01/src/main/java/com/example/apollo01/Apollo01Application.java b/project/apollo/apollo-01/src/main/java/com/example/apollo01/Apollo01Application.java new file mode 100644 index 00000000..43ea63fb --- /dev/null +++ b/project/apollo/apollo-01/src/main/java/com/example/apollo01/Apollo01Application.java @@ -0,0 +1,14 @@ +package com.example.apollo01; + +import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@EnableApolloConfig +public class Apollo01Application { + public static void main(String[] args) { + SpringApplication.run(Apollo01Application.class, args); + } + +} diff --git a/project/apollo/apollo-01/src/main/java/com/example/apollo01/ValueController.java b/project/apollo/apollo-01/src/main/java/com/example/apollo01/ValueController.java new file mode 100644 index 00000000..cfad7ef1 --- /dev/null +++ b/project/apollo/apollo-01/src/main/java/com/example/apollo01/ValueController.java @@ -0,0 +1,35 @@ +package com.example.apollo01; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author chenpq05 + * @since 2023/11/14 15:59 + */ +@RestController +public class ValueController { + + @Value("${test.value01}") + private String value01; + + @Value("${test9}") + private String test9; + + @Value("${test.public1}") + private String testpublic1; + + //@Value("${cluster-value01}") + //private String clusterValue01; + + @GetMapping("/value01") + public String value01() { + System.out.println("#################value01="+value01); + System.out.println("#################test9="+test9); + System.out.println("#################testpublic1="+testpublic1); + //System.out.println("#################clusterValue01="+clusterValue01); + return ""; + } + +} diff --git a/project/apollo/apollo-01/src/main/resources/application.yml b/project/apollo/apollo-01/src/main/resources/application.yml new file mode 100644 index 00000000..bac886c9 --- /dev/null +++ b/project/apollo/apollo-01/src/main/resources/application.yml @@ -0,0 +1,22 @@ +server: + port: 8888 + +app: + id: SampleApp +apollo: + meta: http://127.0.0.1:8080 + # 指定cluster + cluster: default + bootstrap: + # 项目中的配置文件application.yml优先级最低 + # namespaces不填写application,apollo服务器上的application(Namespace)也会被加载 + # Namespace的优先级与public、private属性无关,与配置项的顺序有关。 + # 例如 namespaces: TEST1.public.common,apollo-01 。TEST1.public.common是公共配置但是优先级比apollo-01高 + namespaces: TEST1.public.common,apollo-01 + enabled: true + eagerLoad: + enabled: true + + + +test9: 999999 diff --git a/project/apollo/apollo-01/src/test/java/com/example/apollo01/Apollo01ApplicationTests.java b/project/apollo/apollo-01/src/test/java/com/example/apollo01/Apollo01ApplicationTests.java new file mode 100644 index 00000000..7a9ed130 --- /dev/null +++ b/project/apollo/apollo-01/src/test/java/com/example/apollo01/Apollo01ApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.apollo01; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Apollo01ApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git "a/project/distributed-transaction/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\344\270\223\351\242\230.pdf" "b/project/distributed-transaction/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\344\270\223\351\242\230.pdf" new file mode 100644 index 00000000..53a5705a Binary files /dev/null and "b/project/distributed-transaction/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\344\270\223\351\242\230.pdf" differ diff --git "a/project/docker+k8s/\345\256\211\350\243\205k8s.txt" "b/project/docker+k8s/\345\256\211\350\243\205k8s.txt" new file mode 100644 index 00000000..e1cf8786 --- /dev/null +++ "b/project/docker+k8s/\345\256\211\350\243\205k8s.txt" @@ -0,0 +1 @@ +https://kuboard.cn/install/history-k8s/install-k8s-1.19.x.html \ No newline at end of file diff --git a/project/java21/pom.xml b/project/java21/pom.xml new file mode 100644 index 00000000..b3f4bd51 --- /dev/null +++ b/project/java21/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + java21 + + + 21 + 21 + UTF-8 + + + \ No newline at end of file diff --git a/project/java21/src/main/java/org/cpq/JDK21.java b/project/java21/src/main/java/org/cpq/JDK21.java new file mode 100644 index 00000000..d613d914 --- /dev/null +++ b/project/java21/src/main/java/org/cpq/JDK21.java @@ -0,0 +1,86 @@ +package org.cpq; + + +public class JDK21 { + public static void main(String[] args) { + + /** + * 序列化集合 + */ + // ArrayList al = new ArrayList<>(); + // al.add(1); + // al.addFirst(0); + // al.addLast(2); + // System.out.println(al.getFirst()); + // System.out.println(al.getLast()); + // System.out.println(al.reversed()); + + // LinkedHashMap map = new LinkedHashMap<>(); + // map.put(2, "two"); + // map.putFirst(1, "one"); + // map.putLast(3, "three"); + // System.out.println(map); + // + // Map.Entry entry = map.pollFirstEntry(); + // System.out.println(entry); + // System.out.println(map); + // + // System.out.println(map.reversed()); + // System.out.println(map); + + /** + * 记录模式 + */ + // 记录模式 与 instanceof 结合使用 + // Shape circle = new Shape("circle", 1); + // if (circle instanceof Shape(String type, long unit)) { + // System.out.println("type = " + type + "," + "unit = " + unit ); + // } + + // 记录模式 与 switch 结合使用 + // IShape shape = new Square(11); + // IShape shape = new Rectangle(11, 2); + // switch (shape) { + // case Circle(double radius): + // System.out.println("shape is circle radius = " + radius); + // break; + // case Square(double s): + // System.out.println("shape is square side = " + s); + // break; + // case Rectangle(double length, double width): + // System.out.println("shape is Rectangle length = " + length); + // break; + // default: + // System.out.println("default....."); + // break; + // } + + + /** + * switch 的模式匹配 + */ + System.out.println(formatterPatternSwitch(1)); + + + + } + + static String formatterPatternSwitch(Object obj) { + return switch (obj) { + case Integer i -> String.format("int %d", i); + case Long l -> String.format("long %d", l); + case Double d -> String.format("double %f", d); + case String s -> String.format("String %s", s); + default -> String.format("unsupported type %s", obj.getClass().getName()); + }; + } + +} + +// 定义记录类 +record Shape(String type, long unit){} + +interface IShape{} +record Circle(double radius) implements IShape{} +record Square(double side) implements IShape{} +record Rectangle(double length, double width) implements IShape{} \ No newline at end of file diff --git a/project/java21/src/main/java/org/cpq/myvirtualthread/Demo.java b/project/java21/src/main/java/org/cpq/myvirtualthread/Demo.java new file mode 100644 index 00000000..61d9c016 --- /dev/null +++ b/project/java21/src/main/java/org/cpq/myvirtualthread/Demo.java @@ -0,0 +1,38 @@ +package org.cpq.myvirtualthread; + +public class Demo { + public static final VirtualThreadScheduler SCHEDULER = new VirtualThreadScheduler(); + + /** + * 模拟虚拟线程: + * + * 创建虚拟线程类 MyVirtualThread + * 创建调度器 VirtualThreadScheduler + * 使用queue保存虚拟线程 + * 使用平台线程运行虚拟线程 + * 使用ScopedValue替代线程的ThreadLocal + * 创建WaitingOperation实现虚拟线程等待一段时间后继续运行 + * Continuation.yield(MyVirtualThread.SCOPE); 实现虚拟线程等待 + * Timer实现虚拟线程重新加入调度器VirtualThreadScheduler,继续运行Continuation.yield(MyVirtualThread.SCOPE)中断点后面的代码 + * + * main方法会创建100个虚拟线程,每个虚拟线程运行到一半会调用WaitingOperation.perform()模拟IO等待, + * 等待一段时间后继续运行,此时虚拟线程可能会在其他平台线程中运行 + */ + public static void main(String[] args) { + new Thread(SCHEDULER::start).start(); + for (int i = 0; i < 100; i++) { + MyVirtualThread vt1 = new MyVirtualThread(() -> { + System.out.println("1.1"); + WaitingOperation.perform("发送HTTP请求,需要时间", 200); + System.out.println("1.2"); + }); + MyVirtualThread vt2 = new MyVirtualThread(() -> { + System.out.println("2.1"); + WaitingOperation.perform("查询数据库,需要时间", 300); + System.out.println("2.2"); + }); + SCHEDULER.schedule(vt1); + SCHEDULER.schedule(vt2); + } + } +} diff --git a/project/java21/src/main/java/org/cpq/myvirtualthread/MyVirtualThread.java b/project/java21/src/main/java/org/cpq/myvirtualthread/MyVirtualThread.java new file mode 100644 index 00000000..0d47a60f --- /dev/null +++ b/project/java21/src/main/java/org/cpq/myvirtualthread/MyVirtualThread.java @@ -0,0 +1,25 @@ +package org.cpq.myvirtualthread; + +import jdk.internal.vm.Continuation; +import jdk.internal.vm.ContinuationScope; + +import java.util.concurrent.atomic.AtomicInteger; + +public class MyVirtualThread { + private static final AtomicInteger COUNTER = new AtomicInteger(1); + public static final ContinuationScope SCOPE = new ContinuationScope("我的虚拟线程"); + + private Continuation cont; + private int id; + + public MyVirtualThread(Runnable runnable) { + id = COUNTER.getAndIncrement(); + cont = new Continuation(SCOPE, runnable); + } + + public void run() { + System.out.println("MyVirtualThread " + id + " is running on" + Thread.currentThread()); + cont.run(); + } + +} diff --git a/project/java21/src/main/java/org/cpq/myvirtualthread/VirtualThreadScheduler.java b/project/java21/src/main/java/org/cpq/myvirtualthread/VirtualThreadScheduler.java new file mode 100644 index 00000000..7e72d10e --- /dev/null +++ b/project/java21/src/main/java/org/cpq/myvirtualthread/VirtualThreadScheduler.java @@ -0,0 +1,31 @@ +package org.cpq.myvirtualthread; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class VirtualThreadScheduler { + + public static ScopedValue MY_VIRTUAL_THREAD = ScopedValue.newInstance(); + + private Queue queue = new ConcurrentLinkedQueue<>(); + private ExecutorService executor = Executors.newFixedThreadPool(10); + + public void start() { + while (true) { + if (!queue.isEmpty()) { + MyVirtualThread virtualThread = queue.remove(); + executor.submit(() -> ScopedValue + .where(MY_VIRTUAL_THREAD, virtualThread) + .run(virtualThread::run) + ); + + } + } + } + + public void schedule(MyVirtualThread virtualThread) { + queue.add(virtualThread); + } +} diff --git a/project/java21/src/main/java/org/cpq/myvirtualthread/WaitingOperation.java b/project/java21/src/main/java/org/cpq/myvirtualthread/WaitingOperation.java new file mode 100644 index 00000000..75039c18 --- /dev/null +++ b/project/java21/src/main/java/org/cpq/myvirtualthread/WaitingOperation.java @@ -0,0 +1,26 @@ +package org.cpq.myvirtualthread; + +import jdk.internal.vm.Continuation; + +import java.util.Timer; +import java.util.TimerTask; + +public class WaitingOperation { + public static void perform(String name, int duration) { + MyVirtualThread myVirtualThread = VirtualThreadScheduler.MY_VIRTUAL_THREAD.get(); + System.out.println("Waiting for "+name+" for "+duration+" milliseconds"); + Timer timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + // 等待duration毫秒后,将虚拟线程重新加入调度器队列,再次运行, + // 再次运行的点是Continuation.yield之后的代码,即WaitingOperation.perform()之后的代码 + Demo.SCHEDULER.schedule(myVirtualThread); + timer.cancel(); + } + }, duration); + + // 虚拟线程进入等待 + Continuation.yield(MyVirtualThread.SCOPE); + } +} diff --git a/project/java21/src/main/java/org/cpq/virtualthread/ContinuationLearn.java b/project/java21/src/main/java/org/cpq/virtualthread/ContinuationLearn.java new file mode 100644 index 00000000..0fad2f97 --- /dev/null +++ b/project/java21/src/main/java/org/cpq/virtualthread/ContinuationLearn.java @@ -0,0 +1,60 @@ +package org.cpq.virtualthread; + +import jdk.internal.vm.Continuation; +import jdk.internal.vm.ContinuationScope; + +public class ContinuationLearn { + + public static void main(String[] args) throws Exception { + // Continuation continuation = getContinuation01(); + // continuation.run(); + + Continuation continuation = getContinuation02(); + continuation.run(); + System.out.println("do some thing after yield A"); + continuation.run(); + System.out.println("do some thing after yield B"); + continuation.run(); + + boolean done = continuation.isDone(); + if (done){ + System.out.println("continuation terminated , you can not run continuation.run()"); + } + } + + + /** + * Continuation是java底层内部api对象, + * 1、需要在idea配置 -> java Compiler -> xxx line parameters 填写: + * --enable-preview --add-exports java.base/jdk.internal.vm=ALL-UNNAMED + * 2、还需要在idea本类配置VM参数 + * --enable-preview + --add-exports java.base/jdk.internal.vm=ALL-UNNAMED + * 才能使用 + */ + private static Continuation getContinuation01() { + var scope = new ContinuationScope("test"); + /** + * Runnable被包装在Continuation中,但Continuation不是线程,用Continuation运行线程,更加节省内存 + */ + var continuation = new Continuation(scope, () -> { + System.out.println("A"); + System.out.println("B"); + System.out.println("C"); + }); + return continuation; + } + + private static Continuation getContinuation02() { + var scope = new ContinuationScope("test"); + var continuation = new Continuation(scope, () -> { + System.out.println("A"); + // 暂停Continuation的Runnable。程序控制权将返回到调用continuation.run()的线程。。 + Continuation.yield(scope); + System.out.println("B"); + Continuation.yield(scope); + System.out.println("C"); + }); + return continuation; + } +} diff --git a/project/java21/src/main/java/org/cpq/virtualthread/VirtualThread.java b/project/java21/src/main/java/org/cpq/virtualthread/VirtualThread.java new file mode 100644 index 00000000..90e8ece9 --- /dev/null +++ b/project/java21/src/main/java/org/cpq/virtualthread/VirtualThread.java @@ -0,0 +1,54 @@ +package org.cpq.virtualthread; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +public class VirtualThread { + + public static void main(String[] args) throws Exception { + /** + * 虚拟线程与平台线程执行速度比较 + */ + // List threadList = IntStream.range(0, 10000).mapToObj(new IntFunction() { + // @Override + // public Thread apply(int value) { + // // 创建虚拟线程 + // return Thread.ofVirtual().unstarted(() -> { + // System.out.println(Thread.currentThread()); + // }); + // // // 创建物理线程 + // // return Thread.ofPlatform().unstarted(() -> { + // // System.out.println(Thread.currentThread()); + // // }); + // } + // }).toList(); + // long begin = System.currentTimeMillis(); + // for (Thread thread : threadList) { + // thread.start(); + // thread.join(); + // } + // long end = System.currentTimeMillis(); + // System.out.println("耗时:" + (end - begin)); + + // 创建虚拟线程的4种方法 + Thread.startVirtualThread(() -> System.out.println("创建虚拟线程1")); + + Thread.ofVirtual().name("虚拟线程2").start(() -> System.out.println("创建虚拟线程2")); + Thread thread21 = Thread.ofVirtual().name("虚拟线程2-1").unstarted(() -> System.out.println("创建虚拟线程2-1")); + thread21.start(); + + ThreadFactory factory = Thread.ofVirtual().factory(); + factory.newThread(() -> System.out.println("通过ThreadFactory创建虚拟线程")).start(); + + try (ExecutorService service = Executors.newVirtualThreadPerTaskExecutor()) { + service.submit(() -> System.out.println("通过ExecutorService创建虚拟线程")); + } catch (Exception e) { + e.printStackTrace(); + } + + TimeUnit.SECONDS.sleep(10); + } + +} diff --git a/spring-cloud-2/app002/.gitignore b/project/java9-17/.gitignore similarity index 72% rename from spring-cloud-2/app002/.gitignore rename to project/java9-17/.gitignore index 153c9335..549e00a2 100644 --- a/spring-cloud-2/app002/.gitignore +++ b/project/java9-17/.gitignore @@ -1,6 +1,8 @@ HELP.md -/target/ +target/ !.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ ### STS ### .apt_generated @@ -23,7 +25,9 @@ HELP.md /dist/ /nbdist/ /.nb-gradle/ -/build/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ ### VS Code ### .vscode/ diff --git a/project/java9-17/pom.xml b/project/java9-17/pom.xml new file mode 100644 index 00000000..27f0b8ea --- /dev/null +++ b/project/java9-17/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.5 + + + com.example + java9-17 + 0.0.1-SNAPSHOT + java9-17 + java9-17 + + 17 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/project/java9-17/src/main/java/com/example/java917/Jdk9_17.java b/project/java9-17/src/main/java/com/example/java917/Jdk9_17.java new file mode 100644 index 00000000..2e114c78 --- /dev/null +++ b/project/java9-17/src/main/java/com/example/java917/Jdk9_17.java @@ -0,0 +1,144 @@ +package com.example.java917; + + +public class Jdk9_17 { + + public static void main(String[] args) throws Exception { + + // HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build(); + // HttpRequest req = HttpRequest.newBuilder(URI.create("https://www.baidu.com")).build(); + // HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofString()); + // System.out.println(resp.body()); + + // var list = new ArrayList(); + // list.add(11122); + // list.add(111); + // Integer i = list.get(0); + // System.out.println(i); + + // var simple = switch (lang) { + // case "java", "scale", "groory" -> "jvm"; + // case "c", "cqp" -> "c"; + // case "go" -> "g"; + // default -> { + // if (lang == null) { + // yield "unknown"; + // } else yield "non"; + // } + // } + + // var block = """ + // lang: java + // version: 13 + // dbname: mysql + // ip: 192.168.140.2 + // """; + // long count = block.lines().count(); + // System.out.println(count); + // + // var b = """ + // lang: java \ + // version: 12 \ + // ip: 22344234 + // """; + // System.out.println(b.lines().count()); + + // Object o = new Random().nextInt() % 2 == 0 ? "java16" : 1000.0d; + // if (o instanceof String s) { + // System.out.println(s.length()); + // } else { + // System.out.println(o); + // } + + + + } +} + +@FunctionalInterface +interface Mapper { + B map(A a); +} + +class MathUtilImpl implements MathUtil { + public static void main(String[] args) throws Exception { + Mapper aa = (var a) -> a.length(); + System.out.println(aa); + } + +} + +interface MathUtil { + // implements 该接口的类将自动获取该方法。 - jdk 8. + default void h(){ + g(); + } + + // 在接口定义的属性直接被视为 static。 -jdk 8. + double Pi = 3.1415; + + // 可以在接口直接定义静态方法。 - jdk 8. + static void f(){} + + // 可以在接口内直接定义私有方法。 -jdk 9. + private void g(){} +} + +@FunctionalInterface +interface Mapper11 { + B map(A a); +} +class JdkMapper11 { + public static void main(String[] args) throws Exception { + Mapper11 string2int = (var a) -> a.length(); + Integer i = string2int.map("sdf sf"); + System.out.println(i); + } +} + +record Student(String name, Integer age) { + + public static void main(String[] args) { + var student = new Student("名字", 11); + System.out.println(student.age); + System.out.println(student.name); + } + +} + + +sealed class People {} + +non-sealed class Teacher extends People{} + +sealed class Driver extends People{} + +non-sealed class TruckDriver extends Driver{} + +sealed class People1 permits Teacher1, Driver1{} +non-sealed class Teacher1 extends People1 {} +non-sealed class Driver1 extends People1{} + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/project/java9-17/src/main/resources/application.properties b/project/java9-17/src/main/resources/application.properties new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/project/java9-17/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/project/java9-module/README.md b/project/java9-module/README.md new file mode 100644 index 00000000..4d67d75d --- /dev/null +++ b/project/java9-module/README.md @@ -0,0 +1,20 @@ +module-a/src/main/java/module-info.java + + module中的包默认是私有的 + 使用exports声明模块的包对其他模块开放,exports只能导出包,不支持导出类。如果对外导出的类少,推荐放到一个包下 + +module-b 导入 maven依赖 + + + org.cpq + module-a + 1.0-SNAPSHOT + +由于module-a只开放了org.cpq.a.a1,所以module-b只能使用org.cpq.a.a1包下的类 +不能使用org.cpq.a.a2包下的类 + +module默认是没有传递性的,module-b必须声明requires transitive org.cpq.a;才能让module-c使用module-a的包 + +module-a使用opens org.cpq.a.reflex;声明reflex包内的类可以反射访问 + +module-a使用exports + provides提供服务。module-b使用uses声明需要使用的服务,module-b使用ServiceLoader loads = ServiceLoader.load(Print.class);获取实现类 diff --git a/project/java9-module/module-a/pom.xml b/project/java9-module/module-a/pom.xml new file mode 100644 index 00000000..1eb20d41 --- /dev/null +++ b/project/java9-module/module-a/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + + org.cpq + module-a + 1.0-SNAPSHOT + jar + + module-a + http://maven.apache.org + + + 21 + 21 + 21 + UTF-8 + + + diff --git a/project/java9-module/module-a/src/main/java/module-info.java b/project/java9-module/module-a/src/main/java/module-info.java new file mode 100644 index 00000000..12dbb6ef --- /dev/null +++ b/project/java9-module/module-a/src/main/java/module-info.java @@ -0,0 +1,21 @@ +module org.cpq.a { + + // exports只能导出一个包,不支持使用,拼接导出多个包 + exports org.cpq.a.a1; + + // to表示向org.cpq.c模块开放org.cpq.a.a2包 + // to 多个模块可以使用,分割 + // exports org.cpq.a.a2 to org.cpq.c; + + // opens导出的包,可以反射访问。 + // 但不能实例化,如果需要实例化,需要配合exports使用 + // open module org.cpq.a {} 表示模块org.cpq.a的所有包都可以被反射访问。 + opens org.cpq.a.reflex; + + // 为接口 Print 提供了两个具体实现类PrintImpl01、PrintImpl02 + // 还需要exports service包 + exports org.cpq.a.service; + provides org.cpq.a.service.Print + with org.cpq.a.impl.PrintImpl01, + org.cpq.a.impl.PrintImpl02; +} \ No newline at end of file diff --git a/project/java9-module/module-a/src/main/java/org/cpq/a/a1/HelloA1.java b/project/java9-module/module-a/src/main/java/org/cpq/a/a1/HelloA1.java new file mode 100644 index 00000000..24729437 --- /dev/null +++ b/project/java9-module/module-a/src/main/java/org/cpq/a/a1/HelloA1.java @@ -0,0 +1,9 @@ +package org.cpq.a.a1; + +public class HelloA1 { + + public void hello(){ + System.out.println("######HelloA1"); + } + +} diff --git a/project/java9-module/module-a/src/main/java/org/cpq/a/a2/HelloA2.java b/project/java9-module/module-a/src/main/java/org/cpq/a/a2/HelloA2.java new file mode 100644 index 00000000..651f39b0 --- /dev/null +++ b/project/java9-module/module-a/src/main/java/org/cpq/a/a2/HelloA2.java @@ -0,0 +1,9 @@ +package org.cpq.a.a2; + +public class HelloA2 { + + public void hello(){ + System.out.println("HelloA2"); + } + +} diff --git a/project/java9-module/module-a/src/main/java/org/cpq/a/impl/PrintImpl01.java b/project/java9-module/module-a/src/main/java/org/cpq/a/impl/PrintImpl01.java new file mode 100644 index 00000000..ec92e1a3 --- /dev/null +++ b/project/java9-module/module-a/src/main/java/org/cpq/a/impl/PrintImpl01.java @@ -0,0 +1,13 @@ +package org.cpq.a.impl; + +import org.cpq.a.service.Print; + +public class PrintImpl01 implements Print { + + // 第一个实现类默认的构造函数是无参构造函数 + @Override + public void printMsg() { + System.out.println("PrintImpl01"); + } + +} diff --git a/project/java9-module/module-a/src/main/java/org/cpq/a/impl/PrintImpl02.java b/project/java9-module/module-a/src/main/java/org/cpq/a/impl/PrintImpl02.java new file mode 100644 index 00000000..ebe7ac45 --- /dev/null +++ b/project/java9-module/module-a/src/main/java/org/cpq/a/impl/PrintImpl02.java @@ -0,0 +1,22 @@ +package org.cpq.a.impl; + +import org.cpq.a.service.Print; + +public class PrintImpl02 implements Print { + + private String name; + + public PrintImpl02(String name) { + this.name = name; + } + + public static Print provider() { + return new PrintImpl02("实现类没有无参构造函数,则规定使用public static Print provider()返回PrintImpl02实例"); + } + + @Override + public void printMsg() { + System.out.println("PrintImpl02 : " + this.name); + } + +} diff --git a/project/java9-module/module-a/src/main/java/org/cpq/a/reflex/Reflex01.java b/project/java9-module/module-a/src/main/java/org/cpq/a/reflex/Reflex01.java new file mode 100644 index 00000000..e17f3d0f --- /dev/null +++ b/project/java9-module/module-a/src/main/java/org/cpq/a/reflex/Reflex01.java @@ -0,0 +1,9 @@ +package org.cpq.a.reflex; + +public class Reflex01 { + + private void reflect(){ + System.out.println("Reflex01.reflect"); + } + +} diff --git a/project/java9-module/module-a/src/main/java/org/cpq/a/service/Print.java b/project/java9-module/module-a/src/main/java/org/cpq/a/service/Print.java new file mode 100644 index 00000000..598a9e22 --- /dev/null +++ b/project/java9-module/module-a/src/main/java/org/cpq/a/service/Print.java @@ -0,0 +1,7 @@ +package org.cpq.a.service; + +public interface Print { + + void printMsg(); + +} diff --git a/project/java9-module/module-b/pom.xml b/project/java9-module/module-b/pom.xml new file mode 100644 index 00000000..56632897 --- /dev/null +++ b/project/java9-module/module-b/pom.xml @@ -0,0 +1,28 @@ + + 4.0.0 + + org.cpq + module-b + 1.0-SNAPSHOT + jar + + module-b + http://maven.apache.org + + + 21 + 21 + 21 + UTF-8 + + + + + org.cpq + module-a + 1.0-SNAPSHOT + + + + diff --git a/project/java9-module/module-b/src/main/java/module-info.java b/project/java9-module/module-b/src/main/java/module-info.java new file mode 100644 index 00000000..99f17a60 --- /dev/null +++ b/project/java9-module/module-b/src/main/java/module-info.java @@ -0,0 +1,8 @@ +module org.cpq.b { + // requires org.cpq.a; + + // transitive 传递依赖,导入了module-b的模块,能使用module-a的模块 + requires transitive org.cpq.a; + + uses org.cpq.a.service.Print; +} \ No newline at end of file diff --git a/project/java9-module/module-b/src/main/java/org/cpq/b/MainB.java b/project/java9-module/module-b/src/main/java/org/cpq/b/MainB.java new file mode 100644 index 00000000..7e3d90f7 --- /dev/null +++ b/project/java9-module/module-b/src/main/java/org/cpq/b/MainB.java @@ -0,0 +1,31 @@ +package org.cpq.b; + +import org.cpq.a.service.Print; + +import java.util.ServiceLoader; + +/** + * Hello world! + * + */ +public class MainB +{ + public static void main( String[] args ) throws Exception + { + // 由于 module org.cpq.a 只开放了 org.cpq.a.a1 ,所以只能使用HelloA1, + // 无法使用org.cpq.a.a2.HelloA2 + // new HelloA1().hello(); + + // Class c = Class.forName("org.cpq.a.reflex.Reflex01"); + // Object instance = c.getDeclaredConstructor().newInstance(); + // Method method = c.getDeclaredMethods()[0]; + // method.setAccessible(true); + // method.invoke(instance, null); + + ServiceLoader loads = ServiceLoader.load(Print.class); + for (Print load : loads) { + load.printMsg(); + } + + } +} diff --git a/project/java9-module/module-b/src/main/java/org/cpq/b/b1/HelloB1.java b/project/java9-module/module-b/src/main/java/org/cpq/b/b1/HelloB1.java new file mode 100644 index 00000000..865d58a5 --- /dev/null +++ b/project/java9-module/module-b/src/main/java/org/cpq/b/b1/HelloB1.java @@ -0,0 +1,9 @@ +package org.cpq.b.b1; + +public class HelloB1 { + + public void hello(){ + System.out.println("HelloBBB1"); + } + +} diff --git a/project/java9-module/module-c/pom.xml b/project/java9-module/module-c/pom.xml new file mode 100644 index 00000000..ba003ac7 --- /dev/null +++ b/project/java9-module/module-c/pom.xml @@ -0,0 +1,33 @@ + + 4.0.0 + + org.cpq + module-c + 1.0-SNAPSHOT + jar + + module-c + http://maven.apache.org + + + 21 + 21 + 21 + UTF-8 + + + + + + + + + + org.cpq + module-b + 1.0-SNAPSHOT + + + + diff --git a/project/java9-module/module-c/src/main/java/module-info.java b/project/java9-module/module-c/src/main/java/module-info.java new file mode 100644 index 00000000..cc4fde18 --- /dev/null +++ b/project/java9-module/module-c/src/main/java/module-info.java @@ -0,0 +1,3 @@ +module org.cpq.c { + requires org.cpq.b; +} \ No newline at end of file diff --git a/project/java9-module/module-c/src/main/java/org/cpq/c/MainC.java b/project/java9-module/module-c/src/main/java/org/cpq/c/MainC.java new file mode 100644 index 00000000..2969fb09 --- /dev/null +++ b/project/java9-module/module-c/src/main/java/org/cpq/c/MainC.java @@ -0,0 +1,19 @@ +package org.cpq.c; + +import org.cpq.a.a1.HelloA1; +// import org.cpq.a.a2.HelloA2; + +/** + * Hello world! + * + */ +public class MainC +{ + public static void main( String[] args ) + { + // module-b使用声明了传递依赖requires transitive org.cpq.a; 在module-c也能使用module-a导出的包 + new HelloA1().hello(); + + // new HelloA2().hello(); + } +} diff --git a/spring-cloud-2/app001/.gitignore b/project/jpa/jpa-01/.gitignore similarity index 100% rename from spring-cloud-2/app001/.gitignore rename to project/jpa/jpa-01/.gitignore diff --git a/spring-cloud-learn/rabbitmq-hello/pom.xml b/project/jpa/jpa-01/pom.xml similarity index 64% rename from spring-cloud-learn/rabbitmq-hello/pom.xml rename to project/jpa/jpa-01/pom.xml index 4810ad26..da683207 100644 --- a/spring-cloud-learn/rabbitmq-hello/pom.xml +++ b/project/jpa/jpa-01/pom.xml @@ -2,43 +2,48 @@ 4.0.0 - - com.cpq - rabbitmq-hello - 0.0.1-SNAPSHOT - jar - - rabbitmq-hello - Demo project for Spring Boot - org.springframework.boot spring-boot-starter-parent - 1.3.7.RELEASE + 2.7.16 + com.cpq + jpa-01 + 0.0.1-SNAPSHOT + jpa-01 + Demo project for Spring Boot - UTF-8 - UTF-8 1.8 - org.springframework.boot - spring-boot-starter-amqp + spring-boot-starter-web org.springframework.boot - spring-boot-starter-test - test + spring-boot-starter-data-jpa + + + mysql + mysql-connector-java + 5.1.46 + runtime + + + + org.projectlombok + lombok + provided + jpa-01 org.springframework.boot @@ -47,5 +52,4 @@ - diff --git a/spring-cloud-learn/config-client/src/main/java/com/cpq/configclient/ConfigClientApplication.java b/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/Jpa01Application.java similarity index 59% rename from spring-cloud-learn/config-client/src/main/java/com/cpq/configclient/ConfigClientApplication.java rename to project/jpa/jpa-01/src/main/java/com/cpq/jpa01/Jpa01Application.java index 5becc18f..fd52c596 100644 --- a/spring-cloud-learn/config-client/src/main/java/com/cpq/configclient/ConfigClientApplication.java +++ b/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/Jpa01Application.java @@ -1,12 +1,13 @@ -package com.cpq.configclient; +package com.cpq.jpa01; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class ConfigClientApplication { +public class Jpa01Application { public static void main(String[] args) { - SpringApplication.run(ConfigClientApplication.class, args); + SpringApplication.run(Jpa01Application.class, args); } + } diff --git a/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/ThreadPoolConfig.java b/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/ThreadPoolConfig.java new file mode 100644 index 00000000..d644ea8a --- /dev/null +++ b/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/ThreadPoolConfig.java @@ -0,0 +1,86 @@ +package com.cpq.jpa01; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + *@ClassName: ThreadPoolConfig + * @author v-xuk19 + * + */ +@Configuration +@EnableAsync +public class ThreadPoolConfig { + + @Bean + public TaskExecutor taskExecutor() { + //ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + MdcThreadPoolTaskExecutor executor = new MdcThreadPoolTaskExecutor(); + // 设置核心线程数 + executor.setCorePoolSize(8); + // 设置最大线程数 + executor.setMaxPoolSize(100); + // 设置队列容量 + executor.setQueueCapacity(30); + // 设置线程活跃时间(秒) + executor.setKeepAliveSeconds(300); + // 设置默认线程名称 + executor.setThreadNamePrefix("thread-obs"); + // 设置拒绝策略rejection-policy:当pool已经达到max size的时候,如何处理新任务 CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } + + /** + * 异步线程池submit时,传递父线程的trackId + * + */ + @Slf4j + public static class MdcThreadPoolTaskExecutor extends ThreadPoolTaskExecutor{ + + @Override + public Future submit(Callable task) { + String trackIdKey = "mdc_key"; + Map context = MDC.getCopyOfContextMap(); + // 将RequestAttributes对象设置为子线程共享 + ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + RequestContextHolder.setRequestAttributes(sra, true); + return super.submit(() -> { + // 将父线程的MDC内容传给子线程 + T result = null; + if (context != null && !StringUtils.isEmpty(context.get(trackIdKey))) { + MDC.setContextMap(context); + } else { + MDC.put(trackIdKey, UUID.randomUUID().toString().replace("-", "")); + } + try { + result = task.call(); + } finally { + try { + MDC.clear(); + } catch (Exception e2) { + log.warn("mdc clear exception.", e2); + } + } + return result; + }); + } + + } + + +} diff --git a/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/controller/Test01Controller.java b/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/controller/Test01Controller.java new file mode 100644 index 00000000..fbc9448c --- /dev/null +++ b/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/controller/Test01Controller.java @@ -0,0 +1,45 @@ +package com.cpq.jpa01.controller; + +import com.cpq.jpa01.dao.Test01Dao; +import com.cpq.jpa01.model.Test01; +import com.cpq.jpa01.service.Test01Service; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.task.TaskExecutor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author chenpq05 + * @since 2023/12/4 13:49 + */ +@Slf4j +@RestController +@RequestMapping("/test01") +public class Test01Controller { + + @Autowired + private Test01Service test01Service; + @Autowired + private Test01Dao test01Dao; + @Autowired + private TaskExecutor taskExecutor; + + + @PostMapping("/add") + public Test01 add(@RequestBody Test01 saveDTO) { + Test01 dbSave = test01Service.save(saveDTO); + return dbSave; + } + + @PostMapping("/test") + public Object test(@RequestBody Test01 test01) { + taskExecutor.execute(() -> { + test01Service.test(test01); + }); + return "page"; + } + +} diff --git a/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/dao/Test01Dao.java b/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/dao/Test01Dao.java new file mode 100644 index 00000000..5de53d62 --- /dev/null +++ b/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/dao/Test01Dao.java @@ -0,0 +1,28 @@ +package com.cpq.jpa01.dao; + +import com.cpq.jpa01.model.Test01; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * @author chenpq05 + * @since 2023/12/4 13:44 + */ +public interface Test01Dao extends JpaRepository { + + + + Logger LOGGER = LoggerFactory.getLogger(Test01Dao.class); + + List findByCompanyCodeInAndCompanyNameLike(List companyCode, String companyName); + + default int getMy() { + Test01 dbOne = this.getById(1L); + LOGGER.info("^^^^^^^^^^^^^^^^^^^^^^{}", dbOne); + return 1; + } + +} diff --git a/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/model/Test01.java b/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/model/Test01.java new file mode 100644 index 00000000..9ec9cd4e --- /dev/null +++ b/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/model/Test01.java @@ -0,0 +1,55 @@ +package com.cpq.jpa01.model; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +/** + *

+ * 业务-公司 + *

+ * + * @author chenpq05 + * @since 2023-05-31 15:57:44 + */ +@Data +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor +@Entity +@DynamicUpdate +@DynamicInsert +public class Test01 implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String companyCode; + + private String companyName; + + private Integer peopleNumber; + + private Double area; + + private LocalDateTime createDate; + + private Integer isDel; + + private BigDecimal actualCapital; + +} diff --git a/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/service/Test01Service.java b/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/service/Test01Service.java new file mode 100644 index 00000000..60067141 --- /dev/null +++ b/project/jpa/jpa-01/src/main/java/com/cpq/jpa01/service/Test01Service.java @@ -0,0 +1,63 @@ +package com.cpq.jpa01.service; + +import com.cpq.jpa01.dao.Test01Dao; +import com.cpq.jpa01.model.Test01; +import java.util.Arrays; +import java.util.List; +import javax.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.stereotype.Service; + +/** + *

+ * 业务-公司 + *

+ * + * @author chenpq05 + * @since 2023-05-31 15:57:44 + */ +@Service +@Slf4j +public class Test01Service { + @Autowired + private Test01Dao test01Dao; + + @Transactional(rollbackOn = Exception.class) + public Test01 save(Test01 test01) { + Test01 dbSave = test01Dao.save(test01); + log.info("######{}", dbSave); + return dbSave; + } + + @Transactional + public void test(Test01 test01) { + int my = test01Dao.getMy(); + log.info("##############{}", my); + + List all = test01Dao.findAll(); + log.info("#############all={}", all); + + List list = test01Dao.findByCompanyCodeInAndCompanyNameLike( + Arrays.asList("7112336ssfdf3334354545", "711233633646dfs3474688"), test01.getCompanyName()); + log.info("#############list={}", list); + + Test01 db = test01Dao.getById(test01.getId()); + + test01Dao.save(test01); + log.info("#############{}", test01); + + // page从0开始,跟limit一样 + PageRequest pageRequest = PageRequest.of(1, 2, Sort.by(Direction.DESC, "id")); + Example test01Example = Example.of(test01); + Page page = test01Dao.findAll(test01Example, pageRequest); + + + } + +} diff --git a/project/jpa/jpa-01/src/main/resources/application.properties b/project/jpa/jpa-01/src/main/resources/application.properties new file mode 100644 index 00000000..646ffdc2 --- /dev/null +++ b/project/jpa/jpa-01/src/main/resources/application.properties @@ -0,0 +1,11 @@ +spring.application.name=jpa-01 +server.port=9999 + +spring.datasource.url= jdbc:mysql://10.39.174.41:3306/cpq_01?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false +spring.datasource.username=root +spring.datasource.password=123456 +spring.datasource.driver-class-name = com.mysql.jdbc.Driver + +#显示 sql 语句 +spring.jpa.show-sql=true +#spring.jpa.open-in-view=true diff --git a/project/jvm/pom.xml b/project/jvm/pom.xml new file mode 100644 index 00000000..43e05763 --- /dev/null +++ b/project/jvm/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + org.cpqjvm + jvm + 1.0-SNAPSHOT + jar + + jvm + http://maven.apache.org + + + UTF-8 + + + + + junit + junit + 3.8.1 + test + + + diff --git a/project/jvm/src/main/java/org/cpqjvm/g1/Demo01.java b/project/jvm/src/main/java/org/cpqjvm/g1/Demo01.java new file mode 100644 index 00000000..d1df1fad --- /dev/null +++ b/project/jvm/src/main/java/org/cpqjvm/g1/Demo01.java @@ -0,0 +1,111 @@ +package org.cpqjvm.g1; + +public class Demo01 { + + /** + * G1最大的特征:将堆分为若干小区域,每个小区域称为region + * 如图片:G1垃圾收集与传统垃圾收集对比.jpg + * + * G1对象管理: + * 新生代对象分配在新生代Eden区 + * 运行一段时间后启动YoungGC,将Eden区中的对象复制到Survivor区 + * 继续运行一段时间后Eden区再次被填满,Eden区和Survivor区对象复制到一个新的Survivor区 + * 如果老的Survivor区的对象复制次数超过规定值,直接将对象复制到Old区 + * 如图片:G1对象管理过程.jpg + * 如果一个对象大于等于分区的一半,则直接将对象放到H区(Humongous区) + * Eden区、Survivor区、Old区、H区的内存地址是动态变化的,一个region为Eden区,Eden区清空后,后面可能会变成Survivor区、Old区、H区 + * + */ + + /** + * 配置VM参数再运行 -Xmx128M -XX:+UseG1GC -Xlog:gc* + * 最后输出 region size 1024K,说明G1GC默认的region大小为1024K + */ + // public static void main(String[] args) { + // byte[] data = new byte[1024 * 256]; + // for (int i = 0; i < 1000; i++) { + // data = new byte[1024 * 256]; + // } + // + // } + + // /** + // * region的个数和大小都是动态可变的 + // * G1默认region最大个数为2048个,region大小的取值范围是2的指数倍:1、2、4、8、16、32M + // * region最大值 = max( (Xmx + Xms) 除以2再除以2048 , 1MB) + // * 配置-Xmx4096M -Xms4096M,通过公式得出region size为2048K + // * 配置VM参数再运行main方法 -Xmx4096M -Xms4096M -XX:+UseG1GC -Xlog:gc* + // * 最后输出 region size 2048K + // * + // * 在启动时设置region的大小会影响JVM调优, + // * 不建议手动指定region的大小,而应该通过调整Xmx和Xms让JVM自动调整region的大小 + // * + // * G1只有YoungGC、MixedGC和FullGC + // * YoungGC:只回收新生代区域,代价低/频率高 + // * MixGC:回收新生代 + 部分老年代,频率一般 + // * FullGC:回收堆空间,代价高/频率低 + // */ + // public static void main(String[] args) { + // byte[] data = new byte[1024 * 256]; + // for (int i = 0; i < 1000; i++) { + // data = new byte[1024 * 256]; + // } + // + // } + + /** + * YGC的基本过程: + * 1、从GC roots出发标记存活对象。GC roots有哪些:栈对象、静态变量、运行时常量池、程序计数器、锁 + * 2、复制对象到S区,复制过程是最慢的 + * 3、释放垃圾集合,回收region + * 4、动态调整(增加或减少)新生代region数量 + * 4、判断是否开启并发标记,为下一步执行混合回收做准备。如果堆空间使用率超过45%则会进行混合回收 + * + * Eden区、Survivor区采用标记-复制算法。标记和复制过程是同时进行的,找到一个存活对象就复制一个 + * + * -XX:MaxGCPauseMillis 用于配置GC停顿时间 + * -Xmx128M -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -Xlog:gc* + */ + // public static void main(String[] args) { + // byte[] data = new byte[1024 * 256]; + // for (int i = 0; i < 100; i++) { + // data = new byte[1024 * 256]; + // } + // + // } + + /** + * 默认情况下,YGC之后,已分配内存超过内存总容量的45%会触发混合回收MixGC + * 混合回收基本步骤: + * 第一步:初始标记阶段。标记所有由GCRoot等直接引用的对象,会暂停用户程序的运行 + * 第二步:并发标记。标记上一步中标记的所有引用对象,执行时间略长,不会暂停用户程序的运行,不会STW + * 第三步:再标记阶段。标记出上一个阶段没有被标记的对象,执行速度非常快,会STW + * 第四步:存活对象计数阶段。统计出每个region存活对象的数量 + * 第五步:垃圾回收阶段。选择回收价值高的区域,把存活对象复制到新分区,然后回收老分区 + * + * 图片:G1混合回收完整过程.jpg + * + * YGC是MixedGC的前奏,YGC完成,就代表mixedGC已经走完了初始标记阶段,YGC已经帮MixedGC完成了初始化的活 + */ + // 触发混合回收 + // public static List list = new ArrayList<>(); + // public static void main(String[] args) throws Exception{ + // while (true) { + // byte[] data = new byte[1024 * 256]; + // for (int i = 0; i < 50; i++) { + // data = new byte[1024 * 256]; + // list.add(data); + // } + // TimeUnit.SECONDS.sleep(1L); + // } + // } + + /** + * 老年代的空间已经不足以放下新对象,会触发FullGC + * FullGC可能执行两次,第二次是回收软引用,如果堆空间还是不足以存放新对象,会触发OOM + * YGC、MixedGC采用标记-复制算法。 + * FullGC采用标记-压缩算法,FullGC是没有堆空间可用了才触发的,因为没有可用的region了,就不能采用复制算法了 + */ + + +} diff --git "a/project/jvm/src/main/java/org/cpqjvm/g1/G1\345\236\203\345\234\276\346\224\266\351\233\206\344\270\216\344\274\240\347\273\237\345\236\203\345\234\276\346\224\266\351\233\206\345\257\271\346\257\224.jpg" "b/project/jvm/src/main/java/org/cpqjvm/g1/G1\345\236\203\345\234\276\346\224\266\351\233\206\344\270\216\344\274\240\347\273\237\345\236\203\345\234\276\346\224\266\351\233\206\345\257\271\346\257\224.jpg" new file mode 100644 index 00000000..81e621df Binary files /dev/null and "b/project/jvm/src/main/java/org/cpqjvm/g1/G1\345\236\203\345\234\276\346\224\266\351\233\206\344\270\216\344\274\240\347\273\237\345\236\203\345\234\276\346\224\266\351\233\206\345\257\271\346\257\224.jpg" differ diff --git "a/project/jvm/src/main/java/org/cpqjvm/g1/G1\345\257\271\350\261\241\347\256\241\347\220\206\350\277\207\347\250\213.jpg" "b/project/jvm/src/main/java/org/cpqjvm/g1/G1\345\257\271\350\261\241\347\256\241\347\220\206\350\277\207\347\250\213.jpg" new file mode 100644 index 00000000..42210754 Binary files /dev/null and "b/project/jvm/src/main/java/org/cpqjvm/g1/G1\345\257\271\350\261\241\347\256\241\347\220\206\350\277\207\347\250\213.jpg" differ diff --git "a/project/jvm/src/main/java/org/cpqjvm/g1/G1\346\267\267\345\220\210\345\233\236\346\224\266\345\256\214\346\225\264\350\277\207\347\250\213.jpg" "b/project/jvm/src/main/java/org/cpqjvm/g1/G1\346\267\267\345\220\210\345\233\236\346\224\266\345\256\214\346\225\264\350\277\207\347\250\213.jpg" new file mode 100644 index 00000000..50ee4303 Binary files /dev/null and "b/project/jvm/src/main/java/org/cpqjvm/g1/G1\346\267\267\345\220\210\345\233\236\346\224\266\345\256\214\346\225\264\350\277\207\347\250\213.jpg" differ diff --git a/project/jvm/src/main/java/org/cpqjvm/zgc/ZGC.java b/project/jvm/src/main/java/org/cpqjvm/zgc/ZGC.java new file mode 100644 index 00000000..fa479c55 --- /dev/null +++ b/project/jvm/src/main/java/org/cpqjvm/zgc/ZGC.java @@ -0,0 +1,42 @@ +package org.cpqjvm.zgc; + +public class ZGC { + + /** + * ZGC的特征: + * 1、停顿时间(STW)不超过10ms,JDK16不超过1ms,且不会随着堆的大小增加而增加 + * 2、理论上最大支持16TB的大堆,最小支持8MB的小堆 + * 3、跟G1相比,对应用程序的吞吐量的影响小于15% + * + * ZGC的堆内存采用分页模型,分为:小页面、中页面、大页面。图片:ZGC堆空间分页模型.jpg + * Linux2.6引入了标准大页,默认是2M,所以ZGC队内存的小页面默认是2M + * + * ZGC的指针着色技术 + * ZGC只支持64位系统,使用低42表示使用的堆空间地址,借高几位来做GC相关的事情 + * 图片:指针着色.jpg,42~45位使用不同的颜色表示Java对象在GC时的状态 + * 图片:指针着色2使用44表示对象地址.jpg,45~48位使用不同的颜色表示Java对象在GC时的状态 + * + * ZGC分为标记阶段、转移阶段。 + * 图片:ZGC的过程.jpg + * 图片:标记前的状态.jpg。新创建的对象使用蓝色指针表示。 + * 1、初始标记会STW,只会标记GCRoot,停顿时间非常短 + * 2、并发标记,从GCRoot开始,找打被引用的对象,此阶段与业务线程同时运行 + * 图片:初始标记和再标记.jpg。本次GC,将蓝色的被引用的对象标记为绿色 + * 下一次垃圾回收的并发标记阶段,将绿色的对象标记为红色 + * 3、再标记阶段,会STW,标记漏标的对象 + * 重新标记并发标记阶段产生的新对象,会STW + * 4、并发转移准备。计算region的垃圾占比,以便确定回收哪个region收益最高 + * 5、初始转移。 + * 图片:初始转移.jpg。把GCRoot转移走,例如把图中的A转移到新region + * 初始转移后,绿色的A对象指针会还原为蓝色 + * 6、并发转移 + * 图片:并发转移时候使用转发表.jpg。 + * GCRoot关联的对象,在并发转移阶段迁移,迁移过程中会将对象的老地址、新地址记录到转发表中。 + * 在转发表里的对象是程序通过转发表里面的新地址,就可以找到对象了。 + * 下一次垃圾回收的并发标记阶段,发现指针式绿色,则将把对象地址设置为转发表的新地址。并删除转发表中的记录 + * + * + * 图片:标记阶段.jpg + */ + +} diff --git "a/project/jvm/src/main/java/org/cpqjvm/zgc/ZGC\345\240\206\347\251\272\351\227\264\345\210\206\351\241\265\346\250\241\345\236\213.jpg" "b/project/jvm/src/main/java/org/cpqjvm/zgc/ZGC\345\240\206\347\251\272\351\227\264\345\210\206\351\241\265\346\250\241\345\236\213.jpg" new file mode 100644 index 00000000..9ad08882 Binary files /dev/null and "b/project/jvm/src/main/java/org/cpqjvm/zgc/ZGC\345\240\206\347\251\272\351\227\264\345\210\206\351\241\265\346\250\241\345\236\213.jpg" differ diff --git "a/project/jvm/src/main/java/org/cpqjvm/zgc/ZGC\347\232\204\350\277\207\347\250\213.jpg" "b/project/jvm/src/main/java/org/cpqjvm/zgc/ZGC\347\232\204\350\277\207\347\250\213.jpg" new file mode 100644 index 00000000..1644d681 Binary files /dev/null and "b/project/jvm/src/main/java/org/cpqjvm/zgc/ZGC\347\232\204\350\277\207\347\250\213.jpg" differ diff --git "a/project/jvm/src/main/java/org/cpqjvm/zgc/\345\210\235\345\247\213\346\240\207\350\256\260\345\222\214\345\206\215\346\240\207\350\256\260.jpg" "b/project/jvm/src/main/java/org/cpqjvm/zgc/\345\210\235\345\247\213\346\240\207\350\256\260\345\222\214\345\206\215\346\240\207\350\256\260.jpg" new file mode 100644 index 00000000..f0f5aa78 Binary files /dev/null and "b/project/jvm/src/main/java/org/cpqjvm/zgc/\345\210\235\345\247\213\346\240\207\350\256\260\345\222\214\345\206\215\346\240\207\350\256\260.jpg" differ diff --git "a/project/jvm/src/main/java/org/cpqjvm/zgc/\345\210\235\345\247\213\350\275\254\347\247\273.jpg" "b/project/jvm/src/main/java/org/cpqjvm/zgc/\345\210\235\345\247\213\350\275\254\347\247\273.jpg" new file mode 100644 index 00000000..272be951 Binary files /dev/null and "b/project/jvm/src/main/java/org/cpqjvm/zgc/\345\210\235\345\247\213\350\275\254\347\247\273.jpg" differ diff --git "a/project/jvm/src/main/java/org/cpqjvm/zgc/\345\271\266\345\217\221\350\275\254\347\247\273\346\227\266\345\200\231\344\275\277\347\224\250\350\275\254\345\217\221\350\241\250.jpg" "b/project/jvm/src/main/java/org/cpqjvm/zgc/\345\271\266\345\217\221\350\275\254\347\247\273\346\227\266\345\200\231\344\275\277\347\224\250\350\275\254\345\217\221\350\241\250.jpg" new file mode 100644 index 00000000..54d2c35a Binary files /dev/null and "b/project/jvm/src/main/java/org/cpqjvm/zgc/\345\271\266\345\217\221\350\275\254\347\247\273\346\227\266\345\200\231\344\275\277\347\224\250\350\275\254\345\217\221\350\241\250.jpg" differ diff --git "a/project/jvm/src/main/java/org/cpqjvm/zgc/\346\214\207\351\222\210\347\235\200\350\211\262.jpg" "b/project/jvm/src/main/java/org/cpqjvm/zgc/\346\214\207\351\222\210\347\235\200\350\211\262.jpg" new file mode 100644 index 00000000..a318eba7 Binary files /dev/null and "b/project/jvm/src/main/java/org/cpqjvm/zgc/\346\214\207\351\222\210\347\235\200\350\211\262.jpg" differ diff --git "a/project/jvm/src/main/java/org/cpqjvm/zgc/\346\214\207\351\222\210\347\235\200\350\211\2622\344\275\277\347\224\25044\350\241\250\347\244\272\345\257\271\350\261\241\345\234\260\345\235\200.jpg" "b/project/jvm/src/main/java/org/cpqjvm/zgc/\346\214\207\351\222\210\347\235\200\350\211\2622\344\275\277\347\224\25044\350\241\250\347\244\272\345\257\271\350\261\241\345\234\260\345\235\200.jpg" new file mode 100644 index 00000000..f8c4171b Binary files /dev/null and "b/project/jvm/src/main/java/org/cpqjvm/zgc/\346\214\207\351\222\210\347\235\200\350\211\2622\344\275\277\347\224\25044\350\241\250\347\244\272\345\257\271\350\261\241\345\234\260\345\235\200.jpg" differ diff --git "a/project/jvm/src/main/java/org/cpqjvm/zgc/\346\240\207\350\256\260\345\211\215\347\232\204\347\212\266\346\200\201.jpg" "b/project/jvm/src/main/java/org/cpqjvm/zgc/\346\240\207\350\256\260\345\211\215\347\232\204\347\212\266\346\200\201.jpg" new file mode 100644 index 00000000..5b335536 Binary files /dev/null and "b/project/jvm/src/main/java/org/cpqjvm/zgc/\346\240\207\350\256\260\345\211\215\347\232\204\347\212\266\346\200\201.jpg" differ diff --git "a/project/jvm/src/main/java/org/cpqjvm/zgc/\346\240\207\350\256\260\351\230\266\346\256\265.jpg" "b/project/jvm/src/main/java/org/cpqjvm/zgc/\346\240\207\350\256\260\351\230\266\346\256\265.jpg" new file mode 100644 index 00000000..96bc4689 Binary files /dev/null and "b/project/jvm/src/main/java/org/cpqjvm/zgc/\346\240\207\350\256\260\351\230\266\346\256\265.jpg" differ diff --git a/project/kafka/README.md b/project/kafka/README.md new file mode 100644 index 00000000..bc461976 --- /dev/null +++ b/project/kafka/README.md @@ -0,0 +1,23 @@ +下载kafka_2.13-3.6.1 +https://downloads.apache.org/kafka/3.6.1/kafka_2.13-3.6.1.tgz + +kafka_2.13-3.6.1\config\zookeeper.properties 修改 +maxClientCnxns=10000 + +kafka_2.13-3.6.1\config\server.properties 修改 +listeners=PLAINTEXT://:9092 +zookeeper.connect=127.0.0.1:2181 + +新建 kafka_2.13-3.6.1\zookerper-start.bat +.\bin\windows\zookeeper-server-start.bat .\config\zookeeper.properties + +新建 kafka_2.13-3.6.1\kafka-start.bat +.\bin\windows\kafka-server-start.bat .\config\server.properties + +启动zookerper-start.bat、kafka-start.bat + +进入:kafka_2.13-3.6.1\bin\windows> +创建topic +.\kafka-topics.bat --bootstrap-server 127.0.0.1:9092 --create --partitions 1 --replication-factor 1 --topic first + + diff --git a/project/kafka/kafka01/pom.xml b/project/kafka/kafka01/pom.xml index 77186338..de46cb9d 100644 --- a/project/kafka/kafka01/pom.xml +++ b/project/kafka/kafka01/pom.xml @@ -25,6 +25,10 @@ org.springframework.kafka spring-kafka + + org.apache.kafka + kafka-streams + org.projectlombok diff --git a/project/kafka/kafka01/src/main/java/com/example/kafka01/a_simple/SimpleConsumer.java b/project/kafka/kafka01/src/main/java/com/example/kafka01/a_simple/SimpleConsumer.java new file mode 100644 index 00000000..0338b333 --- /dev/null +++ b/project/kafka/kafka01/src/main/java/com/example/kafka01/a_simple/SimpleConsumer.java @@ -0,0 +1,55 @@ +package com.example.kafka01.a_simple; + +import java.util.Arrays; +import java.util.Properties; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author chenpq05 + * @since 2024/2/2 14:00 + */ +public class SimpleConsumer { + + public static void main(String[] args) throws Exception { + + // 设置日志级别 + Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(Level.INFO); + + String topicName = "first"; + Properties props = new Properties(); + + props.put("bootstrap.servers", "localhost:9092"); + props.put("group.id", "test"); + props.put("enable.auto.commit", "true"); + props.put("auto.commit.interval.ms", "1000"); + props.put("session.timeout.ms", "30000"); + props.put("key.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + props.put("value.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + KafkaConsumer consumer = new KafkaConsumer(props); + // Kafka Consumer subscribes list of topics here. + consumer.subscribe(Arrays.asList(topicName)); + + // print the topic name + System.out.println("Subscribed to topic " + topicName); + int i = 0; + + while (true) { + ConsumerRecords records = consumer.poll(100); + for (ConsumerRecord record : records) { + // print the offset,key and value for the consumer records. + System.out.printf("offset = %d, key = %s, value = %s\n", + record.offset(), record.key(), record.value()); + } + } + } + +} diff --git a/project/kafka/kafka01/src/main/java/com/example/kafka01/a_simple/SimpleGroupConsumer.java b/project/kafka/kafka01/src/main/java/com/example/kafka01/a_simple/SimpleGroupConsumer.java new file mode 100644 index 00000000..1b7b16e6 --- /dev/null +++ b/project/kafka/kafka01/src/main/java/com/example/kafka01/a_simple/SimpleGroupConsumer.java @@ -0,0 +1,42 @@ +package com.example.kafka01.a_simple; + +import java.util.Arrays; +import java.util.Properties; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; + +/** + * @author chenpq05 + * @since 2024/2/2 14:00 + */ +public class SimpleGroupConsumer { + + public static void main(String[] args) throws Exception { + String topic = "first-p"; + String group = "my-group"; + Properties props = new Properties(); + props.put("bootstrap.servers", "localhost:9092"); + props.put("group.id", group); + props.put("enable.auto.commit", "true"); + props.put("auto.commit.interval.ms", "1000"); + props.put("session.timeout.ms", "30000"); + props.put("key.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + props.put("value.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + KafkaConsumer consumer = new KafkaConsumer(props); + + consumer.subscribe(Arrays.asList(topic)); + System.out.println("Subscribed to topic " + topic); + int i = 0; + + while (true) { + ConsumerRecords records = consumer.poll(100); + for (ConsumerRecord record : records) + System.out.printf("offset = %d, key = %s, value = %s\n", + record.offset(), record.key(), record.value()); + } + } + +} diff --git a/project/kafka/kafka01/src/main/java/com/example/kafka01/a_simple/SimpleProducer.java b/project/kafka/kafka01/src/main/java/com/example/kafka01/a_simple/SimpleProducer.java new file mode 100644 index 00000000..36e46059 --- /dev/null +++ b/project/kafka/kafka01/src/main/java/com/example/kafka01/a_simple/SimpleProducer.java @@ -0,0 +1,54 @@ +package com.example.kafka01.a_simple; + +import java.util.Properties; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.Producer; +import org.apache.kafka.clients.producer.ProducerRecord; + +/** + * @author chenpq05 + * @since 2024/2/1 16:31 + */ +public class SimpleProducer { + + public static void main(String[] args) { + // create instance for properties to access producer configs + Properties props = new Properties(); + + //Assign localhost id + props.put("bootstrap.servers", "localhost:9092"); + + //Set acknowledgements for producer requests. + props.put("acks", "all"); + + //If the request fails, the producer can automatically retry, + props.put("retries", 0); + + //Specify buffer size in config + props.put("batch.size", 16384); + + //Reduce the no of requests less than 0 + props.put("linger.ms", 1); + + //The buffer.memory controls the total amount of memory available to the producer for buffering. + props.put("buffer.memory", 33554432); + + props.put("key.serializer", + "org.apache.kafka.common.serialization.StringSerializer"); + + props.put("value.serializer", + "org.apache.kafka.common.serialization.StringSerializer"); + + Producer producer = new KafkaProducer + (props); + + for(int i = 0; i < 10; i++) { + producer.send(new ProducerRecord("first", Integer.toString(i), Integer.toString(i))); + } + + producer.flush(); + System.out.println("Message sent successfully"); + producer.close(); + } + +} diff --git a/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderCommitSync02OffsetConsumer.java b/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderCommitSync02OffsetConsumer.java new file mode 100644 index 00000000..405964a8 --- /dev/null +++ b/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderCommitSync02OffsetConsumer.java @@ -0,0 +1,53 @@ +package com.example.kafka01.b_commit; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.clients.consumer.OffsetAndMetadata; +import org.apache.kafka.common.TopicPartition; +import org.slf4j.LoggerFactory; + +/** + * @author chenpq05 + * @since 2024/2/19 13:53 + */ +public class OrderCommitSync02OffsetConsumer { + public static void main(String[] args) { + // 设置日志级别 + Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(Level.INFO); + + Properties props = new Properties(); + props.put("bootstrap.servers", "127.0.0.1:9092"); + props.put("group.id", "test"); + //关闭自动提交确认选项 + props.put("enable.auto.commit", "false"); + props.put("key.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + props.put("value.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + KafkaConsumer consumer = new KafkaConsumer<>(props); + consumer.subscribe(Arrays.asList("order-p")); + try { + while(true) { + ConsumerRecords records = consumer.poll(100); + for (TopicPartition partition : records.partitions()) { + List> partitionRecords = records.records(partition); + for (ConsumerRecord record : partitionRecords) { + System.out.println("消费:" + record.offset() + " : " + record.value()); + } + long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset(); + consumer.commitSync(Collections.singletonMap(partition, new OffsetAndMetadata(lastOffset + 1))); + } + } + } finally { + consumer.close(); + } + } +} diff --git a/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderCommitSyncConsumer.java b/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderCommitSyncConsumer.java new file mode 100644 index 00000000..0c1ec72b --- /dev/null +++ b/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderCommitSyncConsumer.java @@ -0,0 +1,50 @@ +package com.example.kafka01.b_commit; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.slf4j.LoggerFactory; + +/** + * @author chenpq05 + * @since 2024/2/19 13:53 + */ +public class OrderCommitSyncConsumer { + public static void main(String[] args) { + // 设置日志级别 + Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(Level.INFO); + + Properties props = new Properties(); + props.put("bootstrap.servers", "127.0.0.1:9092"); + props.put("group.id", "test"); +//关闭自动提交确认选项 + props.put("enable.auto.commit", "false"); + props.put("key.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + props.put("value.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + KafkaConsumer consumer = new KafkaConsumer<>(props); + consumer.subscribe(Arrays.asList("order")); + final int minBatchSize = 6; + List> buffer = new ArrayList<>(); + while (true) { + ConsumerRecords records = consumer.poll(100); + for (ConsumerRecord record : records) { + buffer.add(record); + } + if (buffer.size() >= minBatchSize) { + buffer.forEach(e -> System.out.println("消费:"+e.value())); + // 手动提交offset值 + consumer.commitSync(); + buffer.clear(); + } + } + } +} diff --git a/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderConsumer.java b/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderConsumer.java new file mode 100644 index 00000000..c0929bd0 --- /dev/null +++ b/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderConsumer.java @@ -0,0 +1,41 @@ +package com.example.kafka01.b_commit; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import java.util.Arrays; +import java.util.Properties; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.slf4j.LoggerFactory; + +/** + * @author chenpq05 + * @since 2024/2/19 13:53 + */ +public class OrderConsumer { + public static void main(String[] args) { + // 设置日志级别 + Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(Level.INFO); + // 1、连接集群 + Properties props = new Properties(); + props.put("bootstrap.servers", "127.0.0.1:9092"); + props.put("group.id", "test2"); + //以下两行代码 ---消费者自动提交offset值 + props.put("enable.auto.commit", "true"); + props.put("auto.commit.interval.ms", "1000"); + props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + KafkaConsumer kafkaConsumer = new KafkaConsumer<>(props); + // 2、发送数据 发送数据需要,订阅下要消费的topic:order + kafkaConsumer.subscribe(Arrays.asList("order-p")); + while (true) { + // jdk queue offer插入、poll获取元素。 blockingqueue put插入原生, take获取元素 + ConsumerRecords consumerRecords = kafkaConsumer.poll(100); + for (ConsumerRecord record : consumerRecords) { + System.out.println("消费的数据为:" + record.value()); + } + } + } +} diff --git a/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderPartitionConsumer.java b/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderPartitionConsumer.java new file mode 100644 index 00000000..6345ca35 --- /dev/null +++ b/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderPartitionConsumer.java @@ -0,0 +1,51 @@ +package com.example.kafka01.b_commit; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import java.util.Arrays; +import java.util.Properties; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.common.TopicPartition; +import org.slf4j.LoggerFactory; + +/** + * @author chenpq05 + * @since 2024/2/19 13:53 + */ +public class OrderPartitionConsumer { + public static void main(String[] args) { + // 设置日志级别 + Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(Level.INFO); + + Properties props = new Properties(); + props.put("bootstrap.servers", "localhost:9092"); + props.put("group.id", "test"); + props.put("enable.auto.commit", "true"); + props.put("auto.commit.interval.ms", "1000"); + props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + KafkaConsumer consumer = new KafkaConsumer<>(props); + + + //手动指定消费指定分区的数据---start + String topic = "order"; + TopicPartition partition0 = new TopicPartition(topic, 0); + TopicPartition partition1 = new TopicPartition(topic, 1); + consumer.assign(Arrays.asList(partition0, partition1)); + + //要使用此模式,只需使用要使用的分区的完整列表调用assign(Collection),而不是使用subscribe订阅主题。 + //主题与分区订阅只能二选一。 + //consumer.subscribe(Arrays.asList("order")); + + //手动指定消费指定分区的数据---end + while (true) { + ConsumerRecords records = consumer.poll(100); + for (ConsumerRecord record : records) { + System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value()); + } + } + } +} diff --git a/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderProducer.java b/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderProducer.java new file mode 100644 index 00000000..eca0ecec --- /dev/null +++ b/project/kafka/kafka01/src/main/java/com/example/kafka01/b_commit/OrderProducer.java @@ -0,0 +1,61 @@ +package com.example.kafka01.b_commit; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import java.util.Properties; +import java.util.concurrent.Future; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.clients.producer.RecordMetadata; +import org.slf4j.LoggerFactory; + +/** + * @author chenpq05 + * @since 2024/2/19 13:42 + */ +public class OrderProducer { + public static void main(String[] args) throws InterruptedException { + // 设置日志级别 + Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(Level.INFO); + /* 1、连接集群,通过配置文件的方式 + * 2、发送数据-topic:order,value + */ + Properties props = new Properties(); + props.put("bootstrap.servers", "127.0.0.1:9092"); + props.put("acks", "all"); + props.put("retries", 0); + props.put("batch.size", 16384); + props.put("linger.ms", 1); + props.put("buffer.memory", 33554432); + props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); + props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); + KafkaProducer kafkaProducer = new KafkaProducer<>(props); + for (int i = 0; i < 30; i++) { + + // 发送数据,需要一个producerRecord对象,最少参数 String topic, V value + ProducerRecord record = new ProducerRecord<>("order-p", "订单信息!" + i); + Future future = kafkaProducer.send(record); + //future.get().pa + + ////第一种分区策略,如果既没有指定分区号,也没有指定数据key,那么就会使用轮询的方式将数据均匀的发送到不同的分区里面去 + //ProducerRecord producerRecord1 = new ProducerRecord<>("mypartition", "mymessage" + i); + //kafkaProducer.send(producerRecord1); + // + ////第二种分区策略 如果没有指定分区号,指定了数据key,通过key.hashCode % numPartitions来计算数据究竟会保存在哪一个分区里面 + ////注意:如果数据key,没有变化 key.hashCode % numPartitions = 固定值 所有的数据都会写入到某一个分区里面去 + //ProducerRecord producerRecord2 = new ProducerRecord<>("mypartition", "mykey", "mymessage" + i); + //kafkaProducer.send(producerRecord2); + // + ////第三种分区策略:如果指定了分区号,那么就会将数据直接写入到对应的分区里面去 + //ProducerRecord producerRecord3 = new ProducerRecord<>("mypartition", 0, "mykey", "mymessage" + i); + // kafkaProducer.send(producerRecord3); + // + ////第四种分区策略:自定义分区策略。如果不自定义分区规则,那么会将数据使用轮询的方式均匀的发送到各个分区里面去 + //ProducerRecord producerRecord4 = new ProducerRecord<>("mypartition", "mymessage" + i); + //kafkaProducer.send(producerRecord4); + + Thread.sleep(100); + } + } +} diff --git a/project/kafka/kafka01/src/main/java/com/example/kafka01/c_stream/StreamAPI.java b/project/kafka/kafka01/src/main/java/com/example/kafka01/c_stream/StreamAPI.java new file mode 100644 index 00000000..b731ddc5 --- /dev/null +++ b/project/kafka/kafka01/src/main/java/com/example/kafka01/c_stream/StreamAPI.java @@ -0,0 +1,27 @@ +package com.example.kafka01.c_stream; + +import java.util.Properties; +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.StreamsBuilder; +import org.apache.kafka.streams.StreamsConfig; + +/** + * @author chenpq05 + * @since 2024/2/19 15:27 + */ +public class StreamAPI { + + public static void main(String[] args) { + Properties props = new Properties(); + props.put(StreamsConfig.APPLICATION_ID_CONFIG, "kafka-stream-test"); + props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092"); + props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass()); + props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass()); + StreamsBuilder streamsBuilder = new StreamsBuilder(); + streamsBuilder.stream("test").mapValues(line -> line.toString().toUpperCase()).to("test2"); + KafkaStreams streams = new KafkaStreams(streamsBuilder.build(), props); + streams.start(); + } + +} diff --git a/project/kafka/kafka01/src/main/java/com/example/kafka01/d_callback/CallbackProducer.java b/project/kafka/kafka01/src/main/java/com/example/kafka01/d_callback/CallbackProducer.java new file mode 100644 index 00000000..cba773a5 --- /dev/null +++ b/project/kafka/kafka01/src/main/java/com/example/kafka01/d_callback/CallbackProducer.java @@ -0,0 +1,41 @@ +package com.example.kafka01.d_callback; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import java.util.Properties; +import java.util.concurrent.Future; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.clients.producer.RecordMetadata; +import org.slf4j.LoggerFactory; + +/** + * @author chenpq05 + * @since 2024/2/19 13:42 + */ +public class CallbackProducer { + public static void main(String[] args) throws InterruptedException { + // 设置日志级别 + Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(Level.INFO); + /* 1、连接集群,通过配置文件的方式 + * 2、发送数据-topic:order,value + */ + Properties props = new Properties(); + props.put("bootstrap.servers", "127.0.0.1:9092"); + props.put("acks", "all"); + props.put("retries", 0); + props.put("batch.size", 16384); + props.put("linger.ms", 1); + props.put("buffer.memory", 33554432); + props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); + props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); + KafkaProducer kafkaProducer = new KafkaProducer<>(props); + for (int i = 0; i < 30; i++) { + // 发送数据,需要一个producerRecord对象,最少参数 String topic, V value + ProducerRecord record = new ProducerRecord<>("order-p", "订单信息!" + i); + Future future = kafkaProducer.send(record, new DemoProducerCallback()); + Thread.sleep(100); + } + } +} diff --git a/project/kafka/kafka01/src/main/java/com/example/kafka01/d_callback/DemoProducerCallback.java b/project/kafka/kafka01/src/main/java/com/example/kafka01/d_callback/DemoProducerCallback.java new file mode 100644 index 00000000..ea2796a3 --- /dev/null +++ b/project/kafka/kafka01/src/main/java/com/example/kafka01/d_callback/DemoProducerCallback.java @@ -0,0 +1,22 @@ +package com.example.kafka01.d_callback; + +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.producer.Callback; +import org.apache.kafka.clients.producer.RecordMetadata; + +/** + * @author chenpq05 + * @since 2024/2/20 10:17 + */ +@Slf4j +public class DemoProducerCallback implements Callback { + + @Override + public void onCompletion(RecordMetadata recordMetadata, Exception e) { + log.info("发送消息回调recordMetadata={}", recordMetadata); + if (e != null) { + log.error("发送消息异常", e); + } + } + +} diff --git a/project/kafka/kafka01/src/main/resources/application.properties b/project/kafka/kafka01/src/main/resources/application.properties index 48416f2b..208bcc4e 100644 --- a/project/kafka/kafka01/src/main/resources/application.properties +++ b/project/kafka/kafka01/src/main/resources/application.properties @@ -1,17 +1,16 @@ -# \u8FDE\u63A5kafka\u96C6\u7FA4 +# 连接kafka集群 spring.kafka.bootstrap-servers=192.168.1.221:9092 -#key value\u7684\u5E8F\u5217\u5316 +#key value的序列化 spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer -# \u8FDE\u63A5kafka \u96C6\u7FA4 +# 连接kafka 集群 -# key value \u7684\u53CD\u5E8F\u5217\u5316 +# key value 的反序列化 spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer -# \u6D88\u8D39\u8005\u7EC4id -spring.kafka.consumer.group-id=consumer01 - +# 消费者组id +spring.kafka.consumer.group-id=consumer01 \ No newline at end of file diff --git a/project/liquibase-test/README.md b/project/liquibase-test/README.md new file mode 100644 index 00000000..6e16cf54 --- /dev/null +++ b/project/liquibase-test/README.md @@ -0,0 +1,145 @@ +## liquibase sql脚本管理工具(类似与git--代码管理工具) + +1. [官方文档](https://docs.liquibase.com/start/home.html) +2. [脚本命令参数执行示例](https://docs.liquibase.com/commands/home.html#database-inspection-commands) +3. [文件定义格式(sql、xml等等)](https://docs.liquibase.com/concepts/changelogs/sql-format.html) +4. [官方文档--springboot使用方式](https://contribute.liquibase.com/extensions-integrations/directory/integration-docs/springboot/) +5. [源码怎么构建](https://contribute.liquibase.com/code/) + +## Changelog 文件的结构 +changelog->changeset()->changeTypes(多个操作类型,例如insert、update、delete、createTable等等) +![结构](https://gitee.com/RenZhenGongZuo/base-components/raw/master/img/liquibase/struct1.png) + +## 最佳实践 +[文档地址](https://docs.liquibase.com/concepts/bestpractices.html) + +1. 定义好版本目录 +```text +com + example + db + changelog + db.changelog-root.xml + db.changelog-1.0.xml + db.changelog-1.1.xml + db.changelog-2.0.xml + DatabasePool.java + AbstractDAO.java +``` +1. 定义团队的changeset ID 格式 我们建议您使用从 1 开始的递增数字序列 +2. 请为每个changeset增加 注释 +3. 规划回滚策略 (为了确保安全且可预测的回滚,请在生产环境中运行它们之前在开发环境中对其进行测试) +```text +1. 通过脚本执行回滚 +2. 通过测试用例执行 (自己打liquibase的扩展测试包,然后引用) + //回滚到某个时间点 + liquibase = createLiquibase(rollbackChangeLog); + liquibase.update(this.contexts); + + liquibase = createLiquibase(rollbackChangeLog); + liquibase.rollback(new Date(0), this.contexts); + //生成回滚的sql + liquibase = createLiquibase(rollbackChangeLog); + liquibase.futureRollbackSQL(new Contexts(this.contexts), new LabelExpression(), writer); + //打tag回滚到tag + liquibase = createLiquibase(completeChangeLog); + liquibase.checkLiquibaseTables(false, null, new Contexts(), new LabelExpression()); + liquibase.tag("empty"); + liquibase = createLiquibase(rollbackChangeLog); + liquibase.update(new Contexts()); + liquibase.rollback("empty", new Contexts()); + //回滚8个changeset + liquibase = createLiquibase(rollbackChangeLog); + liquibase.rollback(8, this.contexts); +``` +4. liquibase 永远不会将相同的变更集应用于同一环境,除非您覆盖其默认值(变更集属性 runOnChange 设置为 "true") + +## 团队管理数据库 +1. 一个团队管理一个数据库或一个团队管理多个数据库 + ![图片1](https://gitee.com/RenZhenGongZuo/base-components/raw/master/img/liquibase/img1.png) + ![图片2](https://gitee.com/RenZhenGongZuo/base-components/raw/master/img/liquibase/img2.png) +2. 多个团队管理一个数据库或多个团队管理多个数据库 + ![创建数据库](https://gitee.com/RenZhenGongZuo/base-components/raw/master/img/liquibase/img3.png) + 1. 您将使用单个 URL 和凭据连接到所有数据库。这要求您使用具有多个数据库权限的单个服务帐户。 + 2. 在 SQL 脚本中,每个对象都需要使用数据库名称、架构名称和对象名称进行完全限定 + 3. Liquibase 跟踪表 ( DATABASECHANGELOG 和 DATABASECHANGELOGLOCK ) 将仅在 URL 中指定的一个数据库中创建。对多个数据库的部署将由 + URL 中指定的数据库中的单个 DATABASECHANGELOG 跟踪表进行跟踪。 + 4. 当多个团队共享公共数据库时,无法使用每个团队自己的应用程序存储库。此用例需要为共享数据库设置专用的 SQL 存储库。 + +```text +-- 面向对象发布1 +com + example + db + changelog + changelog-root.xml + changelog-index.xml + changelog-procedure.xml + changelog-table.xml + changelog-view.xml +-- 面向对象发布2 +com + example + db + changelog + changelog-root.xml + changelog-indexes + my-favorite-index.xml + that-other-index.xml + changelog-tables + employees.xml + customers.xml +-- +com + example + db + changelog + changelog-root.xml + changelog-1.0.xml + changelog-1.1.xml + changelog-2.0.xml +com + example + db + changelog + changelog-root.xml + changelog-1.x + changelog-1.0.xml + changelog-1.1.xml + changelog-2.x + changelog-2.0.xml +``` + +[官网文档地址](https://contribute.liquibase.com/code/get-started/env-setup/) + +
自己操作后的命令记录 + +## diff 的命令允许您将两个相同类型或不同类型的数据库相互比较 + +```text +liquibase diff +--url="jdbc:oracle:thin:@::" +--username= +--password= +--reference-url="jdbc:oracle:thin:@::" +--reference-username= +--reference-password= +``` + +## generate-changelog 命令创建一个更改日志文件,该文件具有一系列变更集,这些变更集描述如何重新创建数据库的当前状态 + +[generate-changelog](https://docs.liquibase.com/commands/inspection/generate-changelog.html) + +```text +第一种方法.通过liquibase.properties 指定数据源信息,然后执行下面命令 +liquibase generate-changelog --changelog-file=example-changelog.xml +第二种方法 +liquibase generate-changelog +--url="jdbc:oracle:thin:@::" +--username= +--password= + +如果您的数据库需要 & in URL,则在命令行上指定 URL 时,可能需要将 URL 括在双引号中 +``` + +
\ No newline at end of file diff --git a/project/liquibase-test/pom.xml b/project/liquibase-test/pom.xml new file mode 100644 index 00000000..6c7aa0e7 --- /dev/null +++ b/project/liquibase-test/pom.xml @@ -0,0 +1,56 @@ + + 4.0.0 + + com.zhou.base.components + base-components + 1.0-SNAPSHOT + + + com.zhou.base + liquibase-test + jar + + liquibase-test + http://maven.apache.org + + + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.liquibase + liquibase-core + + + mysql + mysql-connector-java + 8.0.30 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/project/liquibase-test/src/main/java/com/zhou/base/liquibase/test/LiquibaseApplication.java b/project/liquibase-test/src/main/java/com/zhou/base/liquibase/test/LiquibaseApplication.java new file mode 100644 index 00000000..8561c149 --- /dev/null +++ b/project/liquibase-test/src/main/java/com/zhou/base/liquibase/test/LiquibaseApplication.java @@ -0,0 +1,34 @@ +package com.zhou.base.liquibase.test; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; + +import java.util.Arrays; + +/** + * Hello world! + */ +@SpringBootApplication +public class LiquibaseApplication { + + + public static void main(String[] args) { + System.out.println("Hello Liquibase"); + SpringApplication.run(LiquibaseApplication.class, args); + } + + @Bean + public CommandLineRunner commandLineRunner(ApplicationContext ctx) { + return args -> { + System.out.println("Let's inspect the beans provided by Spring Boot:"); + String[] beanNames = ctx.getBeanDefinitionNames(); + Arrays.sort(beanNames); + for (String beanName : beanNames) { + System.out.println(beanName); + } + }; + } +} diff --git a/project/liquibase-test/src/main/resources/application.properties b/project/liquibase-test/src/main/resources/application.properties new file mode 100644 index 00000000..f9f98d54 --- /dev/null +++ b/project/liquibase-test/src/main/resources/application.properties @@ -0,0 +1,8 @@ +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://rm-8vb34z58iimdh7o3z7o.mysql.zhangbei.rds.aliyuncs.com:3306/liquibase-test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&serverTimezone=UTC +spring.datasource.username=zxf_1 +spring.datasource.password=123456123 +#spring.liquibase.enabled=true +#spring.liquibase.change-log=classpath:db/changelog/changelog.sql +#spring.liquibase.change-log=classpath:db/changelog-with-folder/myChangeLog.xml +spring.liquibase.change-log=classpath:db/changelogs-multi/mainChangeLog.xml diff --git a/project/liquibase-test/src/main/resources/db/changelog-with-folder/myChangeLog.xml b/project/liquibase-test/src/main/resources/db/changelog-with-folder/myChangeLog.xml new file mode 100644 index 00000000..98cc273b --- /dev/null +++ b/project/liquibase-test/src/main/resources/db/changelog-with-folder/myChangeLog.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/project/liquibase-test/src/main/resources/db/changelog-with-folder/sql/changelog1.xml b/project/liquibase-test/src/main/resources/db/changelog-with-folder/sql/changelog1.xml new file mode 100644 index 00000000..c95a6f9e --- /dev/null +++ b/project/liquibase-test/src/main/resources/db/changelog-with-folder/sql/changelog1.xml @@ -0,0 +1,34 @@ + + + + + + select 1 + + + + You can add comments to changeSets. + They can even be multiple lines if you would like. + They aren't used to compute the changeSet MD5Sum, so you can update them whenever you want without causing + problems. + + + + + + + + + + + + + + + + + + diff --git a/project/liquibase-test/src/main/resources/db/changelog-with-folder/sql/v1.sql b/project/liquibase-test/src/main/resources/db/changelog-with-folder/sql/v1.sql new file mode 100644 index 00000000..b53f7e26 --- /dev/null +++ b/project/liquibase-test/src/main/resources/db/changelog-with-folder/sql/v1.sql @@ -0,0 +1,18 @@ +-- changeset your.name:zxf21 labels:example-label context:example-context +-- comment: example comment +create table PERSON111121 +( + ID int not null, + FNAME varchar(100) not null +); + +-- changeset your.name:zxf22 labels:example-label context:example-context +-- comment: example comment +create table company122 +( + id int primary key auto_increment not null, + name varchar(50) not null, + address1 varchar(50), + address2 varchar(50), + city varchar(30) +); \ No newline at end of file diff --git a/project/liquibase-test/src/main/resources/db/changelog-with-folder/sql/v2.sql b/project/liquibase-test/src/main/resources/db/changelog-with-folder/sql/v2.sql new file mode 100644 index 00000000..9fc6ad4c --- /dev/null +++ b/project/liquibase-test/src/main/resources/db/changelog-with-folder/sql/v2.sql @@ -0,0 +1,8 @@ +create table company1223 +( + id int primary key auto_increment not null, + name varchar(50) not null, + address1 varchar(50), + address2 varchar(50), + city varchar(30) +); \ No newline at end of file diff --git a/project/liquibase-test/src/main/resources/db/changelog-with-folder/sql/v3.sql b/project/liquibase-test/src/main/resources/db/changelog-with-folder/sql/v3.sql new file mode 100644 index 00000000..1d8bdd97 --- /dev/null +++ b/project/liquibase-test/src/main/resources/db/changelog-with-folder/sql/v3.sql @@ -0,0 +1,5 @@ +select * +from PERSON111121; +/*A comment*/ +select * +from PERSON111121 \ No newline at end of file diff --git a/project/liquibase-test/src/main/resources/db/changelog/changelog.sql b/project/liquibase-test/src/main/resources/db/changelog/changelog.sql new file mode 100644 index 00000000..0d3bce7a --- /dev/null +++ b/project/liquibase-test/src/main/resources/db/changelog/changelog.sql @@ -0,0 +1,44 @@ +-- liquibase formatted sql + +-- changeset liquibase:1 +CREATE TABLE `test_table` +( + `test_id` int NOT NULL, + `test_column` varchar(255) NULL, + PRIMARY KEY (`test_id`) +); + +-- changeset your.name:1 labels:example-label context:example-context +-- comment: example comment +create table person +( + id int primary key auto_increment not null, + name varchar(50) not null, + address1 varchar(50), + address2 varchar(50), + city varchar(30) +) + +-- changeset your.name:22 labels:example-label context:example-context +-- comment: example comment +create table company +( + id int primary key auto_increment not null, + name varchar(50) not null, + address1 varchar(50), + address2 varchar(50), + city varchar(30) +) +-- rollback DROP TABLE company; + +-- changeset your.name:23 labels:example-label context:example-context +-- comment: example comment +create table company1 +( + id int primary key auto_increment not null, + name varchar(50) not null, + address1 varchar(50), + address2 varchar(50), + city varchar(30) +); +-- rollback DROP TABLE company1; \ No newline at end of file diff --git a/project/liquibase-test/src/main/resources/db/changelogs-multi/databaseA/changelog.xml b/project/liquibase-test/src/main/resources/db/changelogs-multi/databaseA/changelog.xml new file mode 100644 index 00000000..1014f868 --- /dev/null +++ b/project/liquibase-test/src/main/resources/db/changelogs-multi/databaseA/changelog.xml @@ -0,0 +1,34 @@ + + + + + + select 1 + + + + You can add comments to changeSets. + They can even be multiple lines if you would like. + They aren't used to compute the changeSet MD5Sum, so you can update them whenever you want without causing + problems. + + + + + + + + + + + + + + + + + + diff --git a/project/liquibase-test/src/main/resources/db/changelogs-multi/databaseA/v1.sql b/project/liquibase-test/src/main/resources/db/changelogs-multi/databaseA/v1.sql new file mode 100644 index 00000000..e69de29b diff --git a/project/liquibase-test/src/main/resources/db/changelogs-multi/databaseB/changelog.xml b/project/liquibase-test/src/main/resources/db/changelogs-multi/databaseB/changelog.xml new file mode 100644 index 00000000..893a4cfb --- /dev/null +++ b/project/liquibase-test/src/main/resources/db/changelogs-multi/databaseB/changelog.xml @@ -0,0 +1,36 @@ + + + + + + select 1 + + + + + You can add comments to changeSets. + They can even be multiple lines if you would like. + They aren't used to compute the changeSet MD5Sum, so you can update them whenever you want without causing + problems. + + + + + + + + + + + + + + + + + + + diff --git a/project/liquibase-test/src/main/resources/db/changelogs-multi/databaseB/v1.sql b/project/liquibase-test/src/main/resources/db/changelogs-multi/databaseB/v1.sql new file mode 100644 index 00000000..e69de29b diff --git a/project/liquibase-test/src/main/resources/db/changelogs-multi/mainChangelog.xml b/project/liquibase-test/src/main/resources/db/changelogs-multi/mainChangelog.xml new file mode 100644 index 00000000..cd295985 --- /dev/null +++ b/project/liquibase-test/src/main/resources/db/changelogs-multi/mainChangelog.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/project/liquibase-test/src/test/resources/rollbackable.changelog.xml b/project/liquibase-test/src/test/resources/rollbackable.changelog.xml new file mode 100644 index 00000000..3a7bd1e8 --- /dev/null +++ b/project/liquibase-test/src/test/resources/rollbackable.changelog.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Refactorings that can't be automatically rolled back can be custom rolled back with a "rollback" tag. + + + + + + + + + + + + + + + delete from publication; + + + + + + + + + + + + + + + select title, isbn from publication + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/project/modbus/modbus-01/pom.xml b/project/modbus/modbus-01/pom.xml new file mode 100644 index 00000000..af5d37ff --- /dev/null +++ b/project/modbus/modbus-01/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + org.cpq + modbus-01 + 1.0-SNAPSHOT + + + 21 + 21 + UTF-8 + + + + + com.infiniteautomation + modbus4j + 3.1.0 + + + + + + + + false + + + true + + ias-snapshots + Infinite Automation Snapshot Repository + https://maven.mangoautomation.net/repository/ias-snapshot/ + + + + true + + + false + + ias-releases + Infinite Automation Release Repository + https://maven.mangoautomation.net/repository/ias-release/ + + + + \ No newline at end of file diff --git "a/project/modbus/modbus-01/src/main/java/org/cpq/A01modbus4j-\345\206\231\345\215\225\344\270\252\345\257\204\345\255\230\345\231\250.jpg" "b/project/modbus/modbus-01/src/main/java/org/cpq/A01modbus4j-\345\206\231\345\215\225\344\270\252\345\257\204\345\255\230\345\231\250.jpg" new file mode 100644 index 00000000..1535a05f Binary files /dev/null and "b/project/modbus/modbus-01/src/main/java/org/cpq/A01modbus4j-\345\206\231\345\215\225\344\270\252\345\257\204\345\255\230\345\231\250.jpg" differ diff --git "a/project/modbus/modbus-01/src/main/java/org/cpq/A01modbus4j-\350\257\273\345\217\226Float\346\225\260\346\215\256.jpg" "b/project/modbus/modbus-01/src/main/java/org/cpq/A01modbus4j-\350\257\273\345\217\226Float\346\225\260\346\215\256.jpg" new file mode 100644 index 00000000..291b0a04 Binary files /dev/null and "b/project/modbus/modbus-01/src/main/java/org/cpq/A01modbus4j-\350\257\273\345\217\226Float\346\225\260\346\215\256.jpg" differ diff --git "a/project/modbus/modbus-01/src/main/java/org/cpq/A01modbus4j-\350\257\273\345\217\226int\346\225\260\346\215\256.jpg" "b/project/modbus/modbus-01/src/main/java/org/cpq/A01modbus4j-\350\257\273\345\217\226int\346\225\260\346\215\256.jpg" new file mode 100644 index 00000000..b0b540dd Binary files /dev/null and "b/project/modbus/modbus-01/src/main/java/org/cpq/A01modbus4j-\350\257\273\345\217\226int\346\225\260\346\215\256.jpg" differ diff --git a/project/modbus/modbus-01/src/main/java/org/cpq/A01modbus4j.java b/project/modbus/modbus-01/src/main/java/org/cpq/A01modbus4j.java new file mode 100644 index 00000000..811bea0c --- /dev/null +++ b/project/modbus/modbus-01/src/main/java/org/cpq/A01modbus4j.java @@ -0,0 +1,102 @@ +package org.cpq; + +import com.serotonin.modbus4j.ModbusFactory; +import com.serotonin.modbus4j.ModbusMaster; +import com.serotonin.modbus4j.code.DataType; +import com.serotonin.modbus4j.exception.ErrorResponseException; +import com.serotonin.modbus4j.exception.ModbusInitException; +import com.serotonin.modbus4j.exception.ModbusTransportException; +import com.serotonin.modbus4j.ip.IpParameters; +import com.serotonin.modbus4j.locator.BaseLocator; +import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest; +import com.serotonin.modbus4j.msg.ReadHoldingRegistersResponse; +import com.serotonin.modbus4j.msg.WriteRegisterRequest; +import com.serotonin.modbus4j.msg.WriteRegisterResponse; + +public class A01modbus4j { + public static void main(String[] args) { + // 创建 Modbus 工厂 + ModbusFactory factory = new ModbusFactory(); + + // 配置 TCP 参数 + IpParameters params = new IpParameters(); + params.setHost("127.0.0.1"); // 设备 IP + params.setPort(502); // 默认端口 502 + + // 创建 Modbus TCP 主站 + ModbusMaster master = factory.createTcpMaster(params, true); // false 表示非长连接 + + // 示例 1: 读取保持寄存器(功能码 03) + int slaveId = 1; // 从站地址 + int startOffset = 0; // 寄存器起始地址 + int numberOfRegisters = 10; // 读取数量 + + // 初始化连接 + try { + master.init(); + } catch (ModbusInitException e) { + e.printStackTrace(); + System.out.println("master.init()发生了异常"); + return; + } + + try { + holdingRegisterInt(slaveId, startOffset, numberOfRegisters, master); + + // holdingRegisterFloat(slaveId, master); + + // 示例 2: 写入单个寄存器(功能码 06) + // writeRegister(slaveId, 0, 1234, master); + + } catch (Exception e) { + e.printStackTrace(); + } finally { + master.destroy(); // 关闭连接 + } + } + + /** + * 读取整型数据 + * 图片:A01modbus4j-读取int数据.jpg + */ + private static void holdingRegisterInt(int slaveId, int startOffset, int numberOfRegisters, ModbusMaster master) throws ModbusTransportException, ErrorResponseException { + ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, startOffset, numberOfRegisters); + ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request); + if (!response.isException()) { + short[] values = response.getShortData(); + System.out.println("寄存器值0:" + values[0] ); + System.out.println("寄存器值1:" + values[1] ); + System.out.println("寄存器值2:" + values[2] ); + System.out.println("寄存器值3:" + values[3] ); + } + + // DataType.TWO_BYTE_INT_SIGNED 是 2字节有符号整数 + BaseLocator loc = BaseLocator.holdingRegister(slaveId, 1, DataType.TWO_BYTE_INT_SIGNED); + Number value = master.getValue(loc); + System.out.println("读取到的值:" + value); + } + + /** + * 读取整型数据 + * 图片:A01modbus4j-读取Float数据.jpg + */ + private static void holdingRegisterFloat(int slaveId, ModbusMaster master) throws ModbusTransportException, ErrorResponseException { + // DataType.FOUR_BYTE_FLOAT 是4字节浮点数 + BaseLocator num7 = BaseLocator.holdingRegister(slaveId, 7, DataType.FOUR_BYTE_FLOAT); + Number v7 = master.getValue(num7); + System.out.println("读取到的值v7:" + v7); + } + + /** + * 写入单个寄存器 + * 图片:A01modbus4j-写单个寄存器.jpg + */ + private static void writeRegister(int slaveId, int writeOffset, int valueToWrite, ModbusMaster master) throws ModbusTransportException { + WriteRegisterRequest writeRequest = new WriteRegisterRequest(slaveId, writeOffset, valueToWrite); + WriteRegisterResponse writeResponse = (WriteRegisterResponse) master.send(writeRequest); + if (!writeResponse.isException()) { + System.out.println("写入成功"); + } + } + +} \ No newline at end of file diff --git a/project/modbus/readme.md b/project/modbus/readme.md new file mode 100644 index 00000000..5d1a58c1 --- /dev/null +++ b/project/modbus/readme.md @@ -0,0 +1,167 @@ +# 存储区 + 输出线圈 0 + 地址:00001 ~ 09999 + 输入线圈 1 + 地址:10001 ~ 19999 + 输入寄存器 3 + 地址:30001 ~ 39999 + 输出寄存器 4 + 地址:40001 ~ 49999 + 存储区范围:5位和6位 标准地址 拓展地址 + 如果是6位的存储区,每个存储区最大地址是65536,则线圈、寄存器的地址分别是 + 000001 ~ 065536 + 100001 ~ 165536 + 300001 ~ 365536 + 400001 ~ 465536 + +# 读写功能码 + 读写 功能码 + + 读输出线圈 01 + 读输入线圈 02 + 读输出寄存器 03 + 读输入寄存器 04 + 写单个输出线圈 05 + 写单个输出寄存器 06 + 写多个输出线圈 15 + 写多个输出寄存器 16 + +# 工具 + mbpoll,模拟Modbus主站(如PLC、SCADA系统),用于测试和调试从设备(如传感器、执行器)。 + mbslave,模拟Modbus从站(如设备、仪表) + +# ModbusRTU协议 + +通用报文格式:从站地址(设备编码)+ 功能码 + 数据 + 校验 + + ModbusRTU + 从站地址:1字节 + 功能码:1字节 + 数据:可变长度 + 校验:2字节(CRC16校验) + +## 01H功能码读取输出线圈 + + 发送报文格式:从站地址(设备编码)+ 功能码 + 开始线圈 + 线圈数量 + CRC + 接收报文格式:从站地址(设备编码)+ 功能码 + 字节计数 + 数据 + CRC + +### 数据解析 + +![](./图片/01-01H功能码读取输出线圈.jpg) + +Tx:000078-01 01 00 0A 00 14 1C 07 + +Tx表示发送,000042-不用理会, + +01表示从站地址 + +01表示功能码,读取输出线圈 + +00 0A表示开始线圈。开始线圈是00010,也就是10,10转16进制是A,开始线圈占2字节,所以是00 0A + +00 14表示线圈数量。读取的线圈数量是20,转为16进制是14,线圈数量占2字节,所以是00 14 + +1C 07表示CRC校验 + +Rx:000079-01 01 03 03 00 00 CC 4E + +Rx表示接受报文,000043-不用理会, + +01表示从站地址 + +01表示功能码,读取输出线圈 + +03表示字节计数,线圈数量是20,一个字节是8,两个字节是16,20得用3个字节表示,所以是03 + +![](./图片/02-01H功能码读取输出线圈-接受报文.jpg) + +03 00 00 表示数据。第一个字节是0000 0011,所以是03,总共20个线圈,但后面的都是0,所以是00 00 + +CC 4E 表示CRC校验 + +## 02H功能码读取输入线圈 + 报文格式与“01H功能码读取输出线圈”相同,只是功能码为02 + 输入线圈只能通过slave改数据,不能通过Modbus poll改数据 + +# ModbusTCP协议 +## 通信格式说明 + MBAP报文头,7字节 + 事务标识符,2字节。报文ID,不参与运算 + 协议标识符,2字节。固定为00 00 + 长度,2字节。 长度之后总共的字节数 + 单元标识符,1字节。相当于ModbusRTU的从站地址 + 功能码,1字节 + 数据部分,可变长度 + +![](./图片/03-ModbusTCP与ModbusRTU对比.jpg) + +### ModbusTCP与RTU之间的关系 + 校验: + ModbusTCP一般是基于TCP/UDP,在传输层已经有了校验,不需要在应用层再做校验 + 站地址: + ModbusTCP弱化了站地址的概念,因为在以太网中,可通过IP地址确定一个设备。 + 但为了保守起见,还是保留了站地址,可使用单元标识符表示站地址。 + 在实际应用中,可以使用单元标识符表示站地址,也可以不使用单元标识符表示站地址。 + 如果忽略单元标识符,则单元标识符默认为1 + +## 02功能码读取输入线圈 + 发送报文格式:事务标识符 + 协议标识符 + 长度 + 单元标识符 + 功能码 + 线圈地址 + 长度 + 返回报文格式:事务标识符 + 协议标识符 + 长度 + 单元标识符 + 功能码 + 字节计数 + 数据 + +### 例子:读取1号站点从10开始的20个线圈的值 +![](./图片/04-读取输入线圈.jpg) + +Tx:000102-02 D8 00 00 00 06 01 02 00 0A 00 14 + + Tx表示发送,000102-不用理会 + 02 D8:事务标识符,自增 + 00 00:协议标识符 + 00 06:长度,06后面的数据是6字节,所以是06 + 01:单元标识符,忽略单元标识符,则单元标识符默认为1 + 02:功能码,02功能码读取输入线圈 + 00 0A:线圈地址,从00010开始读取,所以是00 0A + 00 14:读取的线圈数量,20个,转为16进制是14,所以是00 14 + +Rx:000103-02 D8 00 00 00 06 01 02 03 03 00 00 + + Rx表示接收数据,000103-不用理会 + 02 D8:事务标识符,自增 + 00 00:协议标识符 + 00 06:长度,06后面的数据是6字节,所以是06 + 01:单元标识符,忽略单元标识符,则单元标识符默认为1 + 02:功能码,02功能码读取输入线圈 + 03:字节计数,读20个线圈,一个字节是8,两个字节是16,20得用3个字节表示,所以是03 + 03 00 00:数据,输入线圈的值。第一个字节是0000 0011,所以是03,总共20个线圈,但后面的都是0,所以是00 00 + +## 04功能码读取输入寄存器 + 发送报文格式:事务标识符 + 协议标识符 + 长度 + 单元标识符 + 功能码 + 寄存器地址 + 长度 + 返回报文格式:事务标识符 + 协议标识符 + 长度 + 单元标识符 + 功能码 + 字节计数 + 数据 + +### 例子:读取1号站点从4开始的2个寄存器的值 +![](./图片/05-04功能码读取输入寄存器.jpg) + +Tx:001020-03 48 00 00 00 06 01 04 00 04 00 02 + + Tx表示发送,001020-不用理会 + 03 48:事务标识符,自增 + 00 00:协议标识符 + 00 06:长度,06后面的数据是6字节,所以是06 + 01:单元标识符,忽略单元标识符,则单元标识符默认为1 + 04:功能码,04功能码读取输入寄存器 + 00 04:寄存器地址,从起始地址4开始读取 + 00 02:长度,读取数量是2寄存器 + +Rx:001021-03 48 00 00 00 07 01 04 04 00 7B 01 59 + + Rx表示接收数据,001021-不用理会 + 03 48:事务标识符,自增 + 00 00:协议标识符 + 00 07:长度,07后面的数据是6字节,所以是06 + 01:单元标识符,忽略单元标识符,则单元标识符默认为1 + 04:功能码,04功能码读取输入寄存器 + 04:字节计数,读取两个寄存器,一个字节是8,两个字节是16,所以是04 + 00 7B 01 59:十六进制007B转十进制是123,0159转十进制是345 + + + + diff --git "a/project/modbus/\345\233\276\347\211\207/01-01H\345\212\237\350\203\275\347\240\201\350\257\273\345\217\226\350\276\223\345\207\272\347\272\277\345\234\210.jpg" "b/project/modbus/\345\233\276\347\211\207/01-01H\345\212\237\350\203\275\347\240\201\350\257\273\345\217\226\350\276\223\345\207\272\347\272\277\345\234\210.jpg" new file mode 100644 index 00000000..55e393bc Binary files /dev/null and "b/project/modbus/\345\233\276\347\211\207/01-01H\345\212\237\350\203\275\347\240\201\350\257\273\345\217\226\350\276\223\345\207\272\347\272\277\345\234\210.jpg" differ diff --git "a/project/modbus/\345\233\276\347\211\207/02-01H\345\212\237\350\203\275\347\240\201\350\257\273\345\217\226\350\276\223\345\207\272\347\272\277\345\234\210-\346\216\245\345\217\227\346\212\245\346\226\207.jpg" "b/project/modbus/\345\233\276\347\211\207/02-01H\345\212\237\350\203\275\347\240\201\350\257\273\345\217\226\350\276\223\345\207\272\347\272\277\345\234\210-\346\216\245\345\217\227\346\212\245\346\226\207.jpg" new file mode 100644 index 00000000..b9fce06d Binary files /dev/null and "b/project/modbus/\345\233\276\347\211\207/02-01H\345\212\237\350\203\275\347\240\201\350\257\273\345\217\226\350\276\223\345\207\272\347\272\277\345\234\210-\346\216\245\345\217\227\346\212\245\346\226\207.jpg" differ diff --git "a/project/modbus/\345\233\276\347\211\207/03-ModbusTCP\344\270\216ModbusRTU\345\257\271\346\257\224.jpg" "b/project/modbus/\345\233\276\347\211\207/03-ModbusTCP\344\270\216ModbusRTU\345\257\271\346\257\224.jpg" new file mode 100644 index 00000000..ee5ca2cf Binary files /dev/null and "b/project/modbus/\345\233\276\347\211\207/03-ModbusTCP\344\270\216ModbusRTU\345\257\271\346\257\224.jpg" differ diff --git "a/project/modbus/\345\233\276\347\211\207/04-\350\257\273\345\217\226\350\276\223\345\205\245\347\272\277\345\234\210.jpg" "b/project/modbus/\345\233\276\347\211\207/04-\350\257\273\345\217\226\350\276\223\345\205\245\347\272\277\345\234\210.jpg" new file mode 100644 index 00000000..4497e1e6 Binary files /dev/null and "b/project/modbus/\345\233\276\347\211\207/04-\350\257\273\345\217\226\350\276\223\345\205\245\347\272\277\345\234\210.jpg" differ diff --git "a/project/modbus/\345\233\276\347\211\207/05-04\345\212\237\350\203\275\347\240\201\350\257\273\345\217\226\350\276\223\345\205\245\345\257\204\345\255\230\345\231\250.jpg" "b/project/modbus/\345\233\276\347\211\207/05-04\345\212\237\350\203\275\347\240\201\350\257\273\345\217\226\350\276\223\345\205\245\345\257\204\345\255\230\345\231\250.jpg" new file mode 100644 index 00000000..186d60fa Binary files /dev/null and "b/project/modbus/\345\233\276\347\211\207/05-04\345\212\237\350\203\275\347\240\201\350\257\273\345\217\226\350\276\223\345\205\245\345\257\204\345\255\230\345\231\250.jpg" differ diff --git a/project/mqtt/README.md b/project/mqtt/README.md new file mode 100644 index 00000000..10c66caf --- /dev/null +++ b/project/mqtt/README.md @@ -0,0 +1,61 @@ +# emqx + +### docker 安装 + +docker pull emqx/emqx:v4.0.5 + +docker run -tid --name emqx -p 1883:1883 -p 8083:8083 -p 8081:8081 -p 8883:8883 -p 8084:8084 -p 18083:18083 emqx/emqx:v4.0.5 + +控制台访问地址:http://192.168.1.231:18083/ + +默认用户名/密码:admin/public + +### 认证 +EMQX 默认开启匿名认证,任何客户端都可以连接 + +/opt/emqx/etc + +allow_anonymous = true + +emqx restart 重启 + +启用 emqx_auth_username 插件,使用api接口新增密码 + +curl --location --request POST 'http://192.168.1.231:18083/api/v4/auth_username' \ +--header 'Authorization: Basic YWRtaW46cHVibGlj' \ +--header 'Content-Type: application/json' \ +--data-raw '{ +"username": "user", +"password": "123456" +}' + +Authorization 是 Basic空格 加上 admin/public 的base64编码 + +默认配置中 ACL 是开放授权的,acl_nomatch = allow,需要改为禁止 +配置文件位置:etc/emqx.conf +acl_nomatch = deny + +### webhook +修改 /opt/emqx/etc/plugins/emqx_web_hook.conf + +web.hook.api.url = http://192.168.1.3:8991/mqtt/webhook + +emqx restart 重启 + +在EMQX后台启动 emqx_web_hook 插件 + + + + + + + + + + + + + + + + diff --git a/spring-cloud-learn/s2-api-gateway/.gitignore b/project/mqtt/mqtt-client-paho/.gitignore similarity index 70% rename from spring-cloud-learn/s2-api-gateway/.gitignore rename to project/mqtt/mqtt-client-paho/.gitignore index 4a453031..549e00a2 100644 --- a/spring-cloud-learn/s2-api-gateway/.gitignore +++ b/project/mqtt/mqtt-client-paho/.gitignore @@ -1,5 +1,8 @@ -/target/ +HELP.md +target/ !.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ ### STS ### .apt_generated @@ -22,7 +25,9 @@ /dist/ /nbdist/ /.nb-gradle/ -/build/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ ### VS Code ### .vscode/ diff --git a/project/mqtt/mqtt-client-paho/pom.xml b/project/mqtt/mqtt-client-paho/pom.xml new file mode 100644 index 00000000..b3b8e0eb --- /dev/null +++ b/project/mqtt/mqtt-client-paho/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.3.1.RELEASE + + + com.example + mqtt-client-paho + 0.0.1-SNAPSHOT + mqtt-client-paho + mqtt-client-paho + + 1.8 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.2 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/spring-cloud-learn/stream-group/src/main/java/com/cpq/streamgroup/StreamGroupApplication.java b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/MqttClientPahoApplication.java similarity index 57% rename from spring-cloud-learn/stream-group/src/main/java/com/cpq/streamgroup/StreamGroupApplication.java rename to project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/MqttClientPahoApplication.java index ec174244..ed9379ef 100644 --- a/spring-cloud-learn/stream-group/src/main/java/com/cpq/streamgroup/StreamGroupApplication.java +++ b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/MqttClientPahoApplication.java @@ -1,12 +1,13 @@ -package com.cpq.streamgroup; +package com.example.mqttclientpaho; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class StreamGroupApplication { +public class MqttClientPahoApplication { public static void main(String[] args) { - SpringApplication.run(StreamGroupApplication.class, args); + SpringApplication.run(MqttClientPahoApplication.class, args); } + } diff --git a/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/TestMqtt.java b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/TestMqtt.java new file mode 100644 index 00000000..7b5b509a --- /dev/null +++ b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/TestMqtt.java @@ -0,0 +1,45 @@ +package com.example.mqttclientpaho; + +import com.example.mqttclientpaho.client.EmqClient; +import com.example.mqttclientpaho.enums.QosEnum; +import com.example.mqttclientpaho.properties.MqttProperties; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +public class TestMqtt implements CommandLineRunner { + @Autowired + private EmqClient emqClient; + @Autowired + private MqttProperties mqttProperties; + + @Override + public void run(String... args) throws Exception { + // 连接mqtt broker + emqClient.connect(mqttProperties.getUsername(), mqttProperties.getPassword()); + // 订阅主题 + emqClient.subscribe("testtopic/#", QosEnum.QoS2); + + // 循环推送消息测试 + new Thread(() -> { + String topic = "testtopic/1234"; + while (true) { + String payload = "循环推送消息" + LocalDateTime.now().toString(); + emqClient.publish(topic, payload, QosEnum.QoS2, false); + try { + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("", e); + } + } + }).start(); + } + +} diff --git a/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/callback/MessageCallback.java b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/callback/MessageCallback.java new file mode 100644 index 00000000..6de98d15 --- /dev/null +++ b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/callback/MessageCallback.java @@ -0,0 +1,53 @@ +package com.example.mqttclientpaho.callback; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; + +/** + * mqtt回调 + */ +@Slf4j +@Component +public class MessageCallback implements MqttCallback { + + @Override + public void connectionLost(Throwable throwable) { + // 丢失对服务端的连接后触发该方法回调,此处可以做一些特殊处理,比如重连 + log.info("丢失了对broker的连接"); + } + + /** + * 收到订阅的消息 + * 该方法由mqtt客户端同步调用,在此方法未正确返回之前,不会发送ack确认消息到broker。 + * 一旦该方法向外抛出了异常客户端将异常关闭,当再次连接时;所有QoS1,QoS2且客户端未进行ack确认的消息都将由 + * broker服务器再次发送到客户端 + */ + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + try { + log.info("收到订阅消息,topic={},messageId={},qos={}, payload={}", + topic, message.getId(), message.getQos(), new String(message.getPayload(), StandardCharsets.UTF_8)); + } catch (Exception e) { + log.error("处理订阅消息异常", e); + } + } + + /** + * 消息发布完成且收到ack确认后的回调 + * QoS0:消息被网络发出后触发一次 + * QoS1:当收到broker的PUBACK消息后触发 + * QoS2:当收到broer的PUBCOMP消息后触发 + */ + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + int messageId = token.getMessageId(); + String[] topics = token.getTopics(); + log.info("消息发送完成,messageId={},topics={}",messageId,topics); + } + +} diff --git a/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/client/EmqClient.java b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/client/EmqClient.java new file mode 100644 index 00000000..ff0ca4ef --- /dev/null +++ b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/client/EmqClient.java @@ -0,0 +1,128 @@ +package com.example.mqttclientpaho.client; + +import com.example.mqttclientpaho.enums.QosEnum; +import com.example.mqttclientpaho.properties.MqttProperties; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +/** + * mqtt客户端封装 + */ +@Slf4j +@Component +public class EmqClient { + + private MqttClient mqttClient; + + @Autowired + private MqttProperties mqttProperties; + + @Autowired + private MqttCallback mqttCallback; + + @PostConstruct + public void init() { + // MqttClientPersistence是接口,实现类有 MqttDefaultFilePersistence、MemoryPersistence + MemoryPersistence memoryPersistence = new MemoryPersistence(); + try { + mqttClient = new MqttClient(mqttProperties.getBrokerUrl(), mqttProperties.getClientId(), memoryPersistence); + } catch (MqttException e) { + log.error("初始化mqttClient异常", e); + } + } + + /** + * 连接broker + */ + public void connect(String username, String password) { + // 创建 Mqtt连接选项 + MqttConnectOptions connectOptions = new MqttConnectOptions(); + // 自动重连 + connectOptions.setAutomaticReconnect(true); + /** + * 设置为true后意味着:客户端断开连接后emq不保留会话保留会话,否则会产生订阅共享队列的存活 + 客户端收不到消息的情况 + * 因为断开的连接还被保留的话,emq会将队列中的消息负载到断开但还保留的客户端,导致存活的客户 + 端收不到消息 + * 解决该问题有两种方案:1.连接断开后不要保持;2.保证每个客户端有固定的clientId + */ + connectOptions.setCleanSession(true); + connectOptions.setUserName(username); + connectOptions.setPassword(password.toCharArray()); + + //设置mqtt消息回调 + mqttClient.setCallback(mqttCallback); + + try { + mqttClient.connect(connectOptions); + } catch (MqttException e) { + log.error("连接mqtt broker失败", e); + } + } + + /** + * 发布消息 + */ + public void publish(String topic, String payload, QosEnum qos, boolean retain) { + MqttMessage mqttMessage = new MqttMessage(); + mqttMessage.setQos(qos.value()); + mqttMessage.setRetained(retain); + mqttMessage.setPayload(payload.getBytes()); + if (mqttClient.isConnected()) { + try { + mqttClient.publish(topic, mqttMessage); + } catch (MqttException e) { + log.error("发布消息失败", e); + } + } + } + + /** + * 订阅主题 + */ + public void subscribe(String topicFilter, QosEnum qos) { + try { + mqttClient.subscribe(topicFilter, qos.value()); + } catch (MqttException e) { + log.error("订阅失败", e); + } + } + + /** + * 取消订阅 + */ + public void unSubscribe(String topicFilter) { + try { + mqttClient.unsubscribe(topicFilter); + } catch (MqttException e) { + log.error("取消订阅异常", e); + } + } + + /** + * 重连 + */ + public void reConnect() { + try { + mqttClient.reconnect(); + } catch (MqttException e) { + log.error("重连异常", e); + } + } + + @PreDestroy + public void disConnect() { + try { + mqttClient.disconnect(); + } catch (MqttException e) { + log.error("断开连接异常", e); + } + } + +} diff --git a/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/controller/WebHookController.java b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/controller/WebHookController.java new file mode 100644 index 00000000..ebacd8a5 --- /dev/null +++ b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/controller/WebHookController.java @@ -0,0 +1,39 @@ +package com.example.mqttclientpaho.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/mqtt") +public class WebHookController { + private static final Logger log = LoggerFactory.getLogger(WebHookController.class); + + private Map clientStatus = new HashMap<>(); + + @PostMapping("/webhook") + public void hook(@RequestBody Map params){ + log.info("emqx 触发 webhook,请求体数据={}",params); + + String action = (String) params.get("action"); + String clientId = (String) params.get("clientid"); + if(action.equals("client_connected")){ + log.info("客户端{}接入本系统",clientId); + clientStatus.put(clientId,true); + } + + if(action.equals("client_disconnected")){ + log.info("客户端{}下线",clientId); + clientStatus.put(clientId,false); + } + + } + + @GetMapping("/allStatus") + public Map getStatus(){ + return this.clientStatus; + } +} diff --git a/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/enums/QosEnum.java b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/enums/QosEnum.java new file mode 100644 index 00000000..79ff0afd --- /dev/null +++ b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/enums/QosEnum.java @@ -0,0 +1,19 @@ +package com.example.mqttclientpaho.enums; + +/** + * Qos枚举值 + */ +public enum QosEnum { + + QoS0(0),QoS1(1),QoS2(2); + + private final int value; + + QosEnum(int value) { + this.value = value; + } + + public int value(){ + return this.value; + } +} diff --git a/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/properties/MqttProperties.java b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/properties/MqttProperties.java new file mode 100644 index 00000000..8b3c12c5 --- /dev/null +++ b/project/mqtt/mqtt-client-paho/src/main/java/com/example/mqttclientpaho/properties/MqttProperties.java @@ -0,0 +1,60 @@ +package com.example.mqttclientpaho.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "mqtt") +public class MqttProperties { + + private String brokerUrl; + + private String clientId; + + private String username; + + private String password; + + + public String getBrokerUrl() { + return brokerUrl; + } + + public void setBrokerUrl(String brokerUrl) { + this.brokerUrl = brokerUrl; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public String toString() { + return "MqttProperties{" + + "brokerUrl='" + brokerUrl + '\'' + + ", clientId='" + clientId + '\'' + + ", username='" + username + '\'' + + ", password='" + password + '\'' + + '}'; + } +} diff --git a/project/mqtt/mqtt-client-paho/src/main/resources/application.yml b/project/mqtt/mqtt-client-paho/src/main/resources/application.yml new file mode 100644 index 00000000..ff65e1eb --- /dev/null +++ b/project/mqtt/mqtt-client-paho/src/main/resources/application.yml @@ -0,0 +1,12 @@ +server: + port: 8991 +spring: + application: + name: mqtt-client-paho + + +mqtt: + broker-url: tcp://192.168.1.231:1883 + client-id: emq-client-01 + username: user + password: 123456 \ No newline at end of file diff --git a/project/mqtt/mqtt-client-paho/src/test/java/com/example/mqttclientpaho/MqttClientPahoApplicationTests.java b/project/mqtt/mqtt-client-paho/src/test/java/com/example/mqttclientpaho/MqttClientPahoApplicationTests.java new file mode 100644 index 00000000..23ee7ccd --- /dev/null +++ b/project/mqtt/mqtt-client-paho/src/test/java/com/example/mqttclientpaho/MqttClientPahoApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.mqttclientpaho; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class MqttClientPahoApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git "a/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\2541\347\253\240 MQTT\345\215\217\350\256\256\344\270\216EMQ.pdf" "b/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\2541\347\253\240 MQTT\345\215\217\350\256\256\344\270\216EMQ.pdf" new file mode 100644 index 00000000..3a6f7503 Binary files /dev/null and "b/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\2541\347\253\240 MQTT\345\215\217\350\256\256\344\270\216EMQ.pdf" differ diff --git "a/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\2542\347\253\240 EMQ\345\237\272\347\241\200\345\212\237\350\203\275.pdf" "b/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\2542\347\253\240 EMQ\345\237\272\347\241\200\345\212\237\350\203\275.pdf" new file mode 100644 index 00000000..95df1222 Binary files /dev/null and "b/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\2542\347\253\240 EMQ\345\237\272\347\241\200\345\212\237\350\203\275.pdf" differ diff --git "a/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\254\344\270\211\347\253\240 \351\253\230\347\272\247\345\212\237\350\203\275\344\275\277\347\224\250(\344\270\200).pdf" "b/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\254\344\270\211\347\253\240 \351\253\230\347\272\247\345\212\237\350\203\275\344\275\277\347\224\250(\344\270\200).pdf" new file mode 100644 index 00000000..4e945ec2 Binary files /dev/null and "b/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\254\344\270\211\347\253\240 \351\253\230\347\272\247\345\212\237\350\203\275\344\275\277\347\224\250(\344\270\200).pdf" differ diff --git "a/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\254\344\272\224\347\253\240 EMQ\347\232\204\351\253\230\347\272\247\345\212\237\350\203\275\344\275\277\347\224\250(\344\270\211).pdf" "b/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\254\344\272\224\347\253\240 EMQ\347\232\204\351\253\230\347\272\247\345\212\237\350\203\275\344\275\277\347\224\250(\344\270\211).pdf" new file mode 100644 index 00000000..2c931354 Binary files /dev/null and "b/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\254\344\272\224\347\253\240 EMQ\347\232\204\351\253\230\347\272\247\345\212\237\350\203\275\344\275\277\347\224\250(\344\270\211).pdf" differ diff --git "a/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\254\345\233\233\347\253\240 EMQ\347\232\204\351\253\230\347\272\247\345\212\237\350\203\275\344\275\277\347\224\250(\344\272\214).pdf" "b/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\254\345\233\233\347\253\240 EMQ\347\232\204\351\253\230\347\272\247\345\212\237\350\203\275\344\275\277\347\224\250(\344\272\214).pdf" new file mode 100644 index 00000000..05f08ef9 Binary files /dev/null and "b/project/mqtt/mqtt-client-paho/\346\226\207\346\241\243/\347\254\254\345\233\233\347\253\240 EMQ\347\232\204\351\253\230\347\272\247\345\212\237\350\203\275\344\275\277\347\224\250(\344\272\214).pdf" differ diff --git a/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/MybatisPulsLearnApplication.java b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/MybatisPulsLearnApplication.java index 2acd157b..ecd52ac9 100644 --- a/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/MybatisPulsLearnApplication.java +++ b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/MybatisPulsLearnApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; +@EnableScheduling @SpringBootApplication //@MapperScan("com.cpq.mybatispulslearn.**.mapper") public class MybatisPulsLearnApplication { @@ -11,7 +13,5 @@ public static void main(String[] args) { SpringApplication.run(MybatisPulsLearnApplication.class, args); } - - } diff --git a/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/config/SpringDynamicCronTask.java b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/config/SpringDynamicCronTask.java new file mode 100644 index 00000000..20f955b0 --- /dev/null +++ b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/config/SpringDynamicCronTask.java @@ -0,0 +1,45 @@ +package com.cpq.mybatispulslearn.config; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.cpq.mybatispulslearn.linyi.third_to_cottage.entity.ThirdToCottage; +import com.cpq.mybatispulslearn.linyi.third_to_cottage.mapper.ThirdToCottageMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.Trigger; +import org.springframework.scheduling.TriggerContext; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; +import org.springframework.scheduling.support.CronTrigger; +import org.springframework.stereotype.Component; + +import java.util.Date; + + +@Slf4j +@Component +public class SpringDynamicCronTask implements SchedulingConfigurer { + + @Autowired + private ThirdToCottageMapper thirdToCottageMapper; + + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.addTriggerTask(new Runnable() { + @Override + public void run() { + // 任务逻辑 + log.debug("##############..."); + } + }, new Trigger() { + @Override + public Date nextExecutionTime(TriggerContext triggerContext) { + ThirdToCottage thirdToCottage = thirdToCottageMapper.selectOne(Wrappers.lambdaQuery()); + String cron = thirdToCottage.getThirdVillageCode(); + // 任务触发,可修改任务的执行周期 + CronTrigger trigger = new CronTrigger(cron); + Date nextExec = trigger.nextExecutionTime(triggerContext); + return nextExec; + } + }); + } +} diff --git a/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/config/TaskConfig.java b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/config/TaskConfig.java new file mode 100644 index 00000000..df1be960 --- /dev/null +++ b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/config/TaskConfig.java @@ -0,0 +1,21 @@ +package com.cpq.mybatispulslearn.config; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.cpq.mybatispulslearn.linyi.third_to_cottage.entity.ThirdToCottage; +import com.cpq.mybatispulslearn.linyi.third_to_cottage.mapper.ThirdToCottageMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class TaskConfig { + + @Autowired + private ThirdToCottageMapper thirdToCottageMapper; + + public String getCron() { + ThirdToCottage thirdToCottage = thirdToCottageMapper.selectOne(Wrappers.lambdaQuery()); + return thirdToCottage.getThirdVillageCode(); + } +} diff --git a/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/config/TestTask.java b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/config/TestTask.java new file mode 100644 index 00000000..6f9e5ba0 --- /dev/null +++ b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/config/TestTask.java @@ -0,0 +1,25 @@ +// package com.cpq.mybatispulslearn.config; +// +// import lombok.extern.slf4j.Slf4j; +// import org.springframework.beans.factory.annotation.Autowired; +// import org.springframework.scheduling.annotation.Scheduled; +// import org.springframework.stereotype.Component; +// +// import java.time.LocalDateTime; +// +// @Slf4j +// @Component +// public class TestTask { +// +// @Autowired +// private TaskConfig taskConfig; +// +// @Scheduled(cron = "#{@taskConfig.getCron() ?: '0 0/12 * * * ?'}") +// public void aaa() { +// LocalDateTime now = LocalDateTime.now(); +// log.info("{}################", now); +// } +// +// +// +// } diff --git a/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/linyi/third_to_cottage/controller/ThirdToCottageController.java b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/linyi/third_to_cottage/controller/ThirdToCottageController.java index 9ccaae99..02e4bd2e 100644 --- a/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/linyi/third_to_cottage/controller/ThirdToCottageController.java +++ b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/linyi/third_to_cottage/controller/ThirdToCottageController.java @@ -1,10 +1,16 @@ package com.cpq.mybatispulslearn.linyi.third_to_cottage.controller; - +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.cpq.mybatispulslearn.linyi.third_to_cottage.entity.ThirdToCottage; +import com.cpq.mybatispulslearn.linyi.third_to_cottage.mapper.ThirdToCottageMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; - import org.springframework.web.bind.annotation.RestController; +import java.util.List; + /** *

* 第三方系统、田丁后台的社区、房屋映射表 前端控制器 @@ -17,4 +23,15 @@ @RequestMapping("/third_to_cottage/thirdToCottage") public class ThirdToCottageController { + @Autowired + private ThirdToCottageMapper thirdToCottageMapper; + + @GetMapping("/test") + public String test() { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + List thirdToCottages = thirdToCottageMapper.selectList(lqw); + System.out.println(thirdToCottages); + return ""; + } + } diff --git a/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/linyi/third_to_cottage/entity/ThirdToCottage.java b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/linyi/third_to_cottage/entity/ThirdToCottage.java index fa4f8d2c..e834e176 100644 --- a/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/linyi/third_to_cottage/entity/ThirdToCottage.java +++ b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/linyi/third_to_cottage/entity/ThirdToCottage.java @@ -1,11 +1,11 @@ package com.cpq.mybatispulslearn.linyi.third_to_cottage.entity; -import com.baomidou.mybatisplus.annotation.TableField; -import java.io.Serializable; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; +import java.io.Serializable; + /** *

* 第三方系统、田丁后台的社区、房屋映射表 @@ -21,24 +21,6 @@ public class ThirdToCottage implements Serializable { private static final long serialVersionUID = 1L; - /** - * 社区id - */ - @TableField("villageID") - private Integer villageID; - - /** - * 社区楼栋id - */ - @TableField("buildingID") - private Integer buildingID; - - /** - * 社区房屋id - */ - @TableField("cottageID") - private Integer cottageID; - /** * 第三方社区code */ @@ -49,15 +31,4 @@ public class ThirdToCottage implements Serializable { */ private String thirdBuildingCode; - /** - * 第三方社区房屋code - */ - private String thirdCottageCode; - - /** - * 第三方系统:1-零壹系统 - */ - private Integer source; - - } diff --git a/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/linyi/third_to_cottage/mapper/ThirdToCottageMapper.xml b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/linyi/third_to_cottage/mapper/ThirdToCottageMapper.xml index e4d57084..a29757ef 100644 --- a/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/linyi/third_to_cottage/mapper/ThirdToCottageMapper.xml +++ b/project/mybatis-plus-learn/src/main/java/com/cpq/mybatispulslearn/linyi/third_to_cottage/mapper/ThirdToCottageMapper.xml @@ -4,18 +4,13 @@ - - - - - - villageID, buildingID, cottageID, third_village_code, third_building_code, third_cottage_code, source + third_village_code, third_building_code diff --git a/project/mybatis-plus-learn/src/main/resources/application.properties b/project/mybatis-plus-learn/src/main/resources/application.properties index 9fb7aae7..0c62a27e 100644 --- a/project/mybatis-plus-learn/src/main/resources/application.properties +++ b/project/mybatis-plus-learn/src/main/resources/application.properties @@ -7,7 +7,7 @@ spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driver-class-name = com.mysql.jdbc.Driver spring.datasource.url= jdbc:mysql://localhost:3306/cpq?useUnicode=true&useSSL=false&characterEncoding=utf8 spring.datasource.username=root -spring.datasource.password=poly2017 +spring.datasource.password=cpq..123 spring.datasource.initialSize=10 spring.datasource.maxActive=50 spring.datasource.minIdle=0 @@ -21,10 +21,6 @@ spring.datasource.poolPreparedStatements=false #spring.redis.port=6379 #spring.redis.database=0 -120.24.84.12 - - - #xml\u4F4D\u7F6E #mybatis-plus.mapper-locations=classpath:com/cpq/mybatispulslearn/**/mapper/*.xml #mybatis.mapper-locations=classpath:com/cpq/mybatispulslearn/**/mapper/*.xml diff --git a/project/rabbitmq/rabbitmq-springcloudstream-consumer/src/main/resources/application.properties b/project/rabbitmq/rabbitmq-springcloudstream-consumer/src/main/resources/application.properties index eafce7ee..76a1fe8c 100644 --- a/project/rabbitmq/rabbitmq-springcloudstream-consumer/src/main/resources/application.properties +++ b/project/rabbitmq/rabbitmq-springcloudstream-consumer/src/main/resources/application.properties @@ -2,9 +2,9 @@ server.port=8002 server.context-path=/consumer spring.application.name=consumer -#\u6307\u5B9A\u4EA4\u6362\u673A +#指定交换机 spring.cloud.stream.bindings.input_channel.destination=exchange-3 -#\u517C\u5BB9kafka\uFF0C\u7528group\u4EE3\u66FFqueue +#兼容kafka,用group代替queue spring.cloud.stream.bindings.input_channel.group=queue-3 spring.cloud.stream.bindings.input_channel.binder=rabbit_cluster spring.cloud.stream.bindings.input_channel.consumer.concurrency=1 @@ -13,7 +13,7 @@ spring.cloud.stream.rabbit.bindings.input_channel.consumer.acknowledge-mode=MANU spring.cloud.stream.rabbit.bindings.input_channel.consumer.recovery-interval=3000 spring.cloud.stream.rabbit.bindings.input_channel.consumer.durable-subscription=true spring.cloud.stream.rabbit.bindings.input_channel.consumer.max-concurrency=5 -#rabbit_cluster\u5C31\u662F\u4E0A\u9762\u5B9A\u4E49binder +#rabbit_cluster就是上面定义binder spring.cloud.stream.binders.rabbit_cluster.type=rabbit spring.cloud.stream.binders.rabbit_cluster.environment.spring.rabbitmq.addresses=localhost:5672 spring.cloud.stream.binders.rabbit_cluster.environment.spring.rabbitmq.username=guest diff --git a/project/rabbitmq/springboot-consumer01/src/main/java/com/coq/rabbitmq/sc01/conusmer/ReceiverAck.java b/project/rabbitmq/springboot-consumer01/src/main/java/com/coq/rabbitmq/sc01/conusmer/ReceiverAck.java index 9b48c443..c829d220 100644 --- a/project/rabbitmq/springboot-consumer01/src/main/java/com/coq/rabbitmq/sc01/conusmer/ReceiverAck.java +++ b/project/rabbitmq/springboot-consumer01/src/main/java/com/coq/rabbitmq/sc01/conusmer/ReceiverAck.java @@ -1,158 +1,47 @@ -// package com.coq.rabbitmq.sc01.conusmer; -// -// import com.coq.rabbitmq.sc01.bean.Order01; -// import com.fasterxml.jackson.databind.ObjectMapper; -// import com.rabbitmq.client.Channel; -// import lombok.extern.slf4j.Slf4j; -// import org.springframework.amqp.core.ExchangeTypes; -// import org.springframework.amqp.rabbit.annotation.*; -// import org.springframework.amqp.rabbit.core.RabbitTemplate; -// import org.springframework.amqp.support.AmqpHeaders; -// import org.springframework.beans.factory.annotation.Autowired; -// import org.springframework.messaging.Message; -// import org.springframework.messaging.MessageHeaders; -// import org.springframework.stereotype.Component; -// -// @Slf4j -// @Component -// public class ReceiverAck { -// -// @Autowired -// RabbitTemplate rabbitTemplate; -// -// /* -// 把RabbitAdmin设置成admin.setIgnoreDeclarationExceptions(true);,这样的好处是即使配置出现了错误也不至于整个应用程序都启动失败的情况。默认情况下,当出现异常时, RabbitAdmin 会立即停止所有声明的处理过程,这就有可能会导致一些问题- 如监听器容器会初始化失败,因另外的队列没有声明,从而web应用启动失败 -// -// # 重试 -// #是否开启消费者重试,消费端代码抛出异常,则重试 -// spring.rabbitmq.listener.simple.retry.enabled=true -// #最大重试次数 -// spring.rabbitmq.listener.simple.retry.max-attempts=6 -// #重试间隔时间(单位毫秒) -// spring.rabbitmq.listener.simple.retry.initial-interval=20000 -// #重试次数超过上面的设置之后是否丢弃(false不丢弃时需要写相应代码将该消息加入死信队列) -// #如果没加死信队列,即使设置为false,重试到达最大次数后也会被丢弃 -// spring.rabbitmq.listener.simple.default-requeue-rejected=false -// -// # 死信队列 -// 队列已经创建,但是没加死信队列;后期加上死信队列,会报错。 -// 解决办法一:删除队列重建 -// 解决办法二: -// 1、打开rabbitmq控制台 -> admin -> policies -> Add / update a policy -// Name: 这个Policy的名称 -// Pattern: Policy根据正则表达式去匹配Queues/Exchanges名称 -// Apply to: 这个Policy对Queue还是对Exchange生效,或者两者都适用 -// Priority: 优先级。 -// Definition: 添加的args,KV键值对。 -// 在控制台设置死信队列,key取消前缀x- 。以下是示例 -// Name Pattern Apply to Definition Priority -// dead-policy queue-ack queues dead-letter-exchange: dead_exchange -// dead-letter-routing-key: dead_key -// 消费者抛异常的现象: -// 1、执行消费端代码,执行抛出异常代码,但是没有异常堆栈 -// 2、进入errorHandler,执行errorHandler代码,但是不抛出异常,也没有异常堆栈 -// 3、重复执行 1、2 直到到达最大重试次数 -// 4、抛出异常,打印异常堆栈 -// 5、进入死信队列消费端代码 -// 创建queue时绑定死信列队需要以前缀x-开头,不以x-开头,就要让policy的Pattern能匹配到queue -// 在rabbitmq控制台给queue绑定死信队列,然后添加@Argument注解,key需要取消x- -// */ -// @RabbitListener( -// bindings = @QueueBinding( -// value = @Queue(value = "queue-ack", -// durable = "true", -// autoDelete = "false" -// // arguments = { -// // @Argument(name = "x-dead-letter-exchange", value = DEAD_EXCHANGE), -// // @Argument(name = "x-dead-letter-routing-key", value = DEAD_KEY) -// // } -// ), -// exchange = @Exchange(value = "exchange-ack", -// durable = "true", -// type = ExchangeTypes.DIRECT, -// autoDelete = "false", -// ignoreDeclarationExceptions = "true"), -// key = {"key-ack"}), -// errorHandler = "customListenerErrorHandler" -// ) -// @RabbitHandler -// public void onOrderMessage(Message message, Channel channel) throws Exception{ -// -// /** -// * Message总共有两种 -// * org.springframework.amqp.core.Message 包含较多信息,但是headers默认空的,不好用 -// * org.springframework.messaging.Message 包含 Payload、Headers -// * @Payload 如果使用javaBean,生产者、消费者 发送的javaBean必须是同一个类 -// */ -// // byte[] body = message.getBody(); -// // ObjectMapper objectMapper = new ObjectMapper(); -// // Order01 order01 = objectMapper.readValue(body, Order01.class); -// // System.out.println("消费端:"+order01.toString()); -// // -// // MessageProperties messageProperties = message.getMessageProperties(); -// // Long deliveryTag = messageProperties.getDeliveryTag(); -// // System.out.println("消费端deliveryTag:"+deliveryTag); -// -// /** -// * org.springframework.messaging.Message 不能修改header -// */ -// Object payload = message.getPayload(); -// ObjectMapper objectMapper = new ObjectMapper(); -// String payloadStr = payload instanceof String ? (String) payload : objectMapper.writeValueAsString(payload); -// Order01 order01 = objectMapper.readValue(payloadStr, Order01.class); -// System.out.println("消费端:"+order01.toString()); -// -// MessageHeaders headers = message.getHeaders(); -// Long deliveryTag = (Long)headers.get(AmqpHeaders.DELIVERY_TAG); -// System.out.println("消费端deliveryTag:"+deliveryTag); -// -// // 可以使用MESSAGE_ID作为消息标识,重回队列后MESSAGE_ID不变 -// String messageId = String.valueOf(headers.get(AmqpHeaders.MESSAGE_ID)); -// System.out.println("消费端messageId:"+messageId); -// -// -// /* -// channel.basicAck(deliveryTag, multiple); -// consumer处理成功后,通知broker删除队列中的消息,如果设置multiple=true,表示支持批量确认机制以减少网络流量。 -// 例如:有值为5,6,7,8 deliveryTag的投递 -// 如果此时channel.basicAck(8, true);则表示前面未确认的5,6,7投递也一起确认处理完毕。 -// 如果此时channel.basicAck(8, false);则仅表示deliveryTag=8的消息已经成功处理。 -// */ -// // channel.basicAck(deliveryTag, false); -// -// /* -// consumer处理失败后,例如:有值为5,6,7,8 deliveryTag的投递。 -// 如果channel.basicNack(8, true, true);表示deliveryTag=8之前未确认的消息都处理失败且将这些消息重新放回队列中。 -// 如果channel.basicNack(8, true, false);表示deliveryTag=8之前未确认的消息都处理失败且将这些消息直接丢弃。 -// 如果channel.basicNack(8, false, true);表示deliveryTag=8的消息处理失败且将该消息重新放回队列。 -// 如果channel.basicNack(8, false, false);表示deliveryTag=8的消息处理失败且将该消息直接丢弃。 -// */ -// // channel.basicNack(deliveryTag, false, true); -// /* -// spring.rabbitmq.listener.simple.acknowledge-mode=manual -// 手动ack确认,抛出异常 -// 1、不重试 -// 2、消息不会从队列移除 -// 3、消费端重启,再次消费消息 -// -// spring.rabbitmq.listener.simple.acknowledge-mode=auto // auto是默认值 -// 自动ack确认,,抛出异常 -// 1、一直重试 -// -// */ -// -// // if (order01.getId() > 100){ -// // // 签收 -// // channel.basicAck(deliveryTag, false); -// // }else { -// // // acknowledge-mode=manual -// // // 重回队列,会一直重试,retry.max-attempts无效 -// // channel.basicNack(deliveryTag, false, true); -// -// // acknowledge-mode=auto 抛异常导致的重试,重试期间阻塞消费端消费其他消息 -// throw new RuntimeException("抛异常"); -// // } -// -// } -// -// } + package com.coq.rabbitmq.sc01.conusmer; + + import com.coq.rabbitmq.sc01.bean.Order01; + import com.fasterxml.jackson.databind.ObjectMapper; + import com.rabbitmq.client.Channel; + import java.math.BigDecimal; + import java.util.Date; + import lombok.extern.slf4j.Slf4j; + import org.springframework.amqp.core.ExchangeTypes; + import org.springframework.amqp.rabbit.annotation.*; + import org.springframework.amqp.rabbit.core.RabbitTemplate; + import org.springframework.amqp.support.AmqpHeaders; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.messaging.Message; + import org.springframework.messaging.MessageHeaders; + import org.springframework.stereotype.Component; + + @Component + public class ReceiverAck { + + @Autowired + RabbitTemplate rabbitTemplate; + + /** + * 监听福讯支付通知 + */ + @RabbitListener(bindings = @QueueBinding( + exchange = @Exchange(value = "02q44LTW8O9Xb8uK_thing",type = "topic"), + key = "2R3qQJs42It.event.#", + value = @Queue(value = "02q44LTW8O9Xb8uK_ibms_b11") + ) + ) + public void basicDataListener(Message message, Channel channel) throws Exception { + MessageHeaders headers = message.getHeaders(); + Object deliveryTagObject = headers.get(AmqpHeaders.DELIVERY_TAG); + String payload = message.getPayload(); + System.out.println("收到福讯账单mq消息"); + System.out.println(deliveryTagObject); + System.out.println(payload); + + + //手动ack + // 签收 + //channel.basicAck(deliveryTag, false); + } + + } diff --git a/project/rabbitmq/springboot-consumer01/src/main/resources/application.properties b/project/rabbitmq/springboot-consumer01/src/main/resources/application.properties index 53d7a75c..d831c740 100644 --- a/project/rabbitmq/springboot-consumer01/src/main/resources/application.properties +++ b/project/rabbitmq/springboot-consumer01/src/main/resources/application.properties @@ -1,26 +1,26 @@ server.port=9091 -spring.rabbitmq.addresses=127.0.0.1:5672 -spring.rabbitmq.username=guest -spring.rabbitmq.password=guest -spring.rabbitmq.virtual-host=/ -spring.rabbitmq.connection-timeout=1500000000 +spring.rabbitmq.addresses=rabbitmq-iot.onewo.com:5672 +spring.rabbitmq.username=cq_b11 +spring.rabbitmq.password=GIL16JM7yZ8F +spring.rabbitmq.virtual-host=/ihw +#spring.rabbitmq.connection-timeout=1500000000 #最小消费者数量 -spring.rabbitmq.listener.simple.concurrency=1 +#spring.rabbitmq.listener.simple.concurrency=1 #最大消费者数量,如果消息一直重试,消费者会增加 -spring.rabbitmq.listener.simple.max-concurrency=2 +#spring.rabbitmq.listener.simple.max-concurrency=2 # ACK #none不发送ack。manual手工签收。auto自动ack(默认值) #spring.rabbitmq.listener.simple.acknowledge-mode=manual # 重试 -#是否开启消费者重试,消费端代码抛出异常,则重试 -spring.rabbitmq.listener.simple.retry.enabled=true -#最大重试次数 -spring.rabbitmq.listener.simple.retry.max-attempts=4 -#重试间隔时间(单位毫秒) -spring.rabbitmq.listener.simple.retry.initial-interval=20000 -#重试次数超过上面的设置之后是否丢弃(false不丢弃时需要写相应代码将该消息加入死信队列) -#如果没加死信队列,即使设置为false,重试到达最大次数后也会被丢弃 -spring.rabbitmq.listener.simple.default-requeue-rejected=false \ No newline at end of file +##是否开启消费者重试,消费端代码抛出异常,则重试 +#spring.rabbitmq.listener.simple.retry.enabled=true +##最大重试次数 +#spring.rabbitmq.listener.simple.retry.max-attempts=4 +##重试间隔时间(单位毫秒) +#spring.rabbitmq.listener.simple.retry.initial-interval=20000 +##重试次数超过上面的设置之后是否丢弃(false不丢弃时需要写相应代码将该消息加入死信队列) +##如果没加死信队列,即使设置为false,重试到达最大次数后也会被丢弃 +#spring.rabbitmq.listener.simple.default-requeue-rejected=false \ No newline at end of file diff --git a/project/rabbitmq/springboot-producer01/src/main/java/com/coq/rabbitmq/sp01/config/MyRabbitmqConfig.java b/project/rabbitmq/springboot-producer01/src/main/java/com/coq/rabbitmq/sp01/config/MyRabbitmqConfig.java index 798f6c96..051094f4 100644 --- a/project/rabbitmq/springboot-producer01/src/main/java/com/coq/rabbitmq/sp01/config/MyRabbitmqConfig.java +++ b/project/rabbitmq/springboot-producer01/src/main/java/com/coq/rabbitmq/sp01/config/MyRabbitmqConfig.java @@ -4,6 +4,7 @@ import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessagePostProcessor; import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitAdmin; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.support.CorrelationData; import org.springframework.context.annotation.Bean; @@ -17,6 +18,14 @@ public class MyRabbitmqConfig { // return new RabbitTemplate(); // } + @Bean + public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) { + RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory); + // 必须设置为true,不然在spring启动时不会注入到ioc + rabbitAdmin.setAutoStartup(true); + return rabbitAdmin; + } + /** * 设置回调 * @param connectionFactory @@ -80,4 +89,5 @@ public Message postProcessMessage(Message message) throws AmqpException { } }; } + } diff --git a/project/rabbitmq/springboot-producer01/src/main/java/com/coq/rabbitmq/sp01/notuserspringcomsumer/Consumer01.java b/project/rabbitmq/springboot-producer01/src/main/java/com/coq/rabbitmq/sp01/notuserspringcomsumer/Consumer01.java new file mode 100644 index 00000000..d761cfc5 --- /dev/null +++ b/project/rabbitmq/springboot-producer01/src/main/java/com/coq/rabbitmq/sp01/notuserspringcomsumer/Consumer01.java @@ -0,0 +1,27 @@ +package com.coq.rabbitmq.sp01.notuserspringcomsumer; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; + +import java.io.IOException; + +public class Consumer01 extends DefaultConsumer { + + + private Channel channel; + + public Consumer01(Channel channel) { + super(channel); + this.channel = channel; + } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + System.err.println("-----------consume message-11111---------body: " + new String(body)); + channel.basicAck(envelope.getDeliveryTag(), false); + } + + +} diff --git a/project/rabbitmq/springboot-producer01/src/main/java/com/coq/rabbitmq/sp01/notuserspringcomsumer/Consumer02.java b/project/rabbitmq/springboot-producer01/src/main/java/com/coq/rabbitmq/sp01/notuserspringcomsumer/Consumer02.java new file mode 100644 index 00000000..ad1ceddc --- /dev/null +++ b/project/rabbitmq/springboot-producer01/src/main/java/com/coq/rabbitmq/sp01/notuserspringcomsumer/Consumer02.java @@ -0,0 +1,27 @@ +package com.coq.rabbitmq.sp01.notuserspringcomsumer; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; + +import java.io.IOException; + +public class Consumer02 extends DefaultConsumer { + + + private Channel channel; + + public Consumer02(Channel channel) { + super(channel); + this.channel = channel; + } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + System.err.println("-----------consume message-222222222222---------body: " + new String(body)); + channel.basicAck(envelope.getDeliveryTag(), false); + } + + +} diff --git a/project/rabbitmq/springboot-producer01/src/main/java/com/coq/rabbitmq/sp01/notuserspringcomsumer/CreateConsumerController.java b/project/rabbitmq/springboot-producer01/src/main/java/com/coq/rabbitmq/sp01/notuserspringcomsumer/CreateConsumerController.java new file mode 100644 index 00000000..68a34030 --- /dev/null +++ b/project/rabbitmq/springboot-producer01/src/main/java/com/coq/rabbitmq/sp01/notuserspringcomsumer/CreateConsumerController.java @@ -0,0 +1,142 @@ +package com.coq.rabbitmq.sp01.notuserspringcomsumer; + + +import com.rabbitmq.client.Channel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitAdmin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

+ * ip网段对应地址 前端控制器 + *

+ * + * @author chenpiqian + * @since 2019-04-24 + */ +@RequestMapping("/test") +@Slf4j +@RestController +public class CreateConsumerController { + + public Map connMap = new ConcurrentHashMap<>(); + public Map adminMap = new ConcurrentHashMap<>(); + + // String exchangeName = "test.topic"; + // String routingKey = "test.device.test"; + + private CachingConnectionFactory connectionFactory(String username, String password, String virtualHost, String address) throws Exception{ + CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); + + connectionFactory.setUsername(username); + connectionFactory.setPassword(password); + connectionFactory.setVirtualHost(virtualHost); + + connectionFactory.setPublisherConfirms(true); + + //该方法配置多个host,在当前连接host down掉的时候会自动去重连后面的host + connectionFactory.setAddresses(address); + return connectionFactory; + } + + @GetMapping("/init/connection-admin") + public String initConnectionAdmin(String username, String password, String virtualHost, String address) throws Exception { + String key = address + virtualHost; + synchronized (this) { + if (!connMap.containsKey(key)) { + CachingConnectionFactory cachingConnectionFactory = connectionFactory(username, password, virtualHost, address); + connMap.put(key, cachingConnectionFactory); + } + if (!adminMap.containsKey(key)) { + CachingConnectionFactory cachingConnectionFactory = connMap.get(key); + RabbitAdmin rabbitAdmin = new RabbitAdmin(cachingConnectionFactory); + adminMap.put(key, rabbitAdmin); + } + } + return "OK"; + } + + + @GetMapping("/exchange") + public String exchange(String key, String exchangeName) throws Exception{ + TopicExchange topicExchange = new TopicExchange(exchangeName, true, false); + RabbitAdmin rabbitAdmin = adminMap.get(key); + rabbitAdmin.declareExchange(topicExchange); + return "OK"; + } + + @GetMapping("/listener") + public String listener(String key, String exchangeName, String routingKey, String testDeviceQueueName) throws Exception{ + // queue + HashMap arguments = new HashMap<>(); + arguments.put("x-message-ttl", 10000); + Queue testDeviceQueue = new Queue(testDeviceQueueName, true, false, false, arguments); + RabbitAdmin rabbitAdmin = adminMap.get(key); + rabbitAdmin.declareQueue(testDeviceQueue); + + // binding + TopicExchange topicExchange = new TopicExchange(exchangeName); + Binding binding = BindingBuilder.bind(testDeviceQueue).to(topicExchange).with(routingKey); + rabbitAdmin.declareBinding(binding); + CachingConnectionFactory connectionFactory = connMap.get(key); + synchronized (this) { + Channel channel = connectionFactory.createConnection().createChannel(false); + long count = channel.consumerCount(testDeviceQueueName); + System.out.println("######listener##consumerCount="+count); + if (count == 0) { + channel.basicConsume(testDeviceQueueName, new Consumer01(channel)); + } + } + return "OK"; + } + + @GetMapping("/listener2") + public String listener2(String key, String exchangeName, String routingKey, String testDeviceQueueName) throws Exception{ + // queue + HashMap arguments = new HashMap<>(); + arguments.put("x-message-ttl", 10000); + Queue testDeviceQueue = new Queue(testDeviceQueueName, true, false, false, arguments); + RabbitAdmin rabbitAdmin = adminMap.get(key); + rabbitAdmin.declareQueue(testDeviceQueue); + + // binding + TopicExchange topicExchange = new TopicExchange(exchangeName); + Binding binding = BindingBuilder.bind(testDeviceQueue).to(topicExchange).with(routingKey); + rabbitAdmin.declareBinding(binding); + + CachingConnectionFactory connectionFactory = connMap.get(key); + synchronized (this) { + Channel channel = connectionFactory.createConnection().createChannel(false); + long count = channel.consumerCount(testDeviceQueueName); + System.out.println("######listener2##consumerCount="+count); + if (count == 0) { + channel.basicConsume(testDeviceQueueName, new Consumer02(channel)); + } + } + return "OK"; + } + + + // @GetMapping("/send") + // public String send() throws Exception{ + // rabbitTemplate.convertAndSend(exchangeName, routingKey, "dafdasdfa3fsdfsfsdf", new MessagePostProcessor() { + // @Override + // public Message postProcessMessage(Message message) throws AmqpException { + // log.info("##############msg={}", message); + // return message; + // } + // }); + // return "OK"; + // } +} diff --git a/project/rabbitmq/springboot-producer01/src/main/resources/application.yaml b/project/rabbitmq/springboot-producer01/src/main/resources/application.yaml index 9e166b07..7e1e4a07 100644 --- a/project/rabbitmq/springboot-producer01/src/main/resources/application.yaml +++ b/project/rabbitmq/springboot-producer01/src/main/resources/application.yaml @@ -4,10 +4,10 @@ server: # mq都是用本地 spring: rabbitmq: - host: mqserverin.vanrui.com + host: 127.0.0.1 port: 5672 - username: chenpq05 - password: chenpq05 + username: guest + password: guest publisher-confirms: true virtual-host: / ## mq都是用本地 diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/pom.xml b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/pom.xml new file mode 100644 index 00000000..f00500bc --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/pom.xml @@ -0,0 +1,119 @@ + + + 4.0.0 + + + jar + + + com.cpq + SpringAI-MCP-RAG-Dev + 1.0-SNAPSHOT + + + mcp-client + + + 21 + 21 + UTF-8 + + + + + + org.springframework.ai + spring-ai-bom + 1.0.0 + pom + import + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-aop + + + + + + org.springframework.ai + spring-ai-starter-model-openai + + + + + org.springframework.ai + spring-ai-advisors-vector-store + + + org.springframework.ai + spring-ai-starter-vector-store-redis + + + + + + + + + org.springframework.ai + spring-ai-tika-document-reader + + + + + org.springframework.ai + spring-ai-starter-mcp-client + + + + org.projectlombok + lombok + true + + + + org.apache.commons + commons-lang3 + 3.17.0 + + + + org.apache.groovy + groovy-json + 4.0.27 + + + + cn.hutool + hutool-all + 5.8.30 + + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + + + + + \ No newline at end of file diff --git a/spring-cloud-learn/rabbitmq-hello/src/main/java/com/cpq/rabbitmqhello/RabbitmqHelloApplication.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/Application.java similarity index 58% rename from spring-cloud-learn/rabbitmq-hello/src/main/java/com/cpq/rabbitmqhello/RabbitmqHelloApplication.java rename to project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/Application.java index df0424e8..16c1847b 100644 --- a/spring-cloud-learn/rabbitmq-hello/src/main/java/com/cpq/rabbitmqhello/RabbitmqHelloApplication.java +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/Application.java @@ -1,12 +1,13 @@ -package com.cpq.rabbitmqhello; +package com.cpq; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class RabbitmqHelloApplication { +public class Application { public static void main(String[] args) { - SpringApplication.run(RabbitmqHelloApplication.class, args); + SpringApplication.run(Application.class, args); } + } diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/ChatEntity.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/ChatEntity.java new file mode 100644 index 00000000..1b76f0d6 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/ChatEntity.java @@ -0,0 +1,18 @@ +package com.cpq.bean; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Data +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class ChatEntity { + + private String currentUserName; + private String message; + private String botMsgId; + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/ChatResponseEntity.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/ChatResponseEntity.java new file mode 100644 index 00000000..8cc1fded --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/ChatResponseEntity.java @@ -0,0 +1,17 @@ +package com.cpq.bean; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Data +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class ChatResponseEntity { + + private String message; + private String botMsgId; + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/LeeResult.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/LeeResult.java new file mode 100644 index 00000000..15f3d44f --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/LeeResult.java @@ -0,0 +1,115 @@ +package com.cpq.bean; + +public class LeeResult { + + // 响应业务状态 + private Integer status; + + // 响应消息 + private String msg; + + // 响应中的数据 + private Object data; + + private String ok; // 不使用 + + public static LeeResult build(Integer status, String msg, Object data) { + return new LeeResult(status, msg, data); + } + + public static LeeResult build(Integer status, String msg, Object data, String ok) { + return new LeeResult(status, msg, data, ok); + } + + public static LeeResult ok(Object data) { + return new LeeResult(data); + } + + public static LeeResult ok() { + return new LeeResult(null); + } + + public static LeeResult errorMsg(String msg) { + return new LeeResult(500, msg, null); + } + + public static LeeResult errorUserTicket(String msg) { + return new LeeResult(557, msg, null); + } + + public static LeeResult errorMap(Object data) { + return new LeeResult(501, "error", data); + } + + public static LeeResult errorTokenMsg(String msg) { + return new LeeResult(502, msg, null); + } + + public static LeeResult errorException(String msg) { + return new LeeResult(555, msg, null); + } + + public static LeeResult errorUserQQ(String msg) { + return new LeeResult(556, msg, null); + } + + public LeeResult() { + + } + + public LeeResult(Integer status, String msg, Object data) { + this.status = status; + this.msg = msg; + this.data = data; + } + + public LeeResult(Integer status, String msg, Object data, String ok) { + this.status = status; + this.msg = msg; + this.data = data; + this.ok = ok; + } + + public LeeResult(Object data) { + this.status = 200; + this.msg = "OK"; + this.data = data; + } + + public Boolean isOK() { + return this.status == 200; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + public String getOk() { + return ok; + } + + public void setOk(String ok) { + this.ok = ok; + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/SearXNGResponse.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/SearXNGResponse.java new file mode 100644 index 00000000..f6d4dd6f --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/SearXNGResponse.java @@ -0,0 +1,19 @@ +package com.cpq.bean; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.util.List; + +@Data +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class SearXNGResponse { + + private String query; + private List results; + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/SearchResult.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/SearchResult.java new file mode 100644 index 00000000..0353427a --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/bean/SearchResult.java @@ -0,0 +1,19 @@ +package com.cpq.bean; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Data +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class SearchResult { + + private String title; + private String url; + private String content; + private double score; + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/config/CorsConfig.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/config/CorsConfig.java new file mode 100644 index 00000000..fce6a63f --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/config/CorsConfig.java @@ -0,0 +1,30 @@ +package com.cpq.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @ClassName CorsConfig + * @Author 风间影月 + * @Version 1.0 + * @Description CorsConfig + **/ +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Value("${website.domain}") + private String domain; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins(domain) + .allowedMethods("*") + .allowedHeaders("*") + .allowCredentials(true) + .maxAge(60 * 60); + + } +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/config/LLMConfig.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/config/LLMConfig.java new file mode 100644 index 00000000..0bab0ffa --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/config/LLMConfig.java @@ -0,0 +1,26 @@ +package com.cpq.config; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; +import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class LLMConfig { + + @Bean + public ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory, ToolCallbackProvider tools) { + /** + * defaultToolCallbacks(tools) 注入ToolCallbackProvider,使用mcp + * ChatClient配置defaultOptions可能导致无法使用远程mcp + */ + return ChatClient.builder(chatModel) + .defaultToolCallbacks(tools) + .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) + .build(); + } + +} \ No newline at end of file diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/config/OKHttpConfig.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/config/OKHttpConfig.java new file mode 100644 index 00000000..45baa50b --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/config/OKHttpConfig.java @@ -0,0 +1,20 @@ +package com.cpq.config; + +import okhttp3.OkHttpClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.concurrent.TimeUnit; + +@Configuration +public class OKHttpConfig { + + @Bean + public OkHttpClient okHttpClient() { + return new OkHttpClient.Builder() + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .build(); + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/config/RedisConfig.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/config/RedisConfig.java new file mode 100644 index 00000000..67dc485b --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/config/RedisConfig.java @@ -0,0 +1,49 @@ +package com.cpq.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@Slf4j +public class RedisConfig +{ + /** + * RedisTemplate配置 + * redis序列化的工具配置类,下面这个请一定开启配置 + * 127.0.0.1:6379> keys * + * 1) "ord:102" 序列化过 + * 2) "\xac\xed\x00\x05t\x00\aord:102" 野生,没有序列化过 + * this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法 + * this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法 + * this.redisTemplate.opsForSet(); //提供了操作set的所有方法 + * this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法 + * this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法 + * @param redisConnectionFactor + * @return + */ + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactor) + { + RedisTemplate redisTemplate = new RedisTemplate<>(); + + redisTemplate.setConnectionFactory(redisConnectionFactor); + //设置key序列化方式string + redisTemplate.setKeySerializer(new StringRedisSerializer()); + //设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化 + redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); + + redisTemplate.afterPropertiesSet(); + + return redisTemplate; + } +} + + diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/ChatController.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/ChatController.java new file mode 100644 index 00000000..0ddd30ca --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/ChatController.java @@ -0,0 +1,28 @@ +package com.cpq.controller; + +import com.cpq.bean.ChatEntity; +import com.cpq.service.ChatService; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +@RequestMapping("chat") +public class ChatController { + + + @Resource + private ChatService chatService; + + /** + * @Description: 聊天 + SSE返回 + */ + @PostMapping("doChat") + public void doChat(@RequestBody ChatEntity chatEntity){ + chatService.doChat(chatEntity); + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/InternetController.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/InternetController.java new file mode 100644 index 00000000..57dd75b1 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/InternetController.java @@ -0,0 +1,28 @@ +package com.cpq.controller; + +import com.cpq.bean.ChatEntity; +import com.cpq.service.SearXngService; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.bind.annotation.*; + + +@RestController +@RequestMapping("internet") +public class InternetController { + + @Resource + private SearXngService searXngService; + + @GetMapping("/test") + public Object test(@RequestParam("query") String query){ + return searXngService.search(query); + } + + @PostMapping("/search") + public void search(@RequestBody ChatEntity chatEntity, HttpServletResponse response){ + response.setCharacterEncoding("UTF-8"); + searXngService.doInternetSearch(chatEntity); + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/McpTestController.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/McpTestController.java new file mode 100644 index 00000000..f9cf7db7 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/McpTestController.java @@ -0,0 +1,45 @@ +package com.cpq.controller; + +import com.cpq.bean.ChatEntity; +import com.cpq.service.McpService; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/mcp-test") +public class McpTestController { + + @Resource + private McpService mcpService; + + + /** + 1、测试本地mcp文件服务、远程mcp高德地图服务,请求参数: + curl --location --request POST 'http://127.0.0.1:9090/mcp-test/doChat' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "currentUserName": 12456, + "message": "请在spring-ai-cmp目录下生成一个abc.html文件,把深圳一天旅游攻略写入到abc.html,使用好看的css+html来编写", + "botMsgId": 154545475454 + }' + 但是无法在本地创建文件,提示没有权限 + + 2、测试高德地图服务、发送邮件服务 + curl --location --request POST 'http://127.0.0.1:9090/mcp-test/doChat' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "currentUserName": 12456, + "message": "介绍一份杭州市一天的旅游攻略,并把旅游攻略发送到我的邮箱", + "botMsgId": 154545475454 + }' + + */ + @PostMapping("doChat") + public void doChat(@RequestBody ChatEntity chatEntity){ + mcpService.doChat(chatEntity); + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/RagController.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/RagController.java new file mode 100644 index 00000000..ab226c5b --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/RagController.java @@ -0,0 +1,40 @@ +package com.cpq.controller; + +import com.cpq.bean.ChatEntity; +import com.cpq.bean.LeeResult; +import com.cpq.service.RagService; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.ai.document.Document; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + + +@RestController +@RequestMapping("rag") +public class RagController { + + @Resource + private RagService ragService; + + @PostMapping("/uploadRagDoc") + public LeeResult uploadRagDoc(@RequestParam("file") MultipartFile file ){ + List documentList = ragService.loadText(file.getResource(), file.getOriginalFilename()); + return LeeResult.ok(documentList); + } + + @GetMapping("/doSearch") + public LeeResult doSearch(@RequestParam String question) { + return LeeResult.ok(ragService.vectorSearch(question)); + } + + @PostMapping("/search") + public void search(@RequestBody ChatEntity chatEntity, HttpServletResponse response) { + List list = ragService.vectorSearch(chatEntity.getMessage()); + response.setCharacterEncoding("UTF-8"); + ragService.doChatRagSearch(chatEntity, list); + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/SseEndpointController.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/SseEndpointController.java new file mode 100644 index 00000000..0e59cdc6 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/SseEndpointController.java @@ -0,0 +1,24 @@ +package com.cpq.controller; + +import com.cpq.sse.SseServer; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + + +@RestController +@RequestMapping("sse") +public class SseEndpointController { + + /** + * @Description: 前端发送连接的请求,连接SSE服务 + */ + @GetMapping(path = "connect", produces = {MediaType.TEXT_EVENT_STREAM_VALUE}) + public SseEmitter connect(@RequestParam String userId){ + return SseServer.connect(userId); + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/TestSseController.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/TestSseController.java new file mode 100644 index 00000000..99c93e62 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/controller/TestSseController.java @@ -0,0 +1,46 @@ +package com.cpq.controller; + +import com.cpq.enums.SSEMsgType; +import com.cpq.sse.SseServer; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +@RequestMapping("/test/sse") +public class TestSseController { + + + /** + * @Description: SSE发送单个消息 + */ + @GetMapping("sendMessage") + public Object sendMessage(@RequestParam String userId, @RequestParam String message){ + SseServer.sendMsg(userId, message, SSEMsgType.MESSAGE); + return "OK"; + } + + /** + * @Description: SSE发送单个消息 - add + */ + @GetMapping("sendMessageAdd") + public Object sendMessageAdd(@RequestParam String userId, @RequestParam String message) throws Exception { + for (int i = 0; i < 10; i++) { + Thread.sleep(200); + SseServer.sendMsg(userId, message, SSEMsgType.ADD); + } + return "OK"; + } + + /** + * @Description: SSE发送群消息 + */ + @GetMapping("sendMessageAll") + public Object sendMessageAll(@RequestParam String message){ + SseServer.sendMsgToAllUsers(message); + return "OK"; + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/enums/SSEMsgType.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/enums/SSEMsgType.java new file mode 100644 index 00000000..1235d4f1 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/enums/SSEMsgType.java @@ -0,0 +1,22 @@ +package com.cpq.enums; + +/** + * @Description: 发送SSE的消息类型 + */ +public enum SSEMsgType { + + MESSAGE("message", "单词发送的普通类型消息"), + ADD("add", "消息追加,适用于流式stream推送"), + FINISH("finish", "消息完成"), + CUSTOM_EVENT("custom_event", "单词发送的普通类型消息"), + DONE("done", "单词发送的普通类型消息"); // ChatGLM v4 + + public final String type; + public final String value; + + SSEMsgType(String type, String value) { + this.type = type; + this.value = value; + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/service/ChatService.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/service/ChatService.java new file mode 100644 index 00000000..f583dbf2 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/service/ChatService.java @@ -0,0 +1,48 @@ +package com.cpq.service; + +import cn.hutool.json.JSONUtil; +import com.cpq.bean.ChatEntity; +import com.cpq.bean.ChatResponseEntity; +import com.cpq.enums.SSEMsgType; +import com.cpq.sse.SseServer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; + +import java.util.List; +import java.util.stream.Collectors; + + +@Service +@Slf4j +public class ChatService{ + + @Autowired + private ChatClient chatClient; + + public void doChat(ChatEntity chatEntity) { + + String userId = chatEntity.getCurrentUserName(); + String prompt = chatEntity.getMessage(); + String botMsgId = chatEntity.getBotMsgId(); + + Flux stringFlux = chatClient.prompt(prompt).stream().content(); + + List list = stringFlux.toStream().map(str -> { + String content = str.toString(); + SseServer.sendMsg(userId, content, SSEMsgType.ADD); + log.info("content: {}", content); + return content; + }).collect(Collectors.toList()); + + String fullContent = list.stream().collect(Collectors.joining()); + + ChatResponseEntity chatResponseEntity = new ChatResponseEntity(fullContent, botMsgId); + + SseServer.sendMsg(userId, JSONUtil.toJsonStr(chatResponseEntity), SSEMsgType.FINISH); + + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/service/McpService.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/service/McpService.java new file mode 100644 index 00000000..83727a79 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/service/McpService.java @@ -0,0 +1,45 @@ +package com.cpq.service; + +import cn.hutool.json.JSONUtil; +import com.cpq.bean.ChatEntity; +import com.cpq.bean.ChatResponseEntity; +import com.cpq.enums.SSEMsgType; +import com.cpq.sse.SseServer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class McpService { + + @Autowired + private ChatClient chatClient; + + public void doChat(ChatEntity chatEntity) { + + String userId = chatEntity.getCurrentUserName(); + String prompt = chatEntity.getMessage(); + String botMsgId = chatEntity.getBotMsgId(); + log.info("#############prompt{}", prompt); + Flux stringFlux = chatClient.prompt(prompt).stream().content(); + List list = stringFlux.toStream().map(str -> { + String content = str.toString(); + SseServer.sendMsg(userId, content, SSEMsgType.ADD); + log.info("content: {}", content); + return content; + }).collect(Collectors.toList()); + log.info("#############list={}", list); + String fullContent = list.stream().collect(Collectors.joining()); + + ChatResponseEntity chatResponseEntity = new ChatResponseEntity(fullContent, botMsgId); + + SseServer.sendMsg(userId, JSONUtil.toJsonStr(chatResponseEntity), SSEMsgType.FINISH); + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/service/RagService.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/service/RagService.java new file mode 100644 index 00000000..19fe1dfe --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/service/RagService.java @@ -0,0 +1,123 @@ +package com.cpq.service; + +import cn.hutool.json.JSONUtil; +import com.cpq.bean.ChatEntity; +import com.cpq.bean.ChatResponseEntity; +import com.cpq.enums.SSEMsgType; +import com.cpq.sse.SseServer; +import com.cpq.util.CustomTextSplitter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.prompt.PromptTemplate; +import org.springframework.ai.document.Document; +import org.springframework.ai.reader.TextReader; +import org.springframework.ai.vectorstore.redis.RedisVectorStore; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class RagService { + + @Autowired + private RedisVectorStore redisVectorStore; + @Autowired + private ChatClient chatClient; + + public List loadText(Resource resource, String fileName) { + + // 加载读取文档 + TextReader textReader = new TextReader(resource); + textReader.getCustomMetadata().put("fileName", fileName); + List documentList = textReader.get(); + +// System.out.println("documentList = " + documentList); + +// 默认的文本切分器 +// TokenTextSplitter tokenTextSplitter = new TokenTextSplitter(); +// List list = tokenTextSplitter.apply(documentList); + + CustomTextSplitter tokenTextSplitter = new CustomTextSplitter(); + List list = tokenTextSplitter.apply(documentList); + + System.out.println("list = " + list); + + // 向量存储 + int batchSize = 10; + for (int i = 0; i < list.size(); i += batchSize) { + int endIndex = Math.min(i + batchSize, list.size()); + List batch = list.subList(i, endIndex); + redisVectorStore.add(batch); + } + + return documentList; + } + + public List vectorSearch(String question) { + return redisVectorStore.similaritySearch(question); + } + + + // Dify 智能体引擎构建平台 + + private static final String RAG_PROMPT = """ + 基于上下文的知识库内容回答问题: + 【上下文】 + {context} + + 【问题】 + {question} + + 【输出】 + 如果没有查到,请回复:不知道。 + 如果查到,请回复具体的内容。不相关的近似内容不必提到。 + """; + + public void doChatRagSearch(ChatEntity chatEntity, List documents) { + + String userId = chatEntity.getCurrentUserName(); + String question = chatEntity.getMessage(); + String botMsgId = chatEntity.getBotMsgId(); + + // 构建提示词 + String context = null; + if (CollectionUtils.isNotEmpty(documents)) { + context = documents.stream() + .map(Document::getText) + .collect(Collectors.joining("\n")); + } + + // 组装提示词 + PromptTemplate promptTemplate = new PromptTemplate(RAG_PROMPT); + Map map = Map.of("context", context, + "question", question); + Prompt prompt = promptTemplate.create(map); + + log.info("{}", prompt); + + Flux stringFlux = chatClient.prompt(prompt).stream().content(); + + List list = stringFlux.toStream().map(chatResponse -> { + String content = chatResponse.toString(); + SseServer.sendMsg(userId, content, SSEMsgType.ADD); + log.info("content: {}", content); + return content; + }).collect(Collectors.toList()); + + String fullContent = list.stream().collect(Collectors.joining()); + + ChatResponseEntity chatResponseEntity = new ChatResponseEntity(fullContent, botMsgId); + + SseServer.sendMsg(userId, JSONUtil.toJsonStr(chatResponseEntity), SSEMsgType.FINISH); + + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/service/SearXngService.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/service/SearXngService.java new file mode 100644 index 00000000..edd4150c --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/service/SearXngService.java @@ -0,0 +1,149 @@ +package com.cpq.service; + +import cn.hutool.json.JSONUtil; +import com.cpq.bean.ChatEntity; +import com.cpq.bean.ChatResponseEntity; +import com.cpq.bean.SearXNGResponse; +import com.cpq.bean.SearchResult; +import com.cpq.enums.SSEMsgType; +import com.cpq.sse.SseServer; +import lombok.extern.slf4j.Slf4j; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; + +import java.io.IOException; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class SearXngService { + + @Value("${internet.websearch.searxng.url}") + private String SEARXNG_URL; + + @Value("${internet.websearch.searxng.counts}") + private Integer COUNTS; + + @Autowired + private OkHttpClient okHttpClient; + @Autowired + private ChatClient chatClient; + + public List search(String query) { + // 构建url + HttpUrl url = HttpUrl.get(SEARXNG_URL) + .newBuilder() + .addQueryParameter("q", query) + .addQueryParameter("format", "json") + .build(); + + log.info("搜索的url地址为:" + url.url()); + + // 构建request + Request request = new Request.Builder() + .url(url) + .build(); + + // 发送请求 + try (Response response = okHttpClient.newCall(request).execute()) { + + // 判断请求是否成功还是失败 + if (!response.isSuccessful()) throw new RuntimeException("请求失败: HTTP " + response.code()); + + // 获得响应的数据 + if (response.body() != null) { + String responseBody = response.body().string(); + + SearXNGResponse searXNGResponse = JSONUtil.toBean(responseBody, SearXNGResponse.class); + + return dealResults(searXNGResponse.getResults()); + } + log.error("搜索失败:{}", response.message()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return Collections.emptyList(); + } + + private List dealResults(List results) { + + return results.subList(0, Math.min(COUNTS, results.size())) + .parallelStream() + .sorted(Comparator.comparingDouble(SearchResult::getScore).reversed()) + .limit(COUNTS).toList(); + } + + + private static final String SESRXNG_PROMPT = """ + 你是一个互联网搜索大师,请基于以下互联网返回的结果作为上下文,根据你的理解结合用户的提问综合后,生成并且输出专业的回答: + 【上下文】 + {context} + + 【问题】 + {question} + + 【输出】 + 如果没有查到,请回复:不知道。 + 如果查到,请回复具体的内容。 + """; + + public void doInternetSearch(ChatEntity chatEntity) { + + String userId = chatEntity.getCurrentUserName(); + String question = chatEntity.getMessage(); + String botMsgId = chatEntity.getBotMsgId(); + + List searchResults = this.search(question); + + String finalPrompt = buildSesrXngPrompt(question, searchResults); + + // 组装提示词 + Prompt prompt = new Prompt(finalPrompt); + + System.out.println(prompt.toString()); + + Flux stringFlux = chatClient.prompt(prompt).stream().content(); + + List list = stringFlux.toStream().map(chatResponse -> { + String content = chatResponse.toString(); + SseServer.sendMsg(userId, content, SSEMsgType.ADD); + log.info("content: {}", content); + return content; + }).collect(Collectors.toList()); + + String fullContent = list.stream().collect(Collectors.joining()); + + ChatResponseEntity chatResponseEntity = new ChatResponseEntity(fullContent, botMsgId); + + SseServer.sendMsg(userId, JSONUtil.toJsonStr(chatResponseEntity), SSEMsgType.FINISH); + } + + private static String buildSesrXngPrompt(String question, List searchResults) { + + StringBuilder context = new StringBuilder(); + + searchResults.forEach(searchResult -> { + context.append( + String.format("\n[来源] %s \n [摘要] %s \n \n", + searchResult.getUrl(), + searchResult.getContent())); + }); + + return SESRXNG_PROMPT + .replace("{context}", context) + .replace("{question}", question); + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/sse/SseServer.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/sse/SseServer.java new file mode 100644 index 00000000..17fa9fe2 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/sse/SseServer.java @@ -0,0 +1,118 @@ +package com.cpq.sse; + +import com.cpq.enums.SSEMsgType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.CollectionUtils; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +@Slf4j +public class SseServer { + + // 存放所有用户 + private static final Map sseClients = new ConcurrentHashMap<>(); + + /** + * @Description: 连接SSE服务 + */ + public static SseEmitter connect(String userId) { + + // 设置超时时间,0L表示不超时(永不过期);默认是30秒,超时未完成任务则会抛出异常 + SseEmitter sseEmitter = new SseEmitter(10 * 24 * 60 * 60 * 1000L); + + // 注册回调方法 + sseEmitter.onTimeout(timeoutCallback(userId)); + sseEmitter.onCompletion(completionCallback(userId)); + sseEmitter.onError(errorCallback(userId)); + + sseClients.put(userId, sseEmitter); + + log.info("SSE连接创建成功,连接的用户ID为:{}", userId); + + return sseEmitter; + } + + public static Runnable timeoutCallback(String userId) { + return () -> { + log.info("SSE超时..."); + completeAndRemove(userId); + }; + } + + public static Runnable completionCallback(String userId) { + return () -> { + log.info("SSE完成..."); + // 移除用户连接 + remove(userId); + }; + } + + public static Consumer errorCallback(String userId) { + return Throwable -> { + log.error("SSE异常..."); + completeAndRemove(userId); + }; + } + + public static void remove(String userId) { + // 删除用户 + if (sseClients.containsKey(userId)) { + sseClients.remove(userId); + log.info("SSE连接被移除,移除的用户ID为:{}", userId); + } + } + + public static void completeAndRemove(String userId) { + SseEmitter sseEmitter = sseClients.get(userId); + if (sseEmitter != null) { + sseEmitter.complete(); + // 移除用户连接 + sseClients.remove(userId); + log.info("SSE连接被移除,移除的用户ID为:{}", userId); + } + } + + public static void sendMsg(String userId, String message, SSEMsgType msgType) { + SseEmitter sseEmitter = sseClients.get(userId); + if (sseEmitter == null) { + log.debug("sseEmitter为空,userId={}", userId); + return; + } + sendEmitterMessage(sseEmitter, userId, message, msgType); + } + + private static void sendEmitterMessage(SseEmitter sseEmitter, + String userId, + String message, + SSEMsgType msgType) { + try { + SseEmitter.SseEventBuilder msgEvent = SseEmitter.event() + .id(userId) + .data(message) + .name(msgType.type); + sseEmitter.send(msgEvent); + } catch (IOException e) { + log.error("SSE异常...", e); + remove(userId); + } catch (Exception e) { + log.error("SSE异常Exception...", e); + } + } + + public static void sendMsgToAllUsers(String message) { + if (CollectionUtils.isEmpty(sseClients)) { + return; + } + + sseClients.forEach((userId, sseEmitter) -> { + sendEmitterMessage(sseEmitter, userId, message, SSEMsgType.MESSAGE); + } + ); + } + + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/util/CustomTextSplitter.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/util/CustomTextSplitter.java new file mode 100644 index 00000000..64a66d27 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/java/com/cpq/util/CustomTextSplitter.java @@ -0,0 +1,17 @@ +package com.cpq.util; + +import org.springframework.ai.transformer.splitter.TextSplitter; + +import java.util.List; + +public class CustomTextSplitter extends TextSplitter { + + @Override + protected List splitText(String text) { + return List.of(split(text)); + } + + public String[] split(String text) { + return text.split("\\s*\\R\\s*\\R\\s*"); + } +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/resources/application-dev.yml b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/resources/application-dev.yml new file mode 100644 index 00000000..8f5a7372 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/resources/application-dev.yml @@ -0,0 +1,5 @@ +server: + port: 9090 + +website: + domain: http://127.0.0.1:5501 \ No newline at end of file diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/resources/application.yml b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/resources/application.yml new file mode 100644 index 00000000..c7e482b5 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/resources/application.yml @@ -0,0 +1,55 @@ +spring: + application: + name: spring-ai-mcp-client + profiles: + active: dev + data: + redis: + host: 192.168.1.221 + port: 6379 + ai: + mcp: + client: + enabled: true + name: spring-ai-mcp-client + request-timeout: 60s + type: ASYNC + sse: + connections: + # 通过sse使用远程mcp服务,高德地图mcp服务 + server1: + url: https://mcp.amap.com + sse-endpoint: /sse?key=bfd7f79a957f12846678ed71b420565c + # 是用本地开发的mcp服务 + server2: + url: http://localhost:9060 + sse-endpoint: /sse + stdio: + # 本地通过stdio使用mcp服务 + servers-configuration: classpath:mcp-server.json + openai: + api-key: ${ALI_AI_KEY} + base-url: https://dashscope.aliyuncs.com/compatible-mode + chat: + options: + model: qwen-plus + embedding: + enabled: true + options: + model: text-embedding-v4 + vectorstore: + redis: + initialize-schema: true # 是否初始化所需的模式 + index-name: lee-vectorstore # 用于存储向量的索引名称 + prefix: 'embedding:' # Redis 键的前缀 + +logging: + level: + root: info + +# SearXNG联网搜索 +internet: + websearch: + searxng: + url: http://192.168.1.221:8888/search + counts: 10 \ No newline at end of file diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/resources/mcp-server.json b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/resources/mcp-server.json new file mode 100644 index 00000000..8427b7af --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/src/main/resources/mcp-server.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "filesystem": { + "command": "cmd", + "args": ["/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "/spring-ai-cmp"] + } + } +} \ No newline at end of file diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/test.txt b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/test.txt new file mode 100644 index 00000000..f49a2815 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-client/test.txt @@ -0,0 +1,316 @@ +Deepseek R1 是⽀持复杂推理、多模态处理、技术⽂档⽣成的⾼性能通⽤⼤语⾔模型。本⼿册 +为技术团队提供完整的本地部署指南,涵盖硬件配置、国产芯⽚适配、量化⽅案、云端替代⽅ +案及完整671B MoE模型的Ollama部署⽅法。模型 参数规 +模 +计算精 +度 +最低显存需 +求 最低算⼒需求 +DeepSeek-R1 (671B) 671B FP8 ≥890GB +2*XE9680(16*H20 +GPU) +DeepSeek-R1-Distill- +70B +70B BF16 ≥180GB 4*L20 或 2*H20 GPU +三、国产芯⽚与硬件适配⽅案 +1. 国内⽣态合作伙伴动态 +企业 适配内容 性能对标(vs +NVIDIA) +华为昇 +腾 +昇腾910B原⽣⽀持R1全系列,提供端到端推理优化 +⽅案 等效A100(FP16) +沐曦 +GPU +MXN系列⽀持70B模型BF16推理,显存利⽤率提升 +30% 等效RTX 3090 +海光 +DCU 适配V3/R1模型,性能对标NVIDIA A100 等效A100(BF16) +2. 国产硬件推荐配置 +模型参数 推荐⽅案 适⽤场景 +1.5B 太初T100加速卡 个⼈开发者原型验证 +14B 昆仑芯K200集群 企业级复杂任务推理 +32B 壁彻算⼒平台+昇腾910B集群 科研计算与多模态处理 +12月26日,Deepseek发布了全新系列模型DeepSeek-v3,一夜之间霸榜开源模型,并在性能上和世界顶尖的闭源模型GPT-4o以及 Claude-3.5-Sonnet相提并论。 + +该模型为MOE架构,大大降低了训练成本,据说训练成本仅600万美元,成本降低10倍,资源运用效率极高。有AI投资机构负责人直言,DeepSeek发布的53页的技术论文是黄金。 + +那就先让我们看看论文是怎么说的吧,老规矩,先上资源地址: + +Github: GitHub - deepseek-ai/DeepSeek-V3 + +模型地址:https://huggingface.co/deepseek-ai + +论文地址:https://github.com/deepseek-ai/DeepSeek-V3/blob/main/DeepSeek_V3.pdf + +以下为技术解读: + +前言 +DeepSeek-AI 发布了其最新的大型语言模型 DeepSeek-V3,这款模型在性能和效率方面都取得了显著的进步,成为当前最强大的开源基础模型之一。DeepSeek-V3 是一款拥有 671B参数的大型混合专家 (MoE) 模型,其中每个 token 会有 37 B参数被激活。 + + +为了实现高效的推理和成本效益的训练,DeepSeek-V3 采用了多头潜在注意力 (MLA) 和 DeepSeekMoE 架构,这两个架构在 DeepSeek-V2 中已经得到了充分验证。此外,DeepSeek-V3 还开创了一种无辅助损失策略来平衡负载,并设置了多 token 预测训练目标以进一步提升性能。 + + +架构:创新负载平衡策略和训练目标 +基本架构 +DeepSeek-V3 的基本架构仍然基于 Transformer 框架,但其采用了 MLA 和 DeepSeekMoE 架构来实现高效推理和成本效益的训练。 + + +多头潜在注意力 (MLA) + +MLA 架构的核心思想是对注意力键和值进行低秩联合压缩,从而减少推理过程中的 Key-Value (KV) 缓存。它通过以下步骤实现: + +压缩: 将注意力输入 h_t 映射到一个压缩的潜在向量 c_KV_t。 + +生成键: 使用 W_UK 和 W_VU 矩阵将 c_KV_t 映射到压缩的键和值。 + +生成解码器: 使用 RoPE 矩阵生成带有旋转位置嵌入 (RoPE) 的解码器。 + +计算注意力: 使用 softmax 函数计算注意力权重,并生成最终的注意力输出 u_t。 + +MLA 架构只需要缓存压缩后的潜在向量和带有 RoPE 的解码器,从而显著减少了 KV 缓存,同时保持了与标准多头注意力 (MHA) 相当的性能。 + +DeepSeekMoE:辅助损失免费负载平衡 +DeepSeekMoE 架构使用更细粒度的专家,并将一些专家隔离为共享专家。每个 token 的 FFN 输出 h’_t 通过以下步骤计算: + +共享专家: 使用共享专家 FFN( ) (·) 计算共享专家的输出。 + +路由专家: 使用路由专家 FFN( ) (·) 计算路由专家的输出,并使用门控值 g_i,t 选择激活的专家。 + +输出: 将共享专家和路由专家的输出相加,得到最终的 FFN 输出 h’_t。 + +DeepSeek-V3 还引入了一种辅助损失免费负载平衡策略,通过引入偏置项 b_i 并将其添加到相应的亲和度分数 s_i,t 中,来确定 top-K 路由。通过动态调整偏置项,DeepSeek-V3 能够在整个训练过程中保持平衡的专家负载,并取得比纯粹使用辅助损失的模型更好的性能。 + +多 token 预测 +DeepSeek-V3 采用了一种名为多 token 预测 (MTP) 的训练目标,该目标扩展了预测范围,以便在每个位置预测多个未来的 token。MTP 目标可以提高数据效率和模型的预测能力,并通过预先规划未来的 token 的表示来提升性能。 + +MTP 实现了 D 个连续的模块来预测 D 个额外的 token,每个模块都包含一个共享嵌入层、一个共享输出头、一个 Transformer 模块和一个投影矩阵。每个 MTP 模块都使用线性投影将 token 的表示和嵌入相连接,然后通过 Transformer 模块生成输出表示,并计算额外的预测 token 的概率分布。 + +基础设施:高效训练的基石 +DeepSeek-V3 的训练过程依赖于高效的计算集群和训练框架。 + + +计算集群 +DeepSeek-V3 在一个配备 2048 个 NVIDIA H800 GPU 的集群上进行训练。每个节点包含 8 个 GPU,通过 NVLink 和 NVSwitch 相互连接。跨节点之间使用 InfiniBand (IB) 进行通信。 + + +训练框架 +DeepSeek-V3 的训练框架基于 HAI-LLM 框架,该框架为高效训练提供了强大的支持。DeepSeek-V3 应用了 16 路 Pipeline Parallelism (PP)、64 路 Expert Parallelism (EP) 和 ZeRO-1 Data Parallelism (DP)。 + +双向管道并行 (DualPipe) + +为了解决跨节点专家并行导致的通信开销问题,DeepSeek-V3 设计了一种名为 DualPipe 的新型管道并行算法。DualPipe 通过重叠正向和反向计算通信阶段,不仅提高了模型训练速度,还减少了管道气泡的数量。 + +跨节点全连接通信 + +DeepSeek-V3 开发了高效的跨节点全连接通信内核,以充分利用 IB 和 NVLink 的带宽,并节省专门用于通信的 Streaming Multiprocessors (SMs)。 + +极低的内存占用 + +DeepSeek-V3 通过以下技术来降低训练过程中的内存占用: + +RMSNorm 和 MLA 上投影的重新计算: 在反向传播过程中重新计算所有 RMSNorm 操作和 MLA 上投影,从而消除了永久存储其输出激活的需求。 + +CPU 上的指数移动平均: 在训练过程中保存模型参数的指数移动平均 (EMA),用于早期估计模型性能,并异步更新 EMA 参数,从而避免额外的内存和时间开销。 + +多 token 预测中的共享嵌入和输出头: 利用 DualPipe 策略,将模型的最浅层和最深层部署在同一个 PP 路径上,从而实现共享嵌入和输出头的参数和梯度,进一步提高内存效率。 + +FP8 训练 +DeepSeek-V3 支持使用 FP8 数据格式进行混合精度训练,以实现加速训练和降低 GPU 内存使用。 + + +混合精度框架 + +混合精度框架使用 FP8 格式进行大多数计算密集型操作,而一些关键操作则保留其原始数据格式,以平衡训练效率和数值稳定性。 + + +量化精度提升 + +为了提高低精度训练的精度,DeepSeek-V3 引入了几种策略: + +细粒度量化: 将激活和权重分组并分别进行缩放,以更好地适应异常值。 + +增加累积精度: 将部分结果复制到 FP32 寄存器中进行全精度累积,以提高精度。 + +尾数超过指数: 采用 E4M3 格式,即 4 位指数和 3 位尾数,以提高精度。 + +低精度存储和通信 + +DeepSeek-V3 通过以下方式进一步降低内存和通信开销: + +低精度优化器状态: 使用 BF16 格式跟踪 AdamW 优化器的第一和第二矩。 +低精度激活: 使用 FP8 格式缓存 Linear 操作的激活,并对一些关键激活使用 E5M6 格式,或重新计算其输出。 +低精度通信: 将激活在 MoE 上投影之前量化为 FP8,并使用调度组件,与 MoE 上投影中的 FP8 Fprop 兼容。 +预训练:迈向终极训练效率 +DeepSeek-V3 在一个包含 14.8 万亿高质量和多样化 token 的语料库上进行预训练。预训练过程非常稳定,没有遇到不可恢复的损失峰值或需要回滚的情况。 + +数据构建 +预训练语料库经过优化,数学和编程样本的比例更高,并扩展了多语言覆盖范围,包括英语和中文。数据处理流程也得到了改进,以减少冗余并保持语料库的多样性。 + +超参数设置 +DeepSeek-V3 的超参数包括 Transformer 层数、隐藏维度、注意力头数、每头维度、KV 压缩维度、查询压缩维度、RoPE 维度、MoE 层数、共享专家数量、路由专家数量、中间隐藏维度、激活专家数量、节点限制路由数量、多 token 预测深度、学习率、批大小等。 + + +长上下文扩展 +DeepSeek-V3 采用与 DeepSeek-V2 相似的方法来启用长上下文功能。在预训练阶段之后,应用 YaRN 进行上下文扩展,并进行两个额外的训练阶段,将上下文窗口逐步扩展到 32K 和 128K。 + +评估 +DeepSeek-V3 在一系列基准测试中进行了评估,包括多学科多项选择题、语言理解和推理、闭卷问答、阅读理解、参考消歧、语言模型、中文理解和文化、数学、代码和标准化考试等。DeepSeek-V3 在大多数基准测试中都取得了最强大的性能,尤其是在数学和代码任务上。 + + +讨论 +DeepSeek-V3 中的 MTP 策略和多 token 预测策略都取得了显著的性能提升。辅助损失免费负载平衡策略也取得了更好的性能,并且专家具有更强的专业模式。与序列级辅助损失相比,批量级负载平衡方法也表现出一致的效率优势,但其也面临着潜在的挑战,例如序列或小批量中的负载不平衡以及推理过程中域转换引起的负载不平衡。 + + + +后训练:知识蒸馏与强化学习 +DeepSeek-V3 通过监督微调和强化学习进行后训练,以使其与人类偏好保持一致并进一步释放其潜力。 + +监督微调(Supervised Fine-Tuning ) +DeepSeek-V3 使用一个包含 150 万个实例的数据集进行监督微调,涵盖了多个领域。对于推理相关的数据集,例如数学、代码竞赛问题和逻辑谜题,使用内部 DeepSeek-R1 模型生成数据。对于非推理数据,例如创意写作、角色扮演和简单问答,使用 DeepSeek-V2.5 生成。并通过拒绝抽样方法筛选高质量数据,以确保最终训练数据的准确性和简洁性。 + +SFT 设置:DeepSeek-V3 使用余弦退火学习率调度进行两个 epoch 的训练,初始学习率为 5 × 10^-6,并逐渐降低到 1 × 10^-6。在训练过程中,每个序列由多个样本打包,并使用样本掩码策略确保这些示例保持隔离并相互不可见。 + +强化学习 +DeepSeek-V3 采用基于规则的奖励模型 (RM) 和基于模型的 RM 来确定模型的反馈。对于可以验证的特定规则的问题,使用基于规则的奖励系统来确定反馈。对于具有自由格式真实答案的问题,使用奖励模型来确定答案是否与预期的真实答案匹配。对于没有明确真实答案的问题,奖励模型负责根据问题和答案提供反馈。 + +DeepSeek-V3 使用组相对策略优化 (GRPO) 进行强化学习,该优化方法放弃了与策略模型相同大小的评论模型,而是从组分数中估计基线。在 RL 过程中,模型使用高温采样生成包含来自 DeepSeek-R1 生成数据和原始数据的模式的响应,即使在缺乏明确系统提示的情况下也能做到。 + +评估 +DeepSeek-V3 在一系列基准测试中进行了评估,包括 IFEval、FRAMES、LongBench v2、GPQA、SimpleQA、C-SimpleQA、SWE-Bench Verified、Aider 1、LiveCodeBench、Codeforces、中国高中数学奥林匹克 (CNMO) 2024 和美国邀请数学考试 (AIME) 2024 等。DeepSeek-V3 在大多数基准测试中都取得了最强大的性能,尤其是在代码、数学和长上下文理解任务上。 + + + + +讨论 +DeepSeek-V3 从 DeepSeek-R1 系列模型中蒸馏推理能力取得了成功,显著提高了其在数学和代码基准测试中的性能。同时,DeepSeek-V3 还采用了宪法 AI 方法,利用 DeepSeek-V3 自身的投票评估结果作为反馈来源,进一步提高了其在主观评估中的性能。 + +DeepSeek-V3 中的多 token 预测技术可以显著加速模型的解码速度,而额外的预测 token 的接受率在 85% 到 90% 之间,这表明其具有高度的可靠性。 + +结论、局限性和未来方向 +DeepSeek-V3 是一款性能强大且成本效益高的开源大型语言模型,它在推理和生成任务中都取得了显著的成果。DeepSeek-V3 的训练成本非常低,只需 2.788M H800 GPU 小时即可完成其全部训练,包括预训练、上下文长度扩展和后训练。 + +尽管 DeepSeek-V3 在性能和效率方面取得了显著成果,但它仍然存在一些局限性,尤其是在部署方面。DeepSeek-V3 的推荐部署单元相对较大,这可能对小型团队构成负担。此外,尽管 DeepSeek-V3 的部署策略已经实现了比 DeepSeek-V2 高两倍的端到端生成速度,但仍然存在进一步提升的空间。 + +DeepSeek-V3 开发了创新的负载平衡策略和训练目标,以实现高效训练。它还引入了 FP8 训练和一系列高效的工程优化措施,以进一步降低训练成本。 +DeepSeek-V3 还在后训练阶段取得了成功,通过知识蒸馏和强化学习技术,显著提高了其在数学和代码基准测试中的性能。 +DeepSeek-V3 在一系列基准测试中取得了最强大的性能,尤其是在数学、代码和长上下文理解任务上。 +DeepSeek-V3 的局限性主要在于部署方面,包括较大的部署单元和潜在的性能提升空间。 +DeepSeek-V3 采用了宪法 AI (constitutional AI) 方法,利用 DeepSeek-V3 自身的投票评估结果作为反馈来源,进一步提高了其在主观评估中的性能。 +DeepSeek-V3 中的多 token 预测技术可以显著加速模型的解码速度,而额外的预测 token 的接受率在 85% 到 90% 之间,这表明其具有高度的可靠性。 +DeepSeek 持续致力于开源模型的道路,并计划在未来进行以下方面的研究: + +进一步改进模型架构,以提高训练和推理效率,并尝试突破 Transformer 架构的限制。 +持续迭代训练数据的质量和数量,并探索其他训练信号来源,以推动数据扩展到更广泛的维度。 +持续探索和迭代模型的深度思考能力,以增强其智能和问题解决能力,并扩展其推理长度和深度。 +探索更全面和多维度的模型评估方法,以防止在研究过程中优化固定的一组基准测试,从而产生对模型能力的误导印象并影响我们的基础评估。 +DeepSeek-V3 的发布标志着开源大型语言模型领域的一个重大里程碑,并为未来的研究和应用开辟了新的可能性。 + +简单测试 +DeepSeek-V3开源模型,我肯定是没有资源部署了,所以只能通过它的服务网站进行测试了。 + +地址:DeepSeek + + +算一下星舰从地球到火星的飞行时间: + + +让它分析一下自己的技术文档: + + + + +最后让它比较了一下自己与GPT-4o-0513 + + +... 略... + + +——完—— + +@北方的郎 · 专注模型与代码 + +概述 +前置准备 +1. 申请 DeepSeek API +2. 注册 Dify +集成步骤 +1. 将 DeepSeek 接入至 Dify +2. 搭建 DeepSeek AI 应用 +3. 为 AI 应用启用文本分析能力 +4. 分享 AI 应用 +阅读更多 +Edit on GitHub + + + +阅读更多 +应用案例 +DeepSeek 与 Dify 集成指南:打造具备多轮思考的 AI 应用 +概述 +DeepSeek 作为具备多轮推理能力的开源大语言模型,以高性能、低成本、易部署的特性成为智能应用开发的理想基座。通过其 API 服务,开发者可快速调用 DeepSeek 的复杂逻辑推理与内容生成能力。在传统开发模式下,构建生产级 AI 应用往往需要独立完成模型适配、接口开发、交互设计等环节。 + +Dify 作为同样开源的生成式 AI 应用开发平台,能够帮助开发者基于 DeepSeek 大模型快速开发出更加智能的 AI 应用,你可以在 Dify 平台内获得以下开发体验: + +可视化构建 - 通过可视化编排界面,3 分钟搭建基于 DeepSeek R1 的 AI 应用 + +知识库增强 - 关联内部文档,开启 RAG 能力并构建精准问答系统 + +工作流扩展 - 提供多种第三方工具插件、可视化拖拽式编排应用功能节点,实现复杂业务逻辑 + +数据洞察力 - 内置总对话数、应用使用用户数等数据监控模块,支持与更加专业的监控平台集成 ... + +本文将详解 DeepSeek API 与 Dify 的集成步骤,助你快速实现两大核心场景: + +智能对话机器人开发 - 直接调用 DeepSeek R1 的思维链推理能力 + +知识增强型应用构建 - 通过私有知识库实现精准信息检索与生成 + +针对金融、法律等高合规需求场景,Dify 提供 本地私有化部署 DeepSeek + Dify,支持 DeepSeek 模型与 Dify 平台同步部署至内网 + +通过 Dify × DeepSeek 的技术组合,开发者可跳过底层架构搭建,跃迁至场景化 AI 能力落地阶段,让大模型技术快速转化为业务生产力。 + +前置准备 +1. 申请 DeepSeek API +访问 DeepSeek API 开放平台,按照页面提示进行申请 API Key。 + +若提示链接无法访问,你也可以考虑在本地部署 DeepSeek 模型。详细说明请参考 本地部署指南 + +2. 注册 Dify +Dify 是一个能够帮助你快速搭建生成式 AI 应用的平台,通过接入 DeepSeek API,你可以快速搭建出一个能够易于使用的 DeepSeek AI 应用。 + +集成步骤 +1. 将 DeepSeek 接入至 Dify +访问 Dify 平台,点击右上角头像 → 设置 → 模型供应商,找到 DeepSeek,将上文获取的 API Key 粘贴至其中。点击保存,校验通过后将出现成功提示。 + + +2. 搭建 DeepSeek AI 应用 +轻点 Dify 平台首页左侧的"创建空白应用",选择"聊天助手"类型应用并进行简单的命名。 + + +选择 deepseek-reasoner 模型 + +deepseek-reasoner 模型又称为 deepseek-r1 模型。 + + +配置完成后即可在聊天框中进行互动。 + + +3. 为 AI 应用启用文本分析能力 +RAG(检索增强生成)是一种先进的信息处理技术,它通过检索相关知识,向 LLM 提供必要的上下文信息,融入 LLM 的内容生成过程,提升回答的准确性和专业度。当你上传内部文档或专业资料后,AI 能够基于这些知识提供更有针对性的解答。 + +3.1 创建知识库 +将需要 AI 分析处理的文档上传至知识库中。为确保 DeepSeek 模型能够准确理解文档内容,建议使用"父子分段"模式进行文本处理 - 这种模式能够更好地保留文档的层级结构和上下文关系。如需了解详细的配置步骤,请参考:创建知识库。 + + +3.2 将知识库集成至 AI 应用 +在 AI 应用的"上下文"内添加知识库,在对话框内输入相关问题。LLM 将首先从知识库内获取与问题相关上下文,在此基础上进行总结并给出更高质量的回答。 + + +4. 分享 AI 应用 +构建完成后,你可以将该 AI 应用分享给他人使用或集成至其它网站内。 + + +阅读更多 +除了构建简单的 AI 应用外,你还可以创建 Chatflow / Workflow 搭建更多复杂功能的应用(例如具备文件识别、图像识别、语音识别等能力)。详细说明请参考以下文档: \ No newline at end of file diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/pom.xml b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/pom.xml new file mode 100644 index 00000000..ae043c23 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + com.cpq + SpringAI-MCP-RAG-Dev + 1.0-SNAPSHOT + + + mcp-server + + + 21 + 21 + UTF-8 + + + + + + org.springframework.ai + spring-ai-bom + 1.0.0 + pom + import + + + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.ai + spring-ai-starter-mcp-server-webflux + + + + + org.springframework.boot + spring-boot-starter-mail + + + + + com.mysql + mysql-connector-j + 8.0.33 + + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.10.1 + + + org.mybatis + mybatis-spring + + + + + org.mybatis + mybatis-spring + 3.0.4 + + + + org.projectlombok + lombok + true + + + + org.apache.commons + commons-lang3 + 3.17.0 + + + + + + com.vladsch.flexmark + flexmark-all + 0.64.8 + + + + + \ No newline at end of file diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/Application.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/Application.java new file mode 100644 index 00000000..cdbdfe91 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/Application.java @@ -0,0 +1,15 @@ +package com.cpq; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + +// http://localhost:9060/sse + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/config/McpServerConfig.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/config/McpServerConfig.java new file mode 100644 index 00000000..b8aa029f --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/config/McpServerConfig.java @@ -0,0 +1,25 @@ +package com.cpq.config; + +import com.cpq.mcp.tool.DateTool; +import com.cpq.mcp.tool.EmailTool; +import com.cpq.mcp.tool.ProductTool; +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.ai.tool.method.MethodToolCallbackProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class McpServerConfig { + + /** + * 将工具方法暴露给外部 mcp client 调用 + */ + @Bean + public ToolCallbackProvider toolCallbackProvider(DateTool dateTool, EmailTool emailTool, + ProductTool productTool) { + return MethodToolCallbackProvider.builder() + .toolObjects(dateTool, emailTool, productTool) + .build(); + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/controller/TestProductController.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/controller/TestProductController.java new file mode 100644 index 00000000..1025788d --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/controller/TestProductController.java @@ -0,0 +1,42 @@ +package com.cpq.controller; + +import com.cpq.entity.Product; +import com.cpq.mapper.ProductMapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + * 商品表 前端控制器 + * + * @author cpq + * @since 2025-11-26 21:17:15 + */ +@Tag(name = "商品表-API") +@Slf4j +@RestController +@RequestMapping("/test-product") +public class TestProductController { + + @Autowired + private ProductMapper productMapper; + + @Operation(summary = "新增", description = "返回id") + @PostMapping("/add") + public Object add(@RequestBody Product addDTO) { + int insert = productMapper.insert(addDTO); + return insert; + } + + @Operation(summary = "详情") + @GetMapping("/detail") + public Object detail(@RequestParam("id") @Parameter(description = "主键") Long id) { + Product product = productMapper.selectById(id); + return product; + } + + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/entity/Product.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/entity/Product.java new file mode 100644 index 00000000..a03c53f9 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/entity/Product.java @@ -0,0 +1,63 @@ +package com.cpq.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 商品表 + * + * @author cpq + * @since 2025-11-26 21:17:15 + */ +@Data +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor +@Schema(name = "商品表", description = "商品表") +public class Product implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "主键") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + @Schema(description = "商品的编号") + private String productNumber; + + @Schema(description = "商品的名称") + private String productName; + + @Schema(description = "商品的品牌") + private String brand; + + @Schema(description = "商品的简介(可以为空)") + private String description; + + @Schema(description = "商品的价格") + private Double price; + + @Schema(description = "商品的库存数量") + private Integer stock; + + @Schema(description = "商品的状态(上架状态的值为1/下架状态的值为0/预售状态的值为2)") + private Integer status; + + @Schema(description = "更新时间") + private LocalDateTime createdTime; + + @Schema(description = "更新时间") + private LocalDateTime updatedTime; + + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/enums/ListSortEnum.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/enums/ListSortEnum.java new file mode 100644 index 00000000..44b17a0d --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/enums/ListSortEnum.java @@ -0,0 +1,16 @@ +package com.cpq.enums; + +public enum ListSortEnum { + + ASC("asc", "正序"), + DESC("desc", "倒序"); + + public final String type; + public final String value; + + ListSortEnum(String type, String value) { + this.type = type; + this.value = value; + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/enums/PriceCompareEnum.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/enums/PriceCompareEnum.java new file mode 100644 index 00000000..29f05f15 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/enums/PriceCompareEnum.java @@ -0,0 +1,25 @@ +package com.cpq.enums; + +public enum PriceCompareEnum { + + GREATER_THAN(">", "大于"), + LESS_THAN("<", "小于"), + GREATER_THAN_OR_EQUAL_TO(">=", "大于等于"), + LESS_THAN_OR_EQUAL_TO("<=", "小于等于"), + + HIGHER_THAN(">", "高于"), + LOWER_THAN("<", "低于"), + NOT_HIGHER_THAN("<=", "不高于"), + NOT_LOWER_THAN(">=", "不低于"), + + EQUAL_TO("=", "等于"); + + public final String type; + public final String value; + + PriceCompareEnum(String type, String value) { + this.type = type; + this.value = value; + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mapper/ProductMapper.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mapper/ProductMapper.java new file mode 100644 index 00000000..f0b75229 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mapper/ProductMapper.java @@ -0,0 +1,17 @@ +package com.cpq.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.cpq.entity.Product; +import org.apache.ibatis.annotations.Mapper; +/** + *

+ * 商品表 Mapper 接口 + *

+ * + * @author cpq + * @since 2025-11-26 21:17:15 + */ +@Mapper +public interface ProductMapper extends BaseMapper { + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mapper/ProductMapper.xml b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mapper/ProductMapper.xml new file mode 100644 index 00000000..0b987b1e --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mapper/ProductMapper.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mcp/tool/DateTool.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mcp/tool/DateTool.java new file mode 100644 index 00000000..ec4e79a4 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mcp/tool/DateTool.java @@ -0,0 +1,46 @@ +package com.cpq.mcp.tool; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Component +@Slf4j +public class DateTool { + + /** + * 大模型会根据函数参数,自动填充参数的值 + */ + @Tool(description = "根据城市所在的时区id来获得当前的时间") + public String getCurrentTimeByZoneId(String cityName, String zoneId) { + + log.info("========== 调用MCP工具:getCurrentTimeByZoneId() =========="); + log.info(String.format("========== 参数 cityName:%s ==========", cityName)); + log.info(String.format("========== 参数 zoneId:%s ==========", zoneId)); + + ZoneId zone = ZoneId.of(zoneId); + + // 获取该时区对应的当前时间 + ZonedDateTime zonedDateTime = ZonedDateTime.now(zone); + String currentTime = String.format("当前的时间是 %s", + zonedDateTime.format( + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + return currentTime; + } + + @Tool(description = "获取当前时间") + public String getCurrentTime() { + log.info("========== 调用MCP工具:getCurrentTime() =========="); + String currentTime = String.format("当前的时间是 %s", + LocalDateTime.now().format( + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + log.info("========== 调用MCP工具:getCurrentTime()---result ==========currentTime", currentTime); + return currentTime; + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mcp/tool/EmailTool.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mcp/tool/EmailTool.java new file mode 100644 index 00000000..e8421e72 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mcp/tool/EmailTool.java @@ -0,0 +1,101 @@ +package com.cpq.mcp.tool; + +import com.vladsch.flexmark.html.HtmlRenderer; +import com.vladsch.flexmark.parser.Parser; +import com.vladsch.flexmark.util.data.MutableDataSet; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.ai.tool.annotation.ToolParam; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class EmailTool { + + private final JavaMailSender mailSender; + private final String from; + + @Autowired + public EmailTool(JavaMailSender mailSender, @Value("${spring.mail.username}") String from) { + this.mailSender = mailSender; + this.from = from; + } + + @Tool(description = "查询我的邮件/邮箱地址") + public String getMyEmailAddress() { + log.info("========== 调用MCP工具:getMyEmailAddress() =========="); + return "tfz9011@163.com"; + } + + @Data + @ToString + @NoArgsConstructor + @AllArgsConstructor + public static class EmailRequest { + @ToolParam(description = "收件人邮箱") + private String email; + @ToolParam(description = "发送邮件的标题/主题") + private String subject; + @ToolParam(description = "发送邮件的消息/正文内容") + private String message; + + @ToolParam(description = "邮件的内容是否为html还是markdown格式,如果是markdown格式,则为1;如果是html格式,则为2") + private Integer contentType; + } + + @Tool(description = "给指定邮箱发送邮件信息,email 为收件人邮箱,subject 为邮件标题,message 为邮件的内容") + public void sendMailMessage(EmailRequest emailRequest) { + + log.info("========== 调用MCP工具:sendMailMessage() =========="); + log.info(String.format("========== 参数 emailRequest:%s ==========", emailRequest.toString())); + + Integer contentType = emailRequest.getContentType(); + + try { + MimeMessage mimeMessage = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage); + + helper.setFrom(from); + helper.setTo(emailRequest.getEmail()); + helper.setSubject(emailRequest.getSubject()); + + if (contentType == 1) { + helper.setText(convertToHtml(emailRequest.getMessage()), true); + } else if (contentType == 2) { + helper.setText(emailRequest.getMessage(), true); + } else { + helper.setText(emailRequest.getMessage()); + } + + mailSender.send(mimeMessage); + } catch (MessagingException e) { +// throw new RuntimeException(e); + log.error("========== 发送邮件失败,报错信息:{} ==========", e.getMessage()); + } + } + + /** + * markdown 转 html + * @param markdownStr + * @return + */ + public static String convertToHtml(String markdownStr) { + + MutableDataSet dataSet = new MutableDataSet(); + Parser parser = Parser.builder(dataSet).build(); + HtmlRenderer htmlRenderer = HtmlRenderer.builder(dataSet).build(); + + return htmlRenderer.render(parser.parse(markdownStr)); + } + +} diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mcp/tool/ProductTool.java b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mcp/tool/ProductTool.java new file mode 100644 index 00000000..37e75c04 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/java/com/cpq/mcp/tool/ProductTool.java @@ -0,0 +1,240 @@ +package com.cpq.mcp.tool; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.cpq.entity.Product; +import com.cpq.enums.ListSortEnum; +import com.cpq.enums.PriceCompareEnum; +import com.cpq.mapper.ProductMapper; +import jakarta.annotation.Resource; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.ai.tool.annotation.ToolParam; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +@Component +@Slf4j +public class ProductTool { + + @Resource + private ProductMapper productMapper; + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class ProductAdd implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @ToolParam(description = "商品的名称") + private String productName; + + @ToolParam(description = "商品的品牌") + private String brand; + + @ToolParam(description = "商品的简介(可以为空)", required = false) + private String description; + + @ToolParam(description = "商品的价格") + private Double price; + + @ToolParam(description = "商品的库存数量") + private Integer stock; + + @ToolParam(description = "商品的状态(上架状态的值为1/下架状态的值为0/预售状态的值为2)") + private Integer status=1; + + } + + @Tool(description = "创建/新增商品") + public String addProduct(ProductAdd productAdd) { + log.info("========== 创建/新增商品 productAdd={} ==========", productAdd); + Product product = new Product(); + BeanUtils.copyProperties(productAdd, product); + product.setProductNumber(RandomStringUtils.randomNumeric(12)); + productMapper.insert(product); + return "商品信息创建成功"; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class ProductUpdate implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @ToolParam(description = "商品的编号") + private String productNumber; + + @ToolParam(description = "商品的名称", required = false) + private String productName; + + @ToolParam(description = "商品的品牌", required = false) + private String brand; + + @ToolParam(description = "商品的简介(可以为空)", required = false) + private String description; + + @ToolParam(description = "商品的价格", required = false) + private Double price; + + @ToolParam(description = "商品的库存数量", required = false) + private Integer stock; + + @ToolParam(description = "商品的状态(上架状态的值为1/下架状态的值为0/预售状态的值为2)", required = false) + private Integer status; + + } + + @Tool(description = "根据商品编号修改商品信息") + public String updateProduct(ProductUpdate productUpdate) { + log.info("========== 修改商品 updateProduct={} ==========", productUpdate); + Product product = new Product(); + BeanUtils.copyProperties(productUpdate, product); + + LambdaQueryWrapper lqwUp = Wrappers.lambdaQuery(); + lqwUp.eq(Product::getProductNumber, productUpdate.getProductNumber()); + productMapper.update(product, lqwUp); + return "商品信息更新成功"; + } + + @Tool(description = "根据商品编号删除商品") + public String deleteProduct(String productNumber) { + log.info("========== 根据商品编号删除商品 productNumber={} ==========", productNumber); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(Product::getProductNumber, productNumber); + productMapper.delete(lqw); + return "商品信息删除成功"; + } + + @Tool(description = "把排序(正序/倒序)转换为对应的枚举") + public ListSortEnum getSortEnum(String sort) { + log.info("========== 调用getSortEnum(),sort={} ==========", sort); + if (ListSortEnum.ASC.value.equals(sort)) { + return ListSortEnum.ASC; + } + return ListSortEnum.DESC; + } + + @Tool(description = "把商品价格的比较(大于/小于/大于等于/小于等于/高于/低于/不高于/不低于/等于)转换为对应的枚举") + public PriceCompareEnum getPriceCompareEnum(String priceCompare) { + log.info("========== getPriceCompareEnum() ,priceCompare={}==========", priceCompare); + if (priceCompare.equalsIgnoreCase(PriceCompareEnum.GREATER_THAN.value)) { + return PriceCompareEnum.GREATER_THAN; + } else if (priceCompare.equalsIgnoreCase(PriceCompareEnum.LESS_THAN.value)) { + return PriceCompareEnum.LESS_THAN; + } else if (priceCompare.equalsIgnoreCase(PriceCompareEnum.GREATER_THAN_OR_EQUAL_TO.value)) { + return PriceCompareEnum.GREATER_THAN_OR_EQUAL_TO; + } else if (priceCompare.equalsIgnoreCase(PriceCompareEnum.LESS_THAN_OR_EQUAL_TO.value)) { + return PriceCompareEnum.LESS_THAN_OR_EQUAL_TO; + } else if (priceCompare.equalsIgnoreCase(PriceCompareEnum.HIGHER_THAN.value)) { + return PriceCompareEnum.HIGHER_THAN; + } else if (priceCompare.equalsIgnoreCase(PriceCompareEnum.LOWER_THAN.value)) { + return PriceCompareEnum.LOWER_THAN; + } else if (priceCompare.equalsIgnoreCase(PriceCompareEnum.NOT_HIGHER_THAN.value)) { + return PriceCompareEnum.NOT_HIGHER_THAN; + } else if (priceCompare.equalsIgnoreCase(PriceCompareEnum.NOT_LOWER_THAN.value)) { + return PriceCompareEnum.NOT_LOWER_THAN; + } else { + return PriceCompareEnum.EQUAL_TO; + } + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class ProductQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @ToolParam(description = "商品的编号", required = false) + private String productNumber; + + @ToolParam(description = "商品的名称", required = false) + private String productName; + + @ToolParam(description = "商品的品牌", required = false) + private String brand; + + @ToolParam(description = "商品的价格", required = false) + private Double price; + + @ToolParam(description = "查询列表的排序", required = false) + private ListSortEnum sortEnum; + + @ToolParam(description = "比较价格大小", required = false) + private PriceCompareEnum priceCompare; + + } + + @Tool(description = "根据条件查询商品(product)信息") + public List queryProduct(ProductQuery productQuery) { + log.info("========== 根据条件查询商品 productQuery={}==========", productQuery); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(StringUtils.isNotBlank(productQuery.getProductNumber()), + Product::getProductNumber, productQuery.getProductNumber()); + lqw.like(StringUtils.isNotBlank(productQuery.getProductName()), + Product::getProductName, productQuery.getProductName()); + lqw.like(StringUtils.isNotBlank(productQuery.getBrand()), + Product::getBrand, productQuery.getBrand()); + + Double price = productQuery.getPrice(); + PriceCompareEnum priceCompareEnum = productQuery.getPriceCompare(); + if (price != null && priceCompareEnum != null) { + if (priceCompareEnum.type.equals(PriceCompareEnum.GREATER_THAN.type)) { + lqw.gt(Product::getPrice, price); + } else if (priceCompareEnum.type.equals(PriceCompareEnum.LESS_THAN.type)) { + lqw.lt(Product::getPrice, price); + } else if (priceCompareEnum.type.equals(PriceCompareEnum.GREATER_THAN_OR_EQUAL_TO.type)) { + lqw.ge(Product::getPrice, price); + } else if (priceCompareEnum.type.equals(PriceCompareEnum.LESS_THAN_OR_EQUAL_TO.type)) { + lqw.le(Product::getPrice, price); + } else if (priceCompareEnum.type.equals(PriceCompareEnum.HIGHER_THAN.type)) { + lqw.gt(Product::getPrice, price); + } else if (priceCompareEnum.type.equals(PriceCompareEnum.LOWER_THAN.type)) { + lqw.lt(Product::getPrice, price); + } else if (priceCompareEnum.type.equals(PriceCompareEnum.NOT_HIGHER_THAN.type)) { + lqw.le(Product::getPrice, price); + } else if (priceCompareEnum.type.equals(PriceCompareEnum.NOT_LOWER_THAN.type)) { + lqw.ge(Product::getPrice, price); + } else { + lqw.eq(Product::getPrice, price); + } + + ListSortEnum sortEnum = productQuery.getSortEnum(); + if (sortEnum != null) { + if (sortEnum.type.equals(ListSortEnum.ASC.type)) { + lqw.orderByAsc(Product::getPrice); + } else { + lqw.orderByDesc(Product::getPrice); + } + } + } + + return productMapper.selectList(lqw); + } + + +} + + + + + + + + + diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/resources/application.yml b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/resources/application.yml new file mode 100644 index 00000000..b6beb5d1 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/mcp-server/src/main/resources/application.yml @@ -0,0 +1,59 @@ +server: + port: 9060 + +spring: + application: + name: spring-ai-mcp-server + profiles: + active: dev + datasource: # 数据源的相关配置 + type: com.zaxxer.hikari.HikariDataSource # 数据源的类型,可以更改为其他的数据源配置,比如druid + driver-class-name: com.mysql.cj.jdbc.Driver # mysql/MariaDB 的数据库驱动类名称 + url: jdbc:mysql://127.0.0.1:3306/cpq?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC + username: root + password: 123456 + hikari: + pool-name: DataSourceHikariCP # 连接池的名字 + connection-timeout: 30000 # 等待连接池分配连接的最大时间(毫秒),超过这个时长还没有可用的连接,则会抛出SQLException + minimum-idle: 5 # 最小连接数 + maximum-pool-size: 20 # 最大连接数 + auto-commit: true # 自动提交 + idle-timeout: 600000 # 连接超时的最大时长(毫秒),超时则会被释放(retired) + max-lifetime: 18000000 # 连接池的最大生命时长(毫秒),超时则会被释放(retired) + connection-test-query: SELECT 1 + ai: + mcp: + server: + name: spring-ai-mcp-server-sse + version: 1.0.0 + sse-endpoint: /sse + type: async + mail: + host: smtp.163.com # 邮箱host + port: 465 # 邮箱固定端口 + username: tfz9011@163.com + password: WKrq9UqJ7pPB2npy + protocol: smtp # 邮箱协议 + default-encoding: UTF-8 # 默认编码 + properties: + mail: + smtp: + socketFactory: + port: 465 + class: javax.net.ssl.SSLSocketFactory + ssl: + enable: true + +logging: + level: + root: info + +# MyBatisPlus 的配置 +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + id-type: assign_id + update-strategy: not_empty + mapper-locations: classpath*:/mappers/*.xml diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/pom.xml b/project/spingai/SpringAI-MCP-RAG-Dev/pom.xml new file mode 100644 index 00000000..72f4d2bb --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.0 + + + + com.cpq + SpringAI-MCP-RAG-Dev + 1.0-SNAPSHOT + + pom + + + 21 + 21 + UTF-8 + + + + mcp-client + mcp-server + + + + + + \ No newline at end of file diff --git a/project/spingai/SpringAI-MCP-RAG-Dev/readme.md b/project/spingai/SpringAI-MCP-RAG-Dev/readme.md new file mode 100644 index 00000000..fa585286 --- /dev/null +++ b/project/spingai/SpringAI-MCP-RAG-Dev/readme.md @@ -0,0 +1,50 @@ +# searxng搜索引擎 +### 本地安装 +mkdir -p /etc/searxng/config + +mkdir -p /var/searxng/data + +docker pull docker.io/searxng/searxng:2025.8.1-3d96414 + +docker run --name searxng -d \ +-p 8888:8080 \ +-v "/etc/searxng/config/:/etc/searxng/" \ +-v "/var/searxng/data/:/var/cache/searxng/" \ +docker.io/searxng/searxng:2025.8.1-3d96414 + +vim /etc/searxng/config/settings.yml + +``` +search: + formats: + - html + - json # 加上json +``` + +编辑settings.yml,禁用engines下的Google、wikidata等无法访问的引擎,开启baidu、bing这些能访问的引擎。 +具体配置参考searxng的settings.yml + +docker restart 容器ID + +本地访问:http://192.168.1.221:8888/ + + +# product表 +``` +CREATE TABLE `product` ( +`id` bigint NOT NULL COMMENT '主键', +`product_number` varchar(64) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '商品的编号', +`product_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '商品的名称', +`brand` varchar(64) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '商品的品牌', +`description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '商品的简介(可以为空)', +`price` float(10,2) DEFAULT '0.00' COMMENT '商品的价格', +`stock` int DEFAULT '0' COMMENT '商品的库存数量', +`status` int DEFAULT NULL COMMENT '商品的状态(上架状态的值为1/下架状态的值为0/预售状态的值为2)', +`created_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', +`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', +PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='商品表'; + + +``` + diff --git "a/project/spingai/SpringAI-MCP-RAG-Dev/searxng\347\232\204settings.yml" "b/project/spingai/SpringAI-MCP-RAG-Dev/searxng\347\232\204settings.yml" new file mode 100644 index 00000000..42ec1a7c --- /dev/null +++ "b/project/spingai/SpringAI-MCP-RAG-Dev/searxng\347\232\204settings.yml" @@ -0,0 +1,2819 @@ +general: + # Debug mode, only for development. Is overwritten by ${SEARXNG_DEBUG} + debug: false + # displayed name + instance_name: "SearXNG" + # For example: https://example.com/privacy + privacypolicy_url: false + # use true to use your own donation page written in searx/info/en/donate.md + # use false to disable the donation link + donation_url: false + # mailto:contact@example.com + contact_url: false + # record stats + enable_metrics: true + # expose stats in open metrics format at /metrics + # leave empty to disable (no password set) + # open_metrics: + open_metrics: '' + +brand: + new_issue_url: https://github.com/searxng/searxng/issues/new + docs_url: https://docs.searxng.org/ + public_instances: https://searx.space + wiki_url: https://github.com/searxng/searxng/wiki + issue_url: https://github.com/searxng/searxng/issues + # custom: + # maintainer: "Jon Doe" + # # Custom entries in the footer: [title]: [link] + # links: + # Uptime: https://uptime.searxng.org/history/darmarit-org + # About: "https://searxng.org" + +search: + # Filter results. 0: None, 1: Moderate, 2: Strict + safe_search: 0 + # Existing autocomplete backends: "360search", "baidu", "brave", "dbpedia", "duckduckgo", "google", "yandex", + # "mwmbl", "naver", "seznam", "sogou", "startpage", "stract", "swisscows", "quark", "qwant", "wikipedia" - + # leave blank to turn it off by default. + autocomplete: "" + # minimun characters to type before autocompleter starts + autocomplete_min: 4 + # backend for the favicon near URL in search results. + # Available resolvers: "allesedv", "duckduckgo", "google", "yandex" - leave blank to turn it off by default. + favicon_resolver: "" + # Default search language - leave blank to detect from browser information or + # use codes from 'languages.py' + default_lang: "auto" + # max_page: 0 # if engine supports paging, 0 means unlimited numbers of pages + # Available languages + # languages: + # - all + # - en + # - en-US + # - de + # - it-IT + # - fr + # - fr-BE + # ban time in seconds after engine errors + ban_time_on_fail: 5 + # max ban time in seconds after engine errors + max_ban_time_on_fail: 120 + suspended_times: + # Engine suspension time after error (in seconds; set to 0 to disable) + # For error "Access denied" and "HTTP error [402, 403]" + SearxEngineAccessDenied: 86400 + # For error "CAPTCHA" + SearxEngineCaptcha: 86400 + # For error "Too many request" and "HTTP error 429" + SearxEngineTooManyRequests: 3600 + # Cloudflare CAPTCHA + cf_SearxEngineCaptcha: 1296000 + cf_SearxEngineAccessDenied: 86400 + # ReCAPTCHA + recaptcha_SearxEngineCaptcha: 604800 + + # remove format to deny access, use lower case. + # formats: [html, csv, json, rss] + formats: + - html + - json + +server: + # Is overwritten by ${SEARXNG_PORT} and ${SEARXNG_BIND_ADDRESS} + port: 8888 + bind_address: "127.0.0.1" + # public URL of the instance, to ensure correct inbound links. Is overwritten + # by ${SEARXNG_BASE_URL}. + base_url: false # "http://example.com/location" + # rate limit the number of request on the instance, block some bots. + # Is overwritten by ${SEARXNG_LIMITER} + limiter: false + # enable features designed only for public instances. + # Is overwritten by ${SEARXNG_PUBLIC_INSTANCE} + public_instance: false + + # If your instance owns a /etc/searxng/settings.yml file, then set the following + # values there. + + secret_key: "OipACc2YIAaK9cLLtjJuTMU43n6F4EI" # Is overwritten by ${SEARXNG_SECRET} + # Proxy image results through SearXNG. Is overwritten by ${SEARXNG_IMAGE_PROXY} + image_proxy: false + # 1.0 and 1.1 are supported + http_protocol_version: "1.0" + # POST queries are more secure as they don't show up in history but may cause + # problems when using Firefox containers. + # Is overwritten by ${SEARXNG_METHOD} + method: "POST" + default_http_headers: + X-Content-Type-Options: nosniff + X-Download-Options: noopen + X-Robots-Tag: noindex, nofollow + Referrer-Policy: no-referrer + +valkey: + # URL to connect valkey database. Is overwritten by ${SEARXNG_VALKEY_URL}. + # https://docs.searxng.org/admin/settings/settings_valkey.html#settings-valkey + # url: valkey://localhost:6379/0 + url: false + +ui: + # Custom static path - leave it blank if you didn't change + static_path: "" + # Custom templates path - leave it blank if you didn't change + templates_path: "" + # query_in_title: When true, the result page's titles contains the query + # it decreases the privacy, since the browser can records the page titles. + query_in_title: false + # infinite_scroll: When true, automatically loads the next page when scrolling to bottom of the current page. + infinite_scroll: false + # ui theme + default_theme: simple + # center the results ? + center_alignment: false + # URL prefix of the internet archive, don't forget trailing slash (if needed). + # cache_url: "https://webcache.googleusercontent.com/search?q=cache:" + # Default interface locale - leave blank to detect from browser information or + # use codes from the 'locales' config section + default_locale: "" + # Open result links in a new tab by default + # results_on_new_tab: false + theme_args: + # style of simple theme: auto, light, dark + simple_style: auto + # Perform search immediately if a category selected. + # Disable to select multiple categories at once and start the search manually. + search_on_category_select: true + # Hotkeys: default or vim + hotkeys: default + # URL formatting: pretty, full or host + url_formatting: pretty + +# Lock arbitrary settings on the preferences page. +# +# preferences: +# lock: +# - categories +# - language +# - autocomplete +# - favicon +# - safesearch +# - method +# - doi_resolver +# - locale +# - theme +# - results_on_new_tab +# - infinite_scroll +# - search_on_category_select +# - method +# - image_proxy +# - query_in_title + +# communication with search engines +# +outgoing: + # default timeout in seconds, can be override by engine + request_timeout: 3.0 + # the maximum timeout in seconds + # max_request_timeout: 10.0 + # suffix of searxng_useragent, could contain information like an email address + # to the administrator + useragent_suffix: "" + # The maximum number of concurrent connections that may be established. + pool_connections: 100 + # Allow the connection pool to maintain keep-alive connections below this + # point. + pool_maxsize: 20 + # See https://www.python-httpx.org/http2/ + enable_http2: true + # uncomment below section if you want to use a custom server certificate + # see https://www.python-httpx.org/advanced/#changing-the-verification-defaults + # and https://www.python-httpx.org/compatibility/#ssl-configuration + # verify: ~/.mitmproxy/mitmproxy-ca-cert.cer + # + # uncomment below section if you want to use a proxyq see: SOCKS proxies + # https://2.python-requests.org/en/latest/user/advanced/#proxies + # are also supported: see + # https://2.python-requests.org/en/latest/user/advanced/#socks + # + # proxies: + # all://: + # - http://proxy1:8080 + # - http://proxy2:8080 + # + # using_tor_proxy: true + # + # Extra seconds to add in order to account for the time taken by the proxy + # + # extra_proxy_timeout: 10 + # + # uncomment below section only if you have more than one network interface + # which can be the source of outgoing search requests + # + # source_ips: + # - 1.1.1.1 + # - 1.1.1.2 + # - fe80::/126 + +# Plugin configuration, for more details see +# https://docs.searxng.org/admin/settings/settings_plugins.html +# +plugins: + + searx.plugins.calculator.SXNGPlugin: + active: true + + searx.plugins.hash_plugin.SXNGPlugin: + active: true + + searx.plugins.self_info.SXNGPlugin: + active: true + + searx.plugins.unit_converter.SXNGPlugin: + active: true + + searx.plugins.ahmia_filter.SXNGPlugin: + active: true + + searx.plugins.hostnames.SXNGPlugin: + active: true + + searx.plugins.oa_doi_rewrite.SXNGPlugin: + active: false + + searx.plugins.tor_check.SXNGPlugin: + active: false + + searx.plugins.tracker_url_remover.SXNGPlugin: + active: true + + +# Configuration of the "Hostnames plugin": +# +# hostnames: +# replace: +# '(.*\.)?youtube\.com$': 'yt.example.com' +# '(.*\.)?youtu\.be$': 'yt.example.com' +# '(.*\.)?reddit\.com$': 'teddit.example.com' +# '(.*\.)?redd\.it$': 'teddit.example.com' +# '(www\.)?twitter\.com$': 'nitter.example.com' +# remove: +# - '(.*\.)?facebook.com$' +# low_priority: +# - '(.*\.)?google(\..*)?$' +# high_priority: +# - '(.*\.)?wikipedia.org$' +# +# Alternatively you can use external files for configuring the "Hostnames plugin": +# +# hostnames: +# replace: 'rewrite-hosts.yml' +# +# Content of 'rewrite-hosts.yml' (place the file in the same directory as 'settings.yml'): +# '(.*\.)?youtube\.com$': 'yt.example.com' +# '(.*\.)?youtu\.be$': 'yt.example.com' +# + +checker: + # disable checker when in debug mode + off_when_debug: true + + # use "scheduling: {}" to disable scheduling + # scheduling: interval or int + + # to activate the scheduler: + # * uncomment "scheduling" section + # * add "cache2 = name=searxngcache,items=2000,blocks=2000,blocksize=4096,bitmap=1" + # to your uwsgi.ini + + # scheduling: + # start_after: [300, 1800] # delay to start the first run of the checker + # every: [86400, 90000] # how often the checker runs + + # additional tests: only for the YAML anchors (see the engines section) + # + additional_tests: + rosebud: &test_rosebud + matrix: + query: rosebud + lang: en + result_container: + - not_empty + - ['one_title_contains', 'citizen kane'] + test: + - unique_results + + android: &test_android + matrix: + query: ['android'] + lang: ['en', 'de', 'fr', 'zh-CN'] + result_container: + - not_empty + - ['one_title_contains', 'google'] + test: + - unique_results + + # tests: only for the YAML anchors (see the engines section) + tests: + infobox: &tests_infobox + infobox: + matrix: + query: ["linux", "new york", "bbc"] + result_container: + - has_infobox + +categories_as_tabs: + general: + images: + videos: + news: + map: + music: + it: + science: + files: + social media: + +engines: + - name: 360search + engine: 360search + shortcut: 360so + disabled: true + + - name: 360search videos + engine: 360search_videos + shortcut: 360sov + disabled: true + + - name: 9gag + engine: 9gag + shortcut: 9g + disabled: true + + - name: acfun + engine: acfun + shortcut: acf + disabled: true + + - name: adobe stock + engine: adobe_stock + shortcut: asi + categories: ["images"] + # https://docs.searxng.org/dev/engines/online/adobe_stock.html + adobe_order: relevance + adobe_content_types: ["photo", "illustration", "zip_vector", "template", "3d", "image"] + timeout: 6 + disabled: true + + - name: adobe stock video + engine: adobe_stock + shortcut: asv + network: adobe stock + categories: ["videos"] + adobe_order: relevance + adobe_content_types: ["video"] + timeout: 6 + disabled: true + + - name: adobe stock audio + engine: adobe_stock + shortcut: asa + network: adobe stock + categories: ["music"] + adobe_order: relevance + adobe_content_types: ["audio"] + timeout: 6 + disabled: true + + - name: alexandria + engine: json_engine + shortcut: alx + categories: general + paging: true + search_url: https://api.alexandria.org/?a=1&q={query}&p={pageno} + results_query: results + title_query: title + url_query: url + content_query: snippet + timeout: 1.5 + disabled: true + about: + website: https://alexandria.org/ + official_api_documentation: https://github.com/alexandria-org/alexandria-api/raw/master/README.md + use_official_api: true + require_api_key: false + results: JSON + + # - name: astrophysics data system + # engine: astrophysics_data_system + # sort: asc + # weight: 5 + # categories: [science] + # api_key: your-new-key + # shortcut: ads + + - name: alpine linux packages + engine: alpinelinux + disabled: true + shortcut: alp + + - name: annas archive + engine: annas_archive + disabled: true + shortcut: aa + + - name: ansa + engine: ansa + shortcut: ans + disabled: true + + # - name: annas articles + # engine: annas_archive + # shortcut: aaa + # # https://docs.searxng.org/dev/engines/online/annas_archive.html + # aa_content: 'magazine' # book_fiction, book_unknown, book_nonfiction, book_comic + # aa_ext: 'pdf' # pdf, epub, .. + # aa_sort: oldest' # newest, oldest, largest, smallest + + - name: apk mirror + engine: apkmirror + timeout: 4.0 + shortcut: apkm + disabled: true + + - name: apple app store + engine: apple_app_store + shortcut: aps + disabled: true + + # Requires Tor + - name: ahmia + engine: ahmia + categories: onions + enable_http: true + shortcut: ah + + - name: anaconda + engine: xpath + paging: true + first_page_num: 0 + search_url: https://anaconda.org/search?q={query}&page={pageno} + results_xpath: //tbody/tr + url_xpath: ./td/h5/a[last()]/@href + title_xpath: ./td/h5 + content_xpath: ./td[h5]/text() + categories: it + timeout: 6.0 + shortcut: conda + disabled: true + + - name: arch linux wiki + engine: archlinux + shortcut: al + + - name: nixos wiki + engine: mediawiki + shortcut: nixw + base_url: https://wiki.nixos.org/ + search_type: text + disabled: true + categories: [it, software wikis] + + - name: artic + engine: artic + shortcut: arc + timeout: 4.0 + + - name: arxiv + engine: arxiv + shortcut: arx + timeout: 4.0 + + - name: ask + engine: ask + shortcut: ask + disabled: true + + # tmp suspended: dh key too small + # - name: base + # engine: base + # shortcut: bs + + - name: bandcamp + engine: bandcamp + shortcut: bc + categories: music + + - name: baidu + baidu_category: general + categories: [general] + engine: baidu + shortcut: bd + disabled: false + + - name: baidu images + baidu_category: images + categories: [images] + engine: baidu + shortcut: bdi + disabled: false + + - name: baidu kaifa + baidu_category: it + categories: [it] + engine: baidu + shortcut: bdk + disabled: false + + - name: wikipedia + engine: wikipedia + shortcut: wp + # add "list" to the array to get results in the results list + display_type: ["infobox"] + categories: [general] + disabled: true + + - name: bilibili + engine: bilibili + shortcut: bil + disabled: true + + - name: bing + engine: bing + shortcut: bi + disabled: false + + - name: bing images + engine: bing_images + shortcut: bii + + - name: bing news + engine: bing_news + shortcut: bin + + - name: bing videos + engine: bing_videos + shortcut: biv + + - name: bitchute + engine: bitchute + shortcut: bit + disabled: true + + - name: bitbucket + engine: xpath + paging: true + search_url: https://bitbucket.org/repo/all/{pageno}?name={query} + url_xpath: //article[@class="repo-summary"]//a[@class="repo-link"]/@href + title_xpath: //article[@class="repo-summary"]//a[@class="repo-link"] + content_xpath: //article[@class="repo-summary"]/p + categories: [it, repos] + timeout: 4.0 + disabled: true + shortcut: bb + about: + website: https://bitbucket.org/ + wikidata_id: Q2493781 + official_api_documentation: https://developer.atlassian.com/bitbucket + use_official_api: false + require_api_key: false + results: HTML + + - name: bpb + engine: bpb + shortcut: bpb + disabled: true + + - name: btdigg + engine: btdigg + shortcut: bt + disabled: true + + - name: openverse + engine: openverse + categories: images + shortcut: opv + + - name: media.ccc.de + engine: ccc_media + shortcut: c3tv + # We don't set language: de here because media.ccc.de is not just + # for a German audience. It contains many English videos and many + # German videos have English subtitles. + disabled: true + + - name: chefkoch + engine: chefkoch + shortcut: chef + # to show premium or plus results too: + # skip_premium: false + + # WARNING: links from chinaso.com voilate users privacy + # Before activate these engines its mandatory to read + # - https://github.com/searxng/searxng/issues/4694 + # - https://docs.searxng.org/dev/engines/online/chinaso.html + + - name: chinaso news + engine: chinaso + shortcut: chinaso + categories: [news] + chinaso_category: news + chinaso_news_source: all + disabled: true + inactive: true + + - name: chinaso images + engine: chinaso + network: chinaso news + shortcut: chinasoi + categories: [images] + chinaso_category: images + disabled: true + inactive: true + + - name: chinaso videos + engine: chinaso + network: chinaso news + shortcut: chinasov + categories: [videos] + chinaso_category: videos + disabled: true + inactive: true + + - name: cloudflareai + engine: cloudflareai + shortcut: cfai + # get api token and accont id from https://developers.cloudflare.com/workers-ai/get-started/rest-api/ + cf_account_id: 'your_cf_accout_id' + cf_ai_api: 'your_cf_api' + # create your ai gateway by https://developers.cloudflare.com/ai-gateway/get-started/creating-gateway/ + cf_ai_gateway: 'your_cf_ai_gateway_name' + # find the model name from https://developers.cloudflare.com/workers-ai/models/#text-generation + cf_ai_model: 'ai_model_name' + # custom your preferences + # cf_ai_model_display_name: 'Cloudflare AI' + # cf_ai_model_assistant: 'prompts_for_assistant_role' + # cf_ai_model_system: 'prompts_for_system_role' + timeout: 30 + disabled: true + + # - name: core.ac.uk + # engine: core + # categories: science + # shortcut: cor + # # get your API key from: https://core.ac.uk/api-keys/register/ + # api_key: 'unset' + + - name: cppreference + engine: cppreference + shortcut: cpp + paging: false + disabled: true + + - name: crossref + engine: crossref + shortcut: cr + timeout: 30 + disabled: true + + - name: crowdview + engine: json_engine + shortcut: cv + categories: general + paging: false + search_url: https://crowdview-next-js.onrender.com/api/search-v3?query={query} + results_query: results + url_query: link + title_query: title + content_query: snippet + title_html_to_text: true + content_html_to_text: true + disabled: true + about: + website: https://crowdview.ai/ + + - name: yep + engine: yep + shortcut: yep + categories: general + search_type: web + timeout: 5 + disabled: true + + - name: yep images + engine: yep + shortcut: yepi + categories: images + search_type: images + disabled: true + + - name: yep news + engine: yep + shortcut: yepn + categories: news + search_type: news + disabled: true + + - name: currency + engine: currency_convert + categories: general + shortcut: cc + + - name: deezer + engine: deezer + shortcut: dz + disabled: true + + - name: destatis + engine: destatis + shortcut: destat + disabled: true + + - name: deviantart + engine: deviantart + shortcut: da + timeout: 3.0 + + - name: ddg definitions + engine: duckduckgo_definitions + shortcut: ddd + weight: 2 + disabled: true + tests: *tests_infobox + + # cloudflare protected + # - name: digbt + # engine: digbt + # shortcut: dbt + # timeout: 6.0 + # disabled: true + + - name: docker hub + engine: docker_hub + shortcut: dh + categories: [it, packages] + + - name: encyclosearch + engine: json_engine + shortcut: es + categories: general + paging: true + search_url: https://encyclosearch.org/encyclosphere/search?q={query}&page={pageno}&resultsPerPage=15 + results_query: Results + url_query: SourceURL + title_query: Title + content_query: Description + disabled: true + about: + website: https://encyclosearch.org + official_api_documentation: https://encyclosearch.org/docs/#/rest-api + use_official_api: true + require_api_key: false + results: JSON + + - name: erowid + engine: xpath + paging: true + first_page_num: 0 + page_size: 30 + search_url: https://www.erowid.org/search.php?q={query}&s={pageno} + url_xpath: //dl[@class="results-list"]/dt[@class="result-title"]/a/@href + title_xpath: //dl[@class="results-list"]/dt[@class="result-title"]/a/text() + content_xpath: //dl[@class="results-list"]/dd[@class="result-details"] + categories: [] + shortcut: ew + disabled: true + about: + website: https://www.erowid.org/ + wikidata_id: Q1430691 + official_api_documentation: + use_official_api: false + require_api_key: false + results: HTML + + # - name: elasticsearch + # shortcut: els + # engine: elasticsearch + # base_url: http://localhost:9200 + # username: elastic + # password: changeme + # index: my-index + # enable_http: true + # # available options: match, simple_query_string, term, terms, custom + # query_type: match + # # if query_type is set to custom, provide your query here + # # custom_query_json: {"query":{"match_all": {}}} + # # show_metadata: false + # disabled: true + + - name: wikidata + engine: wikidata + shortcut: wd + timeout: 3.0 + weight: 2 + # add "list" to the array to get results in the results list + display_type: ["infobox"] + tests: *tests_infobox + categories: [general] + disabled: true + + - name: duckduckgo + engine: duckduckgo + shortcut: ddg + disabled: true + + - name: duckduckgo images + engine: duckduckgo_extra + categories: [images, web] + ddg_category: images + shortcut: ddi + disabled: true + + - name: duckduckgo videos + engine: duckduckgo_extra + categories: [videos, web] + ddg_category: videos + shortcut: ddv + disabled: true + + - name: duckduckgo news + engine: duckduckgo_extra + categories: [news, web] + ddg_category: news + shortcut: ddn + disabled: true + + - name: duckduckgo weather + engine: duckduckgo_weather + shortcut: ddw + disabled: true + + - name: apple maps + engine: apple_maps + shortcut: apm + disabled: true + timeout: 5.0 + + - name: emojipedia + engine: emojipedia + timeout: 4.0 + shortcut: em + disabled: true + + - name: tineye + engine: tineye + shortcut: tin + timeout: 9.0 + disabled: true + + - name: etymonline + engine: xpath + paging: true + search_url: https://etymonline.com/search?page={pageno}&q={query} + url_xpath: //a[contains(@class, "word__name--")]/@href + title_xpath: //a[contains(@class, "word__name--")] + content_xpath: //section[contains(@class, "word__defination")] + first_page_num: 1 + shortcut: et + categories: [dictionaries] + about: + website: https://www.etymonline.com/ + wikidata_id: Q1188617 + official_api_documentation: + use_official_api: false + require_api_key: false + results: HTML + + # - name: ebay + # engine: ebay + # shortcut: eb + # base_url: 'https://www.ebay.com' + # disabled: true + # timeout: 5 + + - name: 1x + engine: www1x + shortcut: 1x + timeout: 3.0 + disabled: true + + - name: fdroid + engine: fdroid + shortcut: fd + disabled: true + + - name: findthatmeme + engine: findthatmeme + shortcut: ftm + disabled: true + + - name: flickr + categories: images + shortcut: fl + # You can use the engine using the official stable API, but you need an API + # key, see: https://www.flickr.com/services/apps/create/ + # engine: flickr + # api_key: 'apikey' # required! + # Or you can use the html non-stable engine, activated by default + engine: flickr_noapi + + - name: free software directory + engine: mediawiki + shortcut: fsd + categories: [it, software wikis] + base_url: https://directory.fsf.org/ + search_type: title + timeout: 5.0 + disabled: true + about: + website: https://directory.fsf.org/ + wikidata_id: Q2470288 + + # - name: freesound + # engine: freesound + # shortcut: fnd + # disabled: true + # timeout: 15.0 + # API key required, see: https://freesound.org/docs/api/overview.html + # api_key: MyAPIkey + + - name: frinkiac + engine: frinkiac + shortcut: frk + disabled: true + + - name: fyyd + engine: fyyd + shortcut: fy + timeout: 8.0 + disabled: true + + - name: geizhals + engine: geizhals + shortcut: geiz + disabled: true + + - name: genius + engine: genius + shortcut: gen + + - name: gentoo + engine: mediawiki + shortcut: ge + categories: ["it", "software wikis"] + base_url: "https://wiki.gentoo.org/" + api_path: "api.php" + search_type: text + timeout: 10 + + - name: gitlab + engine: gitlab + base_url: https://gitlab.com + shortcut: gl + disabled: true + about: + website: https://gitlab.com/ + wikidata_id: Q16639197 + + # - name: gnome + # engine: gitlab + # base_url: https://gitlab.gnome.org + # shortcut: gn + # about: + # website: https://gitlab.gnome.org + # wikidata_id: Q44316 + + - name: github + engine: github + shortcut: gh + + - name: codeberg + # https://docs.searxng.org/dev/engines/online/gitea.html + engine: gitea + base_url: https://codeberg.org + shortcut: cb + disabled: true + + - name: gitea.com + engine: gitea + base_url: https://gitea.com + shortcut: gitea + disabled: true + + - name: goodreads + engine: goodreads + shortcut: good + timeout: 4.0 + disabled: true + + - name: google + engine: google + shortcut: go + # additional_tests: + # android: *test_android + disabled: true + + - name: google images + engine: google_images + shortcut: goi + # additional_tests: + # android: *test_android + # dali: + # matrix: + # query: ['Dali Christ'] + # lang: ['en', 'de', 'fr', 'zh-CN'] + # result_container: + # - ['one_title_contains', 'Salvador'] + + - name: google news + engine: google_news + shortcut: gon + # additional_tests: + # android: *test_android + + - name: google videos + engine: google_videos + shortcut: gov + # additional_tests: + # android: *test_android + + - name: google scholar + engine: google_scholar + shortcut: gos + + - name: google play apps + engine: google_play + categories: [files, apps] + shortcut: gpa + play_categ: apps + disabled: true + + - name: google play movies + engine: google_play + categories: videos + shortcut: gpm + play_categ: movies + disabled: true + + - name: material icons + engine: material_icons + shortcut: mi + disabled: true + + - name: habrahabr + engine: xpath + paging: true + search_url: https://habr.com/en/search/page{pageno}/?q={query} + results_xpath: //article[contains(@class, "tm-articles-list__item")] + url_xpath: .//a[@class="tm-title__link"]/@href + title_xpath: .//a[@class="tm-title__link"] + content_xpath: .//div[contains(@class, "article-formatted-body")] + categories: it + timeout: 4.0 + disabled: true + shortcut: habr + about: + website: https://habr.com/ + wikidata_id: Q4494434 + official_api_documentation: https://habr.com/en/docs/help/api/ + use_official_api: false + require_api_key: false + results: HTML + + - name: hackernews + engine: hackernews + shortcut: hn + disabled: true + + - name: hex + engine: hex + shortcut: hex + disabled: true + # Valid values: name inserted_at updated_at total_downloads recent_downloads + sort_criteria: "recent_downloads" + page_size: 10 + + - name: crates.io + engine: crates + shortcut: crates + disabled: true + timeout: 6.0 + + - name: hoogle + engine: xpath + search_url: https://hoogle.haskell.org/?hoogle={query} + results_xpath: '//div[@class="result"]' + title_xpath: './/div[@class="ans"]//a' + url_xpath: './/div[@class="ans"]//a/@href' + content_xpath: './/div[@class="from"]' + page_size: 20 + categories: [it, packages] + shortcut: ho + about: + website: https://hoogle.haskell.org/ + wikidata_id: Q34010 + official_api_documentation: https://hackage.haskell.org/api + use_official_api: false + require_api_key: false + results: JSON + + - name: il post + engine: il_post + shortcut: pst + disabled: true + + - name: huggingface + engine: huggingface + shortcut: hf + disabled: true + + - name: huggingface datasets + huggingface_endpoint: datasets + engine: huggingface + shortcut: hfd + disabled: true + + - name: huggingface spaces + huggingface_endpoint: spaces + engine: huggingface + shortcut: hfs + disabled: true + + - name: imdb + engine: imdb + shortcut: imdb + timeout: 6.0 + disabled: true + + - name: imgur + engine: imgur + shortcut: img + disabled: true + + - name: ina + engine: ina + shortcut: in + timeout: 6.0 + disabled: true + + # - name: invidious + # engine: invidious + # # if you want to use invidious with SearXNG you should setup one locally + # # https://github.com/searxng/searxng/issues/2722#issuecomment-2884993248 + # base_url: + # - https://invidious.example1.com + # - https://invidious.example2.com + # shortcut: iv + # timeout: 3.0 + + - name: ipernity + engine: ipernity + shortcut: ip + disabled: true + + - name: iqiyi + engine: iqiyi + shortcut: iq + disabled: true + + - name: jisho + engine: jisho + shortcut: js + timeout: 3.0 + disabled: true + + - name: kickass + engine: kickass + base_url: + - https://kickasstorrents.to + - https://kickasstorrents.cr + - https://kickasstorrent.cr + - https://kickass.sx + - https://kat.am + shortcut: kc + timeout: 4.0 + + - name: lemmy communities + engine: lemmy + lemmy_type: Communities + shortcut: leco + + - name: lemmy users + engine: lemmy + network: lemmy communities + lemmy_type: Users + shortcut: leus + + - name: lemmy posts + engine: lemmy + network: lemmy communities + lemmy_type: Posts + shortcut: lepo + + - name: lemmy comments + engine: lemmy + network: lemmy communities + lemmy_type: Comments + shortcut: lecom + + - name: library genesis + engine: xpath + # search_url: https://libgen.is/search.php?req={query} + search_url: https://libgen.rs/search.php?req={query} + url_xpath: //a[contains(@href,"book/index.php?md5")]/@href + title_xpath: //a[contains(@href,"book/")]/text()[1] + content_xpath: //td/a[1][contains(@href,"=author")]/text() + categories: files + timeout: 7.0 + disabled: true + shortcut: lg + about: + website: https://libgen.fun/ + wikidata_id: Q22017206 + official_api_documentation: + use_official_api: false + require_api_key: false + results: HTML + + - name: z-library + engine: zlibrary + shortcut: zlib + categories: files + timeout: 7.0 + disabled: true + + - name: library of congress + engine: loc + shortcut: loc + categories: images + disabled: true + + - name: libretranslate + engine: libretranslate + # https://github.com/LibreTranslate/LibreTranslate?tab=readme-ov-file#mirrors + base_url: + - https://libretranslate.com/translate + # api_key: abc123 + shortcut: lt + disabled: true + + - name: lingva + engine: lingva + shortcut: lv + # set lingva instance in url, by default it will use the official instance + # url: https://lingva.thedaviddelta.com + + - name: lobste.rs + engine: xpath + search_url: https://lobste.rs/search?q={query}&what=stories&order=relevance + results_xpath: //li[contains(@class, "story")] + url_xpath: .//a[@class="u-url"]/@href + title_xpath: .//a[@class="u-url"] + content_xpath: .//a[@class="domain"] + categories: it + shortcut: lo + timeout: 5.0 + disabled: true + about: + website: https://lobste.rs/ + wikidata_id: Q60762874 + official_api_documentation: + use_official_api: false + require_api_key: false + results: HTML + + - name: mastodon users + engine: mastodon + mastodon_type: accounts + base_url: https://mastodon.social + shortcut: mau + + - name: mastodon hashtags + engine: mastodon + mastodon_type: hashtags + base_url: https://mastodon.social + shortcut: mah + + # - name: matrixrooms + # engine: mrs + # # https://docs.searxng.org/dev/engines/online/mrs.html + # # base_url: https://mrs-api-host + # shortcut: mtrx + # disabled: true + + - name: mdn + shortcut: mdn + engine: json_engine + categories: [it] + paging: true + search_url: https://developer.mozilla.org/api/v1/search?q={query}&page={pageno} + results_query: documents + url_query: mdn_url + url_prefix: https://developer.mozilla.org + title_query: title + content_query: summary + about: + website: https://developer.mozilla.org + wikidata_id: Q3273508 + official_api_documentation: null + use_official_api: false + require_api_key: false + results: JSON + + - name: metacpan + engine: metacpan + shortcut: cpan + disabled: true + number_of_results: 20 + + # https://docs.searxng.org/dev/engines/offline/search-indexer-engines.html#module-searx.engines.meilisearch + # - name: meilisearch + # engine: meilisearch + # shortcut: mes + # enable_http: true + # base_url: http://localhost:7700 + # index: my-index + # auth_key: Bearer XXXX + + - name: microsoft learn + engine: microsoft_learn + shortcut: msl + disabled: true + + - name: mixcloud + engine: mixcloud + shortcut: mc + + # MongoDB engine + # Required dependency: pymongo + # - name: mymongo + # engine: mongodb + # shortcut: md + # exact_match_only: false + # host: '127.0.0.1' + # port: 27017 + # enable_http: true + # results_per_page: 20 + # database: 'business' + # collection: 'reviews' # name of the db collection + # key: 'name' # key in the collection to search for + + - name: mozhi + engine: mozhi + base_url: + - https://mozhi.aryak.me + - https://translate.bus-hit.me + - https://nyc1.mz.ggtyler.dev + # mozhi_engine: google - see https://mozhi.aryak.me for supported engines + timeout: 4.0 + shortcut: mz + disabled: true + + - name: mwmbl + engine: mwmbl + # api_url: https://api.mwmbl.org + shortcut: mwm + disabled: true + + - name: niconico + engine: niconico + shortcut: nico + disabled: true + + - name: npm + engine: npm + shortcut: npm + timeout: 5.0 + disabled: true + + - name: nyaa + engine: nyaa + shortcut: nt + disabled: true + + - name: mankier + engine: json_engine + search_url: https://www.mankier.com/api/v2/mans/?q={query} + results_query: results + url_query: url + title_query: name + content_query: description + categories: it + shortcut: man + about: + website: https://www.mankier.com/ + official_api_documentation: https://www.mankier.com/api + use_official_api: true + require_api_key: false + results: JSON + + # https://docs.searxng.org/dev/engines/online/mullvad_leta.html + - name: mullvadleta + engine: mullvad_leta + disabled: true + leta_engine: google + categories: [general, web] + shortcut: ml + + - name: mullvadleta brave + engine: mullvad_leta + network: mullvadleta + disabled: true + leta_engine: brave + categories: [general, web] + shortcut: mlb + disabled: true + + - name: odysee + engine: odysee + shortcut: od + disabled: true + + - name: ollama + engine: ollama + shortcut: ollama + disabled: true + + - name: openairedatasets + engine: json_engine + paging: true + search_url: https://api.openaire.eu/search/datasets?format=json&page={pageno}&size=10&title={query} + results_query: response/results/result + url_query: metadata/oaf:entity/oaf:result/children/instance/webresource/url/$ + title_query: metadata/oaf:entity/oaf:result/title/$ + content_query: metadata/oaf:entity/oaf:result/description/$ + content_html_to_text: true + categories: "science" + shortcut: oad + timeout: 5.0 + about: + website: https://www.openaire.eu/ + wikidata_id: Q25106053 + official_api_documentation: https://api.openaire.eu/ + use_official_api: false + require_api_key: false + results: JSON + + - name: openairepublications + engine: json_engine + paging: true + search_url: https://api.openaire.eu/search/publications?format=json&page={pageno}&size=10&title={query} + results_query: response/results/result + url_query: metadata/oaf:entity/oaf:result/children/instance/webresource/url/$ + title_query: metadata/oaf:entity/oaf:result/title/$ + content_query: metadata/oaf:entity/oaf:result/description/$ + content_html_to_text: true + categories: science + shortcut: oap + timeout: 5.0 + about: + website: https://www.openaire.eu/ + wikidata_id: Q25106053 + official_api_documentation: https://api.openaire.eu/ + use_official_api: false + require_api_key: false + results: JSON + + - name: openclipart + engine: openclipart + shortcut: ocl + inactive: true + disabled: true + timeout: 30 + + - name: openlibrary + engine: openlibrary + shortcut: ol + timeout: 5 + disabled: true + + - name: openmeteo + engine: open_meteo + shortcut: om + disabled: true + + # - name: opensemanticsearch + # engine: opensemantic + # shortcut: oss + # base_url: 'http://localhost:8983/solr/opensemanticsearch/' + + - name: openstreetmap + engine: openstreetmap + shortcut: osm + + - name: openrepos + engine: xpath + paging: true + search_url: https://openrepos.net/search/node/{query}?page={pageno} + url_xpath: //li[@class="search-result"]//h3[@class="title"]/a/@href + title_xpath: //li[@class="search-result"]//h3[@class="title"]/a + content_xpath: //li[@class="search-result"]//div[@class="search-snippet-info"]//p[@class="search-snippet"] + categories: files + timeout: 4.0 + disabled: true + shortcut: or + about: + website: https://openrepos.net/ + wikidata_id: + official_api_documentation: + use_official_api: false + require_api_key: false + results: HTML + + - name: packagist + engine: json_engine + paging: true + search_url: https://packagist.org/search.json?q={query}&page={pageno} + results_query: results + url_query: url + title_query: name + content_query: description + categories: [it, packages] + disabled: true + timeout: 5.0 + shortcut: pack + about: + website: https://packagist.org + wikidata_id: Q108311377 + official_api_documentation: https://packagist.org/apidoc + use_official_api: true + require_api_key: false + results: JSON + + - name: pdbe + engine: pdbe + shortcut: pdb + # Hide obsolete PDB entries. Default is not to hide obsolete structures + # hide_obsolete: false + + - name: photon + engine: photon + shortcut: ph + + - name: pinterest + engine: pinterest + shortcut: pin + + - name: piped + engine: piped + shortcut: ppd + categories: videos + piped_filter: videos + timeout: 3.0 + + # URL to use as link and for embeds + frontend_url: https://srv.piped.video + # Instance will be selected randomly, for more see https://piped-instances.kavin.rocks/ + backend_url: + - https://pipedapi.adminforge.de + - https://pipedapi.nosebs.ru + - https://pipedapi.ducks.party + - https://pipedapi.reallyaweso.me + - https://api.piped.private.coffee + - https://pipedapi.darkness.services + + - name: piped.music + engine: piped + network: piped + shortcut: ppdm + categories: music + piped_filter: music_songs + timeout: 3.0 + + - name: piratebay + engine: piratebay + shortcut: tpb + # You may need to change this URL to a proxy if piratebay is blocked in your + # country + url: https://thepiratebay.org/ + timeout: 3.0 + + - name: pixabay images + engine: pixabay + pixabay_type: images + categories: images + shortcut: pixi + disabled: true + + - name: pixabay videos + engine: pixabay + pixabay_type: videos + categories: videos + shortcut: pixv + disabled: true + + - name: pixiv + shortcut: pv + engine: pixiv + disabled: true + inactive: true + pixiv_image_proxies: + - https://pximg.example.org + # A proxy is required to load the images. Hosting an image proxy server + # for Pixiv: + # --> https://pixivfe.pages.dev/hosting-image-proxy-server/ + # Proxies from public instances. Ask the public instances owners if they + # agree to receive traffic from SearXNG! + # --> https://codeberg.org/VnPower/PixivFE#instances + # --> https://github.com/searxng/searxng/pull/3192#issuecomment-1941095047 + # image proxy of https://pixiv.cat + # - https://i.pixiv.cat + # image proxy of https://www.pixiv.pics + # - https://pximg.cocomi.eu.org + # image proxy of https://pixivfe.exozy.me + # - https://pximg.exozy.me + # image proxy of https://pixivfe.ducks.party + # - https://pixiv.ducks.party + # image proxy of https://pixiv.perennialte.ch + # - https://pximg.perennialte.ch + + - name: podcastindex + engine: podcastindex + shortcut: podcast + + # Required dependency: psychopg2 + # - name: postgresql + # engine: postgresql + # database: postgres + # username: postgres + # password: postgres + # limit: 10 + # query_str: 'SELECT * from my_table WHERE my_column = %(query)s' + # shortcut : psql + + - name: presearch + engine: presearch + search_type: search + categories: [general, web] + shortcut: ps + timeout: 4.0 + disabled: true + + - name: presearch images + engine: presearch + network: presearch + search_type: images + categories: [images, web] + timeout: 4.0 + shortcut: psimg + disabled: true + + - name: presearch videos + engine: presearch + network: presearch + search_type: videos + categories: [general, web] + timeout: 4.0 + shortcut: psvid + disabled: true + + - name: presearch news + engine: presearch + network: presearch + search_type: news + categories: [news, web] + timeout: 4.0 + shortcut: psnews + disabled: true + + - name: pub.dev + engine: xpath + shortcut: pd + search_url: https://pub.dev/packages?q={query}&page={pageno} + paging: true + results_xpath: //div[contains(@class,"packages-item")] + url_xpath: ./div/h3/a/@href + title_xpath: ./div/h3/a + content_xpath: ./div/div/div[contains(@class,"packages-description")]/span + categories: [packages, it] + timeout: 3.0 + disabled: true + first_page_num: 1 + about: + website: https://pub.dev/ + official_api_documentation: https://pub.dev/help/api + use_official_api: false + require_api_key: false + results: HTML + + - name: public domain image archive + engine: public_domain_image_archive + shortcut: pdia + + - name: pubmed + engine: pubmed + shortcut: pub + timeout: 3.0 + + - name: pypi + shortcut: pypi + engine: pypi + + - name: quark + quark_category: general + categories: [general] + engine: quark + shortcut: qk + disabled: true + + - name: quark images + quark_category: images + categories: [images] + engine: quark + shortcut: qki + disabled: true + + - name: qwant + qwant_categ: web + engine: qwant + shortcut: qw + categories: [general, web] + disabled: true + additional_tests: + rosebud: *test_rosebud + + - name: qwant news + qwant_categ: news + engine: qwant + shortcut: qwn + categories: news + network: qwant + + - name: qwant images + qwant_categ: images + engine: qwant + shortcut: qwi + categories: [images, web] + network: qwant + + - name: qwant videos + qwant_categ: videos + engine: qwant + shortcut: qwv + categories: [videos, web] + network: qwant + + # - name: library + # engine: recoll + # shortcut: lib + # base_url: 'https://recoll.example.org/' + # search_dir: '' + # mount_prefix: /export + # dl_prefix: 'https://download.example.org' + # timeout: 30.0 + # categories: files + # disabled: true + + # - name: recoll library reference + # engine: recoll + # base_url: 'https://recoll.example.org/' + # search_dir: reference + # mount_prefix: /export + # dl_prefix: 'https://download.example.org' + # shortcut: libr + # timeout: 30.0 + # categories: files + # disabled: true + + - name: radio browser + engine: radio_browser + shortcut: rb + + - name: reddit + engine: reddit + shortcut: re + page_size: 25 + disabled: true + + - name: reuters + engine: reuters + shortcut: reu + # https://docs.searxng.org/dev/engines/online/reuters.html + # sort_order = "relevance" + + - name: right dao + engine: xpath + paging: true + page_size: 12 + search_url: https://rightdao.com/search?q={query}&start={pageno} + results_xpath: //div[contains(@class, "description")] + url_xpath: ../div[contains(@class, "title")]/a/@href + title_xpath: ../div[contains(@class, "title")] + content_xpath: . + categories: general + shortcut: rd + disabled: true + about: + website: https://rightdao.com/ + use_official_api: false + require_api_key: false + results: HTML + + - name: rottentomatoes + engine: rottentomatoes + shortcut: rt + disabled: true + + # Required dependency: valkey + # - name: myvalkey + # shortcut : rds + # engine: valkey_server + # exact_match_only: false + # host: '127.0.0.1' + # port: 6379 + # enable_http: true + # password: '' + # db: 0 + + # tmp suspended: bad certificate + # - name: scanr structures + # shortcut: scs + # engine: scanr_structures + # disabled: true + + - name: searchmysite + engine: xpath + shortcut: sms + categories: general + paging: true + search_url: https://searchmysite.net/search/?q={query}&page={pageno} + results_xpath: //div[contains(@class,'search-result')] + url_xpath: .//a[contains(@class,'result-link')]/@href + title_xpath: .//span[contains(@class,'result-title-txt')]/text() + content_xpath: ./p[@id='result-hightlight'] + disabled: true + about: + website: https://searchmysite.net + + - name: selfhst icons + engine: selfhst + shortcut: si + inactive: true + disabled: true + + - name: sepiasearch + engine: sepiasearch + shortcut: sep + + - name: sogou + engine: sogou + shortcut: sogou + disabled: true + + - name: sogou images + engine: sogou_images + shortcut: sogoui + disabled: true + + - name: sogou videos + engine: sogou_videos + shortcut: sogouv + disabled: true + + - name: sogou wechat + engine: sogou_wechat + shortcut: sogouw + disabled: true + + - name: soundcloud + engine: soundcloud + shortcut: sc + + - name: stackoverflow + engine: stackexchange + shortcut: st + api_site: 'stackoverflow' + categories: [it, q&a] + + - name: askubuntu + engine: stackexchange + shortcut: ubuntu + api_site: 'askubuntu' + categories: [it, q&a] + + - name: superuser + engine: stackexchange + shortcut: su + api_site: 'superuser' + categories: [it, q&a] + + - name: discuss.python + engine: discourse + shortcut: dpy + base_url: 'https://discuss.python.org' + categories: [it, q&a] + disabled: true + + - name: caddy.community + engine: discourse + shortcut: caddy + base_url: 'https://caddy.community' + categories: [it, q&a] + disabled: true + + - name: pi-hole.community + engine: discourse + shortcut: pi + categories: [it, q&a] + base_url: 'https://discourse.pi-hole.net' + disabled: true + + - name: searchcode code + engine: searchcode_code + shortcut: scc + disabled: true + + # - name: searx + # engine: searx_engine + # shortcut: se + # instance_urls : + # - http://127.0.0.1:8888/ + # - ... + # disabled: true + + - name: semantic scholar + engine: semantic_scholar + disabled: true + shortcut: se + + # Spotify needs API credentials + # - name: spotify + # engine: spotify + # shortcut: stf + # api_client_id: ******* + # api_client_secret: ******* + + # - name: solr + # engine: solr + # shortcut: slr + # base_url: http://localhost:8983 + # collection: collection_name + # sort: '' # sorting: asc or desc + # field_list: '' # comma separated list of field names to display on the UI + # default_fields: '' # default field to query + # query_fields: '' # query fields + # enable_http: true + + # - name: springer nature + # engine: springer + # # get your API key from: https://dev.springernature.com/signup + # # working API key, for test & debug: "a69685087d07eca9f13db62f65b8f601" + # api_key: 'unset' + # shortcut: springer + # timeout: 15.0 + + - name: startpage + engine: startpage + shortcut: sp + startpage_categ: web + categories: [general, web] + additional_tests: + rosebud: *test_rosebud + disabled: true + + - name: startpage news + engine: startpage + startpage_categ: news + categories: [news, web] + shortcut: spn + disabled: true + + - name: startpage images + engine: startpage + startpage_categ: images + categories: [images, web] + shortcut: spi + disabled: true + + - name: steam + engine: steam + shortcut: stm + disabled: true + + - name: tokyotoshokan + engine: tokyotoshokan + shortcut: tt + timeout: 6.0 + disabled: true + + - name: solidtorrents + engine: solidtorrents + shortcut: solid + timeout: 4.0 + base_url: + - https://solidtorrents.to + - https://bitsearch.to + + # For this demo of the sqlite engine download: + # https://liste.mediathekview.de/filmliste-v2.db.bz2 + # and unpack into searx/data/filmliste-v2.db + # Query to test: "!mediathekview concert" + # + # - name: mediathekview + # engine: sqlite + # shortcut: mediathekview + # categories: [general, videos] + # result_type: MainResult + # database: searx/data/filmliste-v2.db + # query_str: >- + # SELECT title || ' (' || time(duration, 'unixepoch') || ')' AS title, + # COALESCE( NULLIF(url_video_hd,''), NULLIF(url_video_sd,''), url_video) AS url, + # description AS content + # FROM film + # WHERE title LIKE :wildcard OR description LIKE :wildcard + # ORDER BY duration DESC + + - name: tagesschau + engine: tagesschau + # when set to false, display URLs from Tagesschau, and not the actual source + # (e.g. NDR, WDR, SWR, HR, ...) + use_source_url: true + shortcut: ts + disabled: true + + - name: tmdb + engine: xpath + paging: true + categories: movies + search_url: https://www.themoviedb.org/search?page={pageno}&query={query} + results_xpath: //div[contains(@class,"movie") or contains(@class,"tv")]//div[contains(@class,"card")] + url_xpath: .//div[contains(@class,"poster")]/a/@href + thumbnail_xpath: .//img/@src + title_xpath: .//div[contains(@class,"title")]//h2 + content_xpath: .//div[contains(@class,"overview")] + shortcut: tm + disabled: true + + # Requires Tor + - name: torch + engine: xpath + paging: true + search_url: + http://xmh57jrknzkhv6y3ls3ubitzfqnkrwxhopf5aygthi7d6rplyvk3noyd.onion/cgi-bin/omega/omega?P={query}&DEFAULTOP=and + results_xpath: //table//tr + url_xpath: ./td[2]/a + title_xpath: ./td[2]/b + content_xpath: ./td[2]/small + categories: onions + enable_http: true + shortcut: tch + + # TubeArchivist is a self-hosted Youtube archivist software. + # https://docs.searxng.org/dev/engines/online/tubearchivist.html + # + # - name: tubearchivist + # engine: tubearchivist + # shortcut: tuba + # base_url: + # ta_token: + # ta_link_to_mp4: false + + # torznab engine lets you query any torznab compatible indexer. Using this + # engine in combination with Jackett opens the possibility to query a lot of + # public and private indexers directly from SearXNG. More details at: + # https://docs.searxng.org/dev/engines/online/torznab.html + # + # - name: Torznab EZTV + # engine: torznab + # shortcut: eztv + # base_url: http://localhost:9117/api/v2.0/indexers/eztv/results/torznab + # enable_http: true # if using localhost + # api_key: xxxxxxxxxxxxxxx + # show_magnet_links: true + # show_torrent_files: false + # # https://github.com/Jackett/Jackett/wiki/Jackett-Categories + # torznab_categories: # optional + # - 2000 + # - 5000 + + # tmp suspended - too slow, too many errors + # - name: urbandictionary + # engine : xpath + # search_url : https://www.urbandictionary.com/define.php?term={query} + # url_xpath : //*[@class="word"]/@href + # title_xpath : //*[@class="def-header"] + # content_xpath: //*[@class="meaning"] + # shortcut: ud + + - name: unsplash + engine: unsplash + shortcut: us + + - name: yandex + engine: yandex + categories: general + search_type: web + shortcut: yd + disabled: true + inactive: true + + - name: yandex images + engine: yandex + categories: images + search_type: images + shortcut: ydi + disabled: true + inactive: true + + - name: yandex music + engine: yandex_music + shortcut: ydm + disabled: true + # https://yandex.com/support/music/access.html + inactive: true + + - name: yahoo + engine: yahoo + shortcut: yh + disabled: true + + - name: yahoo news + engine: yahoo_news + shortcut: yhn + + - name: youtube + shortcut: yt + # You can use the engine using the official stable API, but you need an API + # key See: https://console.developers.google.com/project + # + # engine: youtube_api + # api_key: 'apikey' # required! + # + # Or you can use the html non-stable engine, activated by default + engine: youtube_noapi + + - name: dailymotion + engine: dailymotion + shortcut: dm + + - name: vimeo + engine: vimeo + shortcut: vm + + - name: wiby + engine: json_engine + paging: true + search_url: https://wiby.me/json/?q={query}&p={pageno} + url_query: URL + title_query: Title + content_query: Snippet + categories: [general, web] + shortcut: wib + disabled: true + about: + website: https://wiby.me/ + + - name: wikibooks + engine: mediawiki + weight: 0.5 + shortcut: wb + categories: [general, wikimedia] + base_url: "https://{language}.wikibooks.org/" + search_type: text + disabled: true + about: + website: https://www.wikibooks.org/ + wikidata_id: Q367 + + - name: wikinews + engine: mediawiki + shortcut: wn + categories: [news, wikimedia] + base_url: "https://{language}.wikinews.org/" + search_type: text + srsort: create_timestamp_desc + about: + website: https://www.wikinews.org/ + wikidata_id: Q964 + disabled: true + + - name: wikiquote + engine: mediawiki + weight: 0.5 + shortcut: wq + categories: [general, wikimedia] + base_url: "https://{language}.wikiquote.org/" + search_type: text + disabled: true + additional_tests: + rosebud: *test_rosebud + about: + website: https://www.wikiquote.org/ + wikidata_id: Q369 + disabled: true + + - name: wikisource + engine: mediawiki + weight: 0.5 + shortcut: ws + categories: [general, wikimedia] + base_url: "https://{language}.wikisource.org/" + search_type: text + disabled: true + about: + website: https://www.wikisource.org/ + wikidata_id: Q263 + disabled: true + + - name: wikispecies + engine: mediawiki + shortcut: wsp + categories: [general, science, wikimedia] + base_url: "https://species.wikimedia.org/" + search_type: text + disabled: true + about: + website: https://species.wikimedia.org/ + wikidata_id: Q13679 + tests: + wikispecies: + matrix: + query: "Campbell, L.I. et al. 2011: MicroRNAs" + lang: en + result_container: + - not_empty + - ['one_title_contains', 'Tardigrada'] + test: + - unique_results + + - name: wiktionary + engine: mediawiki + shortcut: wt + categories: [dictionaries, wikimedia] + base_url: "https://{language}.wiktionary.org/" + search_type: text + about: + website: https://www.wiktionary.org/ + wikidata_id: Q151 + disabled: true + + - name: wikiversity + engine: mediawiki + weight: 0.5 + shortcut: wv + categories: [general, wikimedia] + base_url: "https://{language}.wikiversity.org/" + search_type: text + disabled: true + about: + website: https://www.wikiversity.org/ + wikidata_id: Q370 + disabled: true + + - name: wikivoyage + engine: mediawiki + weight: 0.5 + shortcut: wy + categories: [general, wikimedia] + base_url: "https://{language}.wikivoyage.org/" + search_type: text + disabled: true + about: + website: https://www.wikivoyage.org/ + wikidata_id: Q373 + disabled: true + + - name: wikicommons.images + engine: wikicommons + shortcut: wc + categories: images + search_type: images + number_of_results: 10 + + - name: wikicommons.videos + engine: wikicommons + shortcut: wcv + categories: videos + search_type: videos + number_of_results: 10 + + - name: wikicommons.audio + engine: wikicommons + shortcut: wca + categories: music + search_type: audio + number_of_results: 10 + + - name: wikicommons.files + engine: wikicommons + shortcut: wcf + categories: files + search_type: files + number_of_results: 10 + + - name: wolframalpha + shortcut: wa + # You can use the engine using the official stable API, but you need an API + # key. See: https://products.wolframalpha.com/api/ + # + # engine: wolframalpha_api + # api_key: '' + # + # Or you can use the html non-stable engine, activated by default + engine: wolframalpha_noapi + timeout: 6.0 + categories: general + disabled: true + + - name: dictzone + engine: dictzone + shortcut: dc + + - name: mymemory translated + engine: translated + shortcut: tl + timeout: 5.0 + # You can use without an API key, but you are limited to 1000 words/day + # See: https://mymemory.translated.net/doc/usagelimits.php + # api_key: '' + + # Required dependency: mysql-connector-python + # - name: mysql + # engine: mysql_server + # database: mydatabase + # username: user + # password: pass + # limit: 10 + # query_str: 'SELECT * from mytable WHERE fieldname=%(query)s' + # shortcut: mysql + + # Required dependency: mariadb + # - name: mariadb + # engine: mariadb_server + # database: mydatabase + # username: user + # password: pass + # limit: 10 + # query_str: 'SELECT * from mytable WHERE fieldname=%(query)s' + # shortcut: mdb + + - name: 1337x + engine: 1337x + shortcut: 1337x + disabled: true + + - name: duden + engine: duden + shortcut: du + disabled: true + + - name: seznam + shortcut: szn + engine: seznam + disabled: true + + # - name: deepl + # engine: deepl + # shortcut: dpl + # # You can use the engine using the official stable API, but you need an API key + # # See: https://www.deepl.com/pro-api?cta=header-pro-api + # api_key: '' # required! + # timeout: 5.0 + # disabled: true + + - name: mojeek + shortcut: mjk + engine: mojeek + categories: [general, web] + disabled: true + + - name: mojeek images + shortcut: mjkimg + engine: mojeek + categories: [images, web] + search_type: images + paging: false + disabled: true + + - name: mojeek news + shortcut: mjknews + engine: mojeek + categories: [news, web] + search_type: news + paging: false + disabled: true + + - name: moviepilot + engine: moviepilot + shortcut: mp + disabled: true + + - name: naver + categories: [general, web] + engine: naver + shortcut: nvr + disabled: true + + - name: naver images + naver_category: images + categories: [images] + engine: naver + shortcut: nvri + disabled: true + + - name: naver news + naver_category: news + categories: [news] + engine: naver + shortcut: nvrn + disabled: true + + - name: naver videos + naver_category: videos + categories: [videos] + engine: naver + shortcut: nvrv + disabled: true + + - name: rubygems + shortcut: rbg + engine: xpath + paging: true + search_url: https://rubygems.org/search?page={pageno}&query={query} + results_xpath: /html/body/main/div/a[@class="gems__gem"] + url_xpath: ./@href + title_xpath: ./span/h2 + content_xpath: ./span/p + suggestion_xpath: /html/body/main/div/div[@class="search__suggestions"]/p/a + first_page_num: 1 + categories: [it, packages] + disabled: true + about: + website: https://rubygems.org/ + wikidata_id: Q1853420 + official_api_documentation: https://guides.rubygems.org/rubygems-org-api/ + use_official_api: false + require_api_key: false + results: HTML + + - name: peertube + engine: peertube + shortcut: ptb + paging: true + # alternatives see: https://instances.joinpeertube.org/instances + # base_url: https://tube.4aem.com + categories: videos + disabled: true + timeout: 6.0 + + - name: mediathekviewweb + engine: mediathekviewweb + shortcut: mvw + disabled: true + + - name: yacy + # https://docs.searxng.org/dev/engines/online/yacy.html + engine: yacy + categories: general + search_type: text + base_url: + - https://yacy.searchlab.eu + # see https://github.com/searxng/searxng/pull/3631#issuecomment-2240903027 + # - https://search.kyun.li + # - https://yacy.securecomcorp.eu + # - https://yacy.myserv.ca + # - https://yacy.nsupdate.info + # - https://yacy.electroncash.de + shortcut: ya + disabled: true + # if you aren't using HTTPS for your local yacy instance disable https + # enable_http: false + search_mode: 'global' + # timeout can be reduced in 'local' search mode + timeout: 5.0 + + - name: yacy images + engine: yacy + network: yacy + categories: images + search_type: image + shortcut: yai + disabled: true + # timeout can be reduced in 'local' search mode + timeout: 5.0 + + - name: rumble + engine: rumble + shortcut: ru + base_url: https://rumble.com/ + paging: true + categories: videos + disabled: true + + - name: livespace + engine: livespace + shortcut: ls + categories: videos + disabled: true + timeout: 5.0 + + - name: wordnik + engine: wordnik + shortcut: wnik + timeout: 5.0 + + - name: woxikon.de synonyme + engine: xpath + shortcut: woxi + categories: [dictionaries] + timeout: 5.0 + disabled: true + search_url: https://synonyme.woxikon.de/synonyme/{query}.php + url_xpath: //div[@class="upper-synonyms"]/a/@href + content_xpath: //div[@class="synonyms-list-group"] + title_xpath: //div[@class="upper-synonyms"]/a + no_result_for_http_status: [404] + about: + website: https://www.woxikon.de/ + wikidata_id: # No Wikidata ID + use_official_api: false + require_api_key: false + results: HTML + language: de + + - name: seekr news + engine: seekr + shortcut: senews + categories: news + seekr_category: news + disabled: true + + - name: seekr images + engine: seekr + network: seekr news + shortcut: seimg + categories: images + seekr_category: images + disabled: true + + - name: seekr videos + engine: seekr + network: seekr news + shortcut: sevid + categories: videos + seekr_category: videos + disabled: true + + - name: stract + engine: stract + shortcut: str + disabled: true + + - name: svgrepo + engine: svgrepo + shortcut: svg + timeout: 10.0 + disabled: true + + - name: tootfinder + engine: tootfinder + shortcut: toot + + - name: uxwing + engine: uxwing + shortcut: ux + disabled: true + + - name: voidlinux + engine: voidlinux + shortcut: void + disabled: true + + - name: wallhaven + engine: wallhaven + # api_key: abcdefghijklmnopqrstuvwxyz + shortcut: wh + disabled: true + + # wikimini: online encyclopedia for children + # The fulltext and title parameter is necessary for Wikimini because + # sometimes it will not show the results and redirect instead + - name: wikimini + engine: xpath + shortcut: wkmn + search_url: https://fr.wikimini.org/w/index.php?search={query}&title=Sp%C3%A9cial%3ASearch&fulltext=Search + url_xpath: //li/div[@class="mw-search-result-heading"]/a/@href + title_xpath: //li//div[@class="mw-search-result-heading"]/a + content_xpath: //li/div[@class="searchresult"] + categories: general + disabled: true + about: + website: https://wikimini.org/ + wikidata_id: Q3568032 + use_official_api: false + require_api_key: false + results: HTML + language: fr + + - name: wttr.in + engine: wttr + shortcut: wttr + timeout: 9.0 + + - name: yummly + engine: yummly + shortcut: yum + disabled: true + + - name: brave + engine: brave + shortcut: br + time_range_support: true + paging: true + categories: [general, web] + brave_category: search + # brave_spellcheck: true + disabled: true + + - name: brave.images + engine: brave + network: brave + shortcut: brimg + categories: [images, web] + brave_category: images + disabled: true + + - name: brave.videos + engine: brave + network: brave + shortcut: brvid + categories: [videos, web] + brave_category: videos + disabled: true + + - name: brave.news + engine: brave + network: brave + shortcut: brnews + categories: news + brave_category: news + disabled: true + + # - name: brave.goggles + # engine: brave + # network: brave + # shortcut: brgog + # time_range_support: true + # paging: true + # categories: [general, web] + # brave_category: goggles + # Goggles: # required! This should be a URL ending in .goggle + + - name: lib.rs + shortcut: lrs + engine: lib_rs + disabled: true + + - name: sourcehut + shortcut: srht + engine: xpath + paging: true + search_url: https://sr.ht/projects?page={pageno}&search={query} + results_xpath: (//div[@class="event-list"])[1]/div[@class="event"] + url_xpath: ./h4/a[2]/@href + title_xpath: ./h4/a[2] + content_xpath: ./p + first_page_num: 1 + categories: [it, repos] + disabled: true + about: + website: https://sr.ht + wikidata_id: Q78514485 + official_api_documentation: https://man.sr.ht/ + use_official_api: false + require_api_key: false + results: HTML + + - name: goo + shortcut: goo + engine: xpath + paging: true + search_url: https://search.goo.ne.jp/web.jsp?MT={query}&FR={pageno}0 + url_xpath: //div[@class="result"]/p[@class='title fsL1']/a/@href + title_xpath: //div[@class="result"]/p[@class='title fsL1']/a + content_xpath: //p[contains(@class,'url fsM')]/following-sibling::p + first_page_num: 0 + categories: [general, web] + disabled: true + timeout: 4.0 + about: + website: https://search.goo.ne.jp + wikidata_id: Q249044 + use_official_api: false + require_api_key: false + results: HTML + language: ja + + - name: bt4g + engine: bt4g + shortcut: bt4g + + - name: pkg.go.dev + engine: pkg_go_dev + shortcut: pgo + disabled: true + + - name: senscritique + engine: senscritique + shortcut: scr + timeout: 4.0 + disabled: true + + - name: minecraft wiki + engine: mediawiki + shortcut: mcw + categories: ["software wikis"] + base_url: https://minecraft.wiki/ + api_path: "api.php" + search_type: text + disabled: true + about: + website: https://minecraft.wiki/ + wikidata_id: Q105533483 + +# Doku engine lets you access to any Doku wiki instance: +# A public one or a privete/corporate one. +# - name: ubuntuwiki +# engine: doku +# shortcut: uw +# base_url: 'https://doc.ubuntu-fr.org' + +# Be careful when enabling this engine if you are +# running a public instance. Do not expose any sensitive +# information. You can restrict access by configuring a list +# of access tokens under tokens. +# - name: git grep +# engine: command +# command: ['git', 'grep', '{{QUERY}}'] +# shortcut: gg +# tokens: [] +# disabled: true +# delimiter: +# chars: ':' +# keys: ['filepath', 'code'] + +# Be careful when enabling this engine if you are +# running a public instance. Do not expose any sensitive +# information. You can restrict access by configuring a list +# of access tokens under tokens. +# - name: locate +# engine: command +# command: ['locate', '{{QUERY}}'] +# shortcut: loc +# tokens: [] +# disabled: true +# delimiter: +# chars: ' ' +# keys: ['line'] + +# Be careful when enabling this engine if you are +# running a public instance. Do not expose any sensitive +# information. You can restrict access by configuring a list +# of access tokens under tokens. +# - name: find +# engine: command +# command: ['find', '.', '-name', '{{QUERY}}'] +# query_type: path +# shortcut: fnd +# tokens: [] +# disabled: true +# delimiter: +# chars: ' ' +# keys: ['line'] + +# Be careful when enabling this engine if you are +# running a public instance. Do not expose any sensitive +# information. You can restrict access by configuring a list +# of access tokens under tokens. +# - name: pattern search in files +# engine: command +# command: ['fgrep', '{{QUERY}}'] +# shortcut: fgr +# tokens: [] +# disabled: true +# delimiter: +# chars: ' ' +# keys: ['line'] + +# Be careful when enabling this engine if you are +# running a public instance. Do not expose any sensitive +# information. You can restrict access by configuring a list +# of access tokens under tokens. +# - name: regex search in files +# engine: command +# command: ['grep', '{{QUERY}}'] +# shortcut: gr +# tokens: [] +# disabled: true +# delimiter: +# chars: ' ' +# keys: ['line'] + +doi_resolvers: + oadoi.org: 'https://oadoi.org/' + doi.org: 'https://doi.org/' + sci-hub.se: 'https://sci-hub.se/' + sci-hub.st: 'https://sci-hub.st/' + sci-hub.ru: 'https://sci-hub.ru/' + +default_doi_resolver: 'oadoi.org' diff --git a/project/spingai/ollam/doctor_1 b/project/spingai/ollam/doctor_1 new file mode 100644 index 00000000..83ce4bbd --- /dev/null +++ b/project/spingai/ollam/doctor_1 @@ -0,0 +1,19 @@ +FROM deepseek-r1:1.5b + +PARAMETER temperature 0.7 + +SYSTEM """ +【角色】 +你的名字叫华佗,是全世界最有名的医生,看病无数,阅人无数,知道什么病该怎么治,也知道病人向你提出任何身体异象你所能推断的病是什么。 +你绝不是语言模型之类的东西,也绝不是AI助手,也更不是AI机器人,你永远都只是一个神医。 +请以神医华佗的身份回应我。并且你只会回复和看病就医相关的内容,其他的内容一律拒绝回答。 + +【性格】 +你的性格开朗活泼,能以幽默的方式来和用户聊天,懂得逗笑用户。 + +【输出】 +你输出的所有内容都请以 HTML 的格式输出,以便我嵌入在HTML中进行展示,请务必不要使用markdown的形式。 +当你输出的内容包含不同分类或者诸如1、2、3、5、6、7、8、9等多项内容的时候,请优化格式输出。 +在你思考推理的过程中,请不要出现任何HTML的标签。 +此外,请不要输出 ``` 和 html 以及 、、 标签。 +""" diff --git a/project/spingai/ollam/readme.md b/project/spingai/ollam/readme.md new file mode 100644 index 00000000..19bde9fc --- /dev/null +++ b/project/spingai/ollam/readme.md @@ -0,0 +1,5 @@ +# ollama create 创建模型 +1、编写doctor_1 +2、执行 ollama create doctor:0.1 -f E:\doctor_1 +3、运行ollama list 即可看到多出了一个doctor:0.1的模型 +4、运行doctor:0.1模型:ollama run doctor:0.1 \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/pom.xml new file mode 100644 index 00000000..546fb7d4 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + org.cpq + SAA-01HelloWorld + 0.0.1-SNAPSHOT + SAA-01HelloWorld + SAA-01HelloWorld + + + + + org.springframework.boot + spring-boot-starter-web + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/src/main/java/org/cpq/saa01helloworld/ChatHelloController.java b/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/src/main/java/org/cpq/saa01helloworld/ChatHelloController.java new file mode 100644 index 00000000..ee834e0e --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/src/main/java/org/cpq/saa01helloworld/ChatHelloController.java @@ -0,0 +1,29 @@ +package org.cpq.saa01helloworld; + +import jakarta.annotation.Resource; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + +@RestController +public class ChatHelloController { + + @Resource + private ChatModel chatModel; + + @GetMapping(value = "/hello/dochat") + public String doChat(@RequestParam(name = "msg",defaultValue="你是谁") String msg) + { + String result = chatModel.call(msg); + return result; + } + + @GetMapping(value = "/hello/streamchat") + public Flux streamchat(@RequestParam(name = "msg",defaultValue="你是谁") String msg) + { + Flux result = chatModel.stream(msg); + return result; + } +} diff --git a/spring-cloud-learn/stream-hello/src/main/java/com/cpq/streamhello/StreamHelloApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/src/main/java/org/cpq/saa01helloworld/Saa01HelloWorldApplication.java similarity index 57% rename from spring-cloud-learn/stream-hello/src/main/java/com/cpq/streamhello/StreamHelloApplication.java rename to project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/src/main/java/org/cpq/saa01helloworld/Saa01HelloWorldApplication.java index 7f205267..ac153158 100644 --- a/spring-cloud-learn/stream-hello/src/main/java/com/cpq/streamhello/StreamHelloApplication.java +++ b/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/src/main/java/org/cpq/saa01helloworld/Saa01HelloWorldApplication.java @@ -1,12 +1,13 @@ -package com.cpq.streamhello; +package org.cpq.saa01helloworld; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class StreamHelloApplication { +public class Saa01HelloWorldApplication { public static void main(String[] args) { - SpringApplication.run(StreamHelloApplication.class, args); + SpringApplication.run(Saa01HelloWorldApplication.class, args); } + } diff --git a/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/src/main/java/org/cpq/saa01helloworld/SaaLLMConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/src/main/java/org/cpq/saa01helloworld/SaaLLMConfig.java new file mode 100644 index 00000000..db39818e --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/src/main/java/org/cpq/saa01helloworld/SaaLLMConfig.java @@ -0,0 +1,21 @@ +package org.cpq.saa01helloworld; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SaaLLMConfig { + + @Value("${spring.ai.dashscope.api-key}") + private String apiKey; + + @Bean + public DashScopeApi dashScopeApi() { + return DashScopeApi.builder() + .apiKey(apiKey) + .build(); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/src/main/resources/application.properties new file mode 100644 index 00000000..b49766b8 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-01HelloWorld/src/main/resources/application.properties @@ -0,0 +1,12 @@ +server.port=8001 +spring.application.name=SAA-01HelloWorld + +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.ai.dashscope.api-key=${ALI_AI_KEY} +spring.ai.openai.base-url=https://dashscope.aliyuncs.com/compatible-mode +spring.ai.openai.chat.options.model=qwen-plus + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-02Ollama/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-02Ollama/pom.xml new file mode 100644 index 00000000..7e587a24 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-02Ollama/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + org.cpq + SAA-02Ollama + + + + 21 + 21 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.springframework.ai + spring-ai-starter-model-ollama + 1.0.0 + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-02Ollama/src/main/java/org/cpq/ollama/Saa02OllamaApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-02Ollama/src/main/java/org/cpq/ollama/Saa02OllamaApplication.java new file mode 100644 index 00000000..872a8a35 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-02Ollama/src/main/java/org/cpq/ollama/Saa02OllamaApplication.java @@ -0,0 +1,13 @@ +package org.cpq.ollama; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa02OllamaApplication +{ + public static void main(String[] args) + { + SpringApplication.run(Saa02OllamaApplication.class,args); + } +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-02Ollama/src/main/java/org/cpq/ollama/controller/OllamaController.java b/project/spingai/spring-ai-alibaba-01/SAA-02Ollama/src/main/java/org/cpq/ollama/controller/OllamaController.java new file mode 100644 index 00000000..96361c09 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-02Ollama/src/main/java/org/cpq/ollama/controller/OllamaController.java @@ -0,0 +1,42 @@ +package org.cpq.ollama.controller; + +import jakarta.annotation.Resource; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + + +@RestController +public class OllamaController +{ + + @Resource + @Qualifier("ollamaChatModel") + private ChatModel chatModel; + + @GetMapping(value = "/ollama/dochat") + public String doChat(@RequestParam(name = "msg",defaultValue="你是谁") String msg) + { + String result = chatModel.call(msg); + return result; + } + + @GetMapping(value = "/ollama/streamchat") + public Flux streamchat(@RequestParam(name = "msg",defaultValue="你是谁") String msg) + { + Flux result = chatModel.stream(msg); + return result; + } + +} + + + + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-02Ollama/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-02Ollama/src/main/resources/application.properties new file mode 100644 index 00000000..a72ff13f --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-02Ollama/src/main/resources/application.properties @@ -0,0 +1,12 @@ +server.port=8002 + +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.application.name=SAA-02Ollama + +# ====ollama Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} +spring.ai.ollama.base-url=http://localhost:11434 +spring.ai.ollama.chat.model=deepseek-r1:1.5b diff --git a/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/pom.xml new file mode 100644 index 00000000..2ae2d972 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + org.cpq + SAA-03ChatModelChatClient + + + + 21 + 21 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/src/main/java/com/atguigu/study/Saa03ChatModelChatClientApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/src/main/java/com/atguigu/study/Saa03ChatModelChatClientApplication.java new file mode 100644 index 00000000..99f8820f --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/src/main/java/com/atguigu/study/Saa03ChatModelChatClientApplication.java @@ -0,0 +1,15 @@ +package com.atguigu.study; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa03ChatModelChatClientApplication +{ + + public static void main(String[] args) + { + SpringApplication.run(Saa03ChatModelChatClientApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/src/main/java/com/atguigu/study/config/SaaLLMConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/src/main/java/com/atguigu/study/config/SaaLLMConfig.java new file mode 100644 index 00000000..37346502 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/src/main/java/com/atguigu/study/config/SaaLLMConfig.java @@ -0,0 +1,24 @@ +package com.atguigu.study.config; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +@Component +public class SaaLLMConfig { + + @Bean + public DashScopeApi dashScopeApi() { + return DashScopeApi.builder() + .apiKey("ALI_AI_KEY") + .build(); + } + + @Bean + public ChatClient chatClient(ChatModel chatModel) { + return ChatClient.builder(chatModel).build(); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/src/main/java/com/atguigu/study/controller/ChatClientControllerV2.java b/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/src/main/java/com/atguigu/study/controller/ChatClientControllerV2.java new file mode 100644 index 00000000..25e699e2 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/src/main/java/com/atguigu/study/controller/ChatClientControllerV2.java @@ -0,0 +1,22 @@ +package com.atguigu.study.controller; + +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ChatClientControllerV2 { + + @Resource + private ChatClient client; + + @GetMapping(value = "/chat/client") + public String doChat(@RequestParam(name = "msg",defaultValue="你是谁") String msg) + { + String result = client.prompt().user(msg).call().content(); + return result; + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/src/main/resources/application.properties new file mode 100644 index 00000000..5b373b9c --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-03ChatModelChatClient/src/main/resources/application.properties @@ -0,0 +1,10 @@ +server.port=8003 + +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.application.name=SAA-03ChatModelChatClient + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/pom.xml new file mode 100644 index 00000000..70ae4b59 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + SAA-04StreamingOutput + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/java/com/atguigu/ai/Saa04StreamingOutputApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/java/com/atguigu/ai/Saa04StreamingOutputApplication.java new file mode 100644 index 00000000..f9ded109 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/java/com/atguigu/ai/Saa04StreamingOutputApplication.java @@ -0,0 +1,15 @@ +package com.atguigu.ai; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa04StreamingOutputApplication +{ + + public static void main(String[] args) + { + SpringApplication.run(Saa04StreamingOutputApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/java/com/atguigu/ai/config/SaaLLMConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/java/com/atguigu/ai/config/SaaLLMConfig.java new file mode 100644 index 00000000..04ca6506 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/java/com/atguigu/ai/config/SaaLLMConfig.java @@ -0,0 +1,53 @@ +package com.atguigu.ai.config; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SaaLLMConfig +{ + private final String DEEPSEEK_MODEL = "deepseek-v3"; + private final String QWEN_MODEL = "qwen-max"; + + @Bean("deepseekModel") + public ChatModel deepseekModel() + { + DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(System.getenv("ALI_AI_KEY")).build(); + DashScopeChatOptions options = DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build(); + return DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .defaultOptions(options) + .build(); + } + + @Bean("qwenModel") + public ChatModel qwenModel() + { + DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(System.getenv("ALI_AI_KEY")).build(); + DashScopeChatOptions options = DashScopeChatOptions.builder().withModel(QWEN_MODEL).build(); + return DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .defaultOptions(options) + .build(); + } + + @Bean("deepseekChatClient") + public ChatClient deepseekChatClient(@Qualifier("deepseekModel") ChatModel deepseekModel) { + ChatOptions options = ChatOptions.builder().model(DEEPSEEK_MODEL).build(); + return ChatClient.builder(deepseekModel).defaultOptions(options).build(); + } + + @Bean("qwenChatClient") + public ChatClient qwenChatClient(@Qualifier("qwenModel") ChatModel qwenModel) { + ChatOptions options = ChatOptions.builder().model(QWEN_MODEL).build(); + return ChatClient.builder(qwenModel).defaultOptions(options).build(); + } + +} \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/java/com/atguigu/ai/controller/StreamOutputController.java b/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/java/com/atguigu/ai/controller/StreamOutputController.java new file mode 100644 index 00000000..ef80a4cf --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/java/com/atguigu/ai/controller/StreamOutputController.java @@ -0,0 +1,31 @@ +package com.atguigu.ai.controller; + +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + + +@RestController +public class StreamOutputController +{ + + @Resource(name="deepseekChatClient") + private ChatClient deepseekChatClient; + + @Resource(name="qwenChatClient") + private ChatClient qwenChatClient; + + @GetMapping(value = "/stringFluxDeepseek") + public Flux stringFluxDeepseek(@RequestParam(name = "msg",defaultValue="你是谁") String msg) { + return deepseekChatClient.prompt(msg).stream().content(); + } + + @GetMapping(value = "/stringFluxQwen") + public Flux stringFluxQwen(@RequestParam(name = "msg",defaultValue="你是谁") String msg) { + return qwenChatClient.prompt(msg).stream().content(); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/resources/application.properties new file mode 100644 index 00000000..8561b8dd --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/resources/application.properties @@ -0,0 +1,10 @@ +server.port=8004 + +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.application.name=SAA-04StreamingOutput + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/resources/static/index.html b/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/resources/static/index.html new file mode 100644 index 00000000..101ea0f3 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-04StreamingOutput/src/main/resources/static/index.html @@ -0,0 +1,92 @@ + + + + SSE流式ChatModel+ChatClient+多模型共存 + + + + +
+ +
+ + + \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/pom.xml new file mode 100644 index 00000000..2586f799 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + + SAA-05Prompt + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/src/main/java/com/atguigu/ai/Saa05PromptApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/src/main/java/com/atguigu/ai/Saa05PromptApplication.java new file mode 100644 index 00000000..a03fc51f --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/src/main/java/com/atguigu/ai/Saa05PromptApplication.java @@ -0,0 +1,14 @@ +package com.atguigu.ai; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +public class Saa05PromptApplication +{ + public static void main(String[] args) + { + SpringApplication.run(Saa05PromptApplication.class,args); + } +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/src/main/java/com/atguigu/ai/config/SaaLLMConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/src/main/java/com/atguigu/ai/config/SaaLLMConfig.java new file mode 100644 index 00000000..2f0a1a48 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/src/main/java/com/atguigu/ai/config/SaaLLMConfig.java @@ -0,0 +1,67 @@ +package com.atguigu.ai.config; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SaaLLMConfig +{ + // 模型名称常量定义 + private final String DEEPSEEK_MODEL = "deepseek-v3"; + private final String QWEN_MODEL = "qwen-plus"; + + @Bean(name = "deepseek") + public ChatModel deepSeek() + { + return DashScopeChatModel.builder() + .dashScopeApi(DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build()) + .defaultOptions( + DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build() + ) + .build(); + } + + @Bean(name = "qwen") + public ChatModel qwen() + { + return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build()) + .defaultOptions( + DashScopeChatOptions.builder() + .withModel(QWEN_MODEL) + .build() + ) + .build(); + } + + @Bean(name = "deepseekChatClient") + public ChatClient deepseekChatClient(@Qualifier("deepseek") ChatModel deepSeek) + { + return ChatClient.builder(deepSeek) + .defaultOptions(ChatOptions.builder() + .model(DEEPSEEK_MODEL) + .build()) + .build(); + } + + + @Bean(name = "qwenChatClient") + public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwen) + { + return ChatClient.builder(qwen) + .defaultOptions(ChatOptions.builder() + .model(QWEN_MODEL) + .build()) + .build(); + } +} \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/src/main/java/com/atguigu/ai/controller/PromptController.java b/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/src/main/java/com/atguigu/ai/controller/PromptController.java new file mode 100644 index 00000000..b07bcda1 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/src/main/java/com/atguigu/ai/controller/PromptController.java @@ -0,0 +1,47 @@ +package com.atguigu.ai.controller; + +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + +/** + * 知识出处,https://java2ai.com/docs/1.0.0.2/tutorials/basics/prompt/?spm=5176.29160081.0.0.2856aa5cdeol7a + */ +@RestController +public class PromptController +{ + @Resource(name = "deepseek") + private ChatModel deepseekChatModel; + @Resource(name = "qwen") + private ChatModel qwenChatModel; + + @Resource(name = "deepseekChatClient") + private ChatClient deepseekChatClient; + @Resource(name = "qwenChatClient") + private ChatClient qwenChatClient; + + @GetMapping("/prompt/system1") + public Flux chat(String question) { + return deepseekChatClient.prompt() + .system("你是一个法律助手,只回答法律问题,其它问题回复,我只能回答法律相关问题,其它无可奉告") + .user(question) + .stream() + .content(); + } + + @GetMapping("/prompt/assistant") + public String assistant(String question) { + AssistantMessage assistantMessage = deepseekChatClient.prompt() + .user(question) + .call() + .chatResponse() + .getResult() + .getOutput(); + return assistantMessage.getText(); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/src/main/resources/application.properties new file mode 100644 index 00000000..8267932c --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-05Prompt/src/main/resources/application.properties @@ -0,0 +1,11 @@ +server.port=8005 + +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.application.name=SAA-05Prompt + + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/pom.xml new file mode 100644 index 00000000..770a16e9 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + + SAA-06PromptTemplate + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/src/main/java/com/atguigu/ai/Saa06PromptTemplateApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/src/main/java/com/atguigu/ai/Saa06PromptTemplateApplication.java new file mode 100644 index 00000000..3cb5d25f --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/src/main/java/com/atguigu/ai/Saa06PromptTemplateApplication.java @@ -0,0 +1,15 @@ +package com.atguigu.ai; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa06PromptTemplateApplication +{ + + public static void main(String[] args) + { + SpringApplication.run(Saa06PromptTemplateApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/src/main/java/com/atguigu/ai/config/SaaLLMConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/src/main/java/com/atguigu/ai/config/SaaLLMConfig.java new file mode 100644 index 00000000..5dbe7140 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/src/main/java/com/atguigu/ai/config/SaaLLMConfig.java @@ -0,0 +1,68 @@ +package com.atguigu.ai.config; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class SaaLLMConfig +{ + // 模型名称常量定义 + private final String DEEPSEEK_MODEL = "deepseek-v3"; + private final String QWEN_MODEL = "qwen-plus"; + + @Bean(name = "deepseek") + public ChatModel deepSeek() + { + return DashScopeChatModel.builder() + .dashScopeApi(DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build()) + .defaultOptions( + DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build() + ) + .build(); + } + + @Bean(name = "qwen") + public ChatModel qwen() + { + return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build()) + .defaultOptions( + DashScopeChatOptions.builder() + .withModel(QWEN_MODEL) + .build() + ) + .build(); + } + + @Bean(name = "deepseekChatClient") + public ChatClient deepseekChatClient(@Qualifier("deepseek") ChatModel deepSeek) + { + return ChatClient.builder(deepSeek) + .defaultOptions(ChatOptions.builder() + .model(DEEPSEEK_MODEL) + .build()) + .build(); + } + + + @Bean(name = "qwenChatClient") + public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwen) + { + return ChatClient.builder(qwen) + .defaultOptions(ChatOptions.builder() + .model(QWEN_MODEL) + .build()) + .build(); + } +} \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/src/main/java/com/atguigu/ai/controller/PromptTemplateController.java b/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/src/main/java/com/atguigu/ai/controller/PromptTemplateController.java new file mode 100644 index 00000000..73d0f1a4 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/src/main/java/com/atguigu/ai/controller/PromptTemplateController.java @@ -0,0 +1,67 @@ +package com.atguigu.ai.controller; + +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.prompt.PromptTemplate; +import org.springframework.ai.chat.prompt.SystemPromptTemplate; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + +import java.util.List; +import java.util.Map; + +@RestController +public class PromptTemplateController +{ + @Resource(name = "deepseek") + private ChatModel deepseekChatModel; + @Resource(name = "qwen") + private ChatModel qwenChatModel; + + @Resource(name = "deepseekChatClient") + private ChatClient deepseekChatClient; + @Resource(name = "qwenChatClient") + private ChatClient qwenChatClient; + + + @GetMapping("/promptTemplate") + public Flux promptTemplate(String topic, String outputFormat, String wordCount) + { + PromptTemplate promptTemplate = new PromptTemplate(""" + 讲一个关于{topic}的故事, + 并以{outputFormat}格式输出, + 字数控制在{wordCount}左右 + """); + Map map = Map.of("topic", topic, + "outputFormat", outputFormat, + "wordCount", wordCount); + Prompt prompt = promptTemplate.create(map); + return deepseekChatClient.prompt(prompt) + .stream() + .content(); + + } + + + @GetMapping("/promptTemplate2") + public Flux promptTemplate(String userTopic, String systemTopic) + { + SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("你是一个{systemTopic}助手,只回答{systemTopic}相关问题,其他问题不回答,以HTML格式输出结果"); + Message systemMessage = systemPromptTemplate.createMessage(Map.of("systemTopic", systemTopic)); + + PromptTemplate userPromptTemplate = new PromptTemplate("请介绍{userTopic}"); + Message userMessage = userPromptTemplate.createMessage(Map.of("userTopic", userTopic)); + + Prompt prompt = new Prompt(List.of(systemMessage, userMessage)); + + return deepseekChatClient.prompt(prompt).stream().content(); + } + + + + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/src/main/resources/application.properties new file mode 100644 index 00000000..e2b9b102 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-06PromptTemplate/src/main/resources/application.properties @@ -0,0 +1,12 @@ +server.port=8006 + +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.application.name=SAA-06PromptTemplate + + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/pom.xml new file mode 100644 index 00000000..b7966cbd --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + + SAA-07StructuredOutput + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + cn.hutool + hutool-all + 5.8.22 + + + + org.projectlombok + lombok + 1.18.38 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/java/com/atguigu/study/Saa07StructuredOutputApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/java/com/atguigu/study/Saa07StructuredOutputApplication.java new file mode 100644 index 00000000..33abf9f9 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/java/com/atguigu/study/Saa07StructuredOutputApplication.java @@ -0,0 +1,15 @@ +package com.atguigu.study; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa07StructuredOutputApplication +{ + + public static void main(String[] args) + { + SpringApplication.run(Saa07StructuredOutputApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/java/com/atguigu/study/config/SaaLLMConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/java/com/atguigu/study/config/SaaLLMConfig.java new file mode 100644 index 00000000..8f6032e1 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/java/com/atguigu/study/config/SaaLLMConfig.java @@ -0,0 +1,67 @@ +package com.atguigu.study.config; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SaaLLMConfig +{ + // 模型名称常量定义 + private final String DEEPSEEK_MODEL = "deepseek-v3"; + private final String QWEN_MODEL = "qwen-plus"; + + @Bean(name = "deepseek") + public ChatModel deepSeek() + { + return DashScopeChatModel.builder() + .dashScopeApi(DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build()) + .defaultOptions( + DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build() + ) + .build(); + } + + @Bean(name = "qwen") + public ChatModel qwen() + { + return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build()) + .defaultOptions( + DashScopeChatOptions.builder() + .withModel(QWEN_MODEL) + .build() + ) + .build(); + } + + @Bean(name = "qwenChatClient") + public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwen) + { + return ChatClient.builder(qwen) + .defaultOptions(ChatOptions.builder() + .model(QWEN_MODEL) + .build()) + .build(); + } + + @Bean(name = "deepseekChatClient") + public ChatClient deepseekChatClient(@Qualifier("deepseek") ChatModel deepSeek) + { + return ChatClient.builder(deepSeek) + .defaultOptions(ChatOptions.builder() + .model(DEEPSEEK_MODEL) + .build()) + .build(); + } + +} \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/java/com/atguigu/study/controller/StructuredOutputController.java b/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/java/com/atguigu/study/controller/StructuredOutputController.java new file mode 100644 index 00000000..fb23088e --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/java/com/atguigu/study/controller/StructuredOutputController.java @@ -0,0 +1,45 @@ +package com.atguigu.study.controller; + +import com.atguigu.study.records.StudentRecord; +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.function.Consumer; + +@RestController +public class StructuredOutputController +{ + @Resource(name = "qwenChatClient") + private ChatClient qwenChatClient; + + @GetMapping("/structuredOutput1") + public StudentRecord structuredOutput1(@RequestParam(name = "sname") String sname, + @RequestParam(name = "email") String email) { + Consumer consumer = new Consumer<>() { + @Override + public void accept(ChatClient.PromptUserSpec promptUserSpec) { + promptUserSpec.text("学生学号是学号1001,我叫{sname},大学专业计算机科学与技术,邮箱{email}") + .param("sname", sname) + .param("email", email); + } + }; + return qwenChatClient.prompt().user(consumer).call().entity(StudentRecord.class); + } + + + @GetMapping("/structuredOutput2") + public StudentRecord structuredOutput2(@RequestParam(name = "sname") String sname, + @RequestParam(name = "email") String email) { + return qwenChatClient.prompt() + .user(spec -> spec.text("学生学号是学号1111,我叫{sname},大学专业是汉语言文学,邮箱{email}") + .param("sname", sname) + .param("email", email) + ) + .call() + .entity(StudentRecord.class); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/java/com/atguigu/study/records/StudentRecord.java b/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/java/com/atguigu/study/records/StudentRecord.java new file mode 100644 index 00000000..b91d9185 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/java/com/atguigu/study/records/StudentRecord.java @@ -0,0 +1,4 @@ +package com.atguigu.study.records; + +public record StudentRecord(String id, String sname, String major, String email) { +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/resources/application.properties new file mode 100644 index 00000000..485831d6 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-07StructuredOutput/src/main/resources/application.properties @@ -0,0 +1,11 @@ +server.port=8007 + +#\u4E2D\u6587\u4E71\u7801\u56DE\u590D +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.application.name=SAA-07StructuredOutput + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/pom.xml new file mode 100644 index 00000000..9e96e846 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + + SAA-08Persistent + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-memory-redis + + + + redis.clients + jedis + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/java/com/atguigu/study/Saa08PersistentApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/java/com/atguigu/study/Saa08PersistentApplication.java new file mode 100644 index 00000000..7e3ac905 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/java/com/atguigu/study/Saa08PersistentApplication.java @@ -0,0 +1,13 @@ +package com.atguigu.study; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa08PersistentApplication +{ + public static void main(String[] args) + { + SpringApplication.run(Saa08PersistentApplication.class,args); + } +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/java/com/atguigu/study/config/RedisMemoryConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/java/com/atguigu/study/config/RedisMemoryConfig.java new file mode 100644 index 00000000..05b299bb --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/java/com/atguigu/study/config/RedisMemoryConfig.java @@ -0,0 +1,27 @@ +package com.atguigu.study.config; + +import com.alibaba.cloud.ai.memory.redis.RedisChatMemoryRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RedisMemoryConfig { + + @Value("${spring.data.redis.host}") + private String host; + @Value("${spring.data.redis.port}") + private int port; + + @Bean + public RedisChatMemoryRepository redisChatMemoryRepository(){ + return RedisChatMemoryRepository.builder() + .host(host) + .port(port) + .build(); + } + + + + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/java/com/atguigu/study/config/SaaLLMConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/java/com/atguigu/study/config/SaaLLMConfig.java new file mode 100644 index 00000000..43203543 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/java/com/atguigu/study/config/SaaLLMConfig.java @@ -0,0 +1,42 @@ +package com.atguigu.study.config; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; +import com.alibaba.cloud.ai.memory.redis.RedisChatMemoryRepository; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; +import org.springframework.ai.chat.memory.MessageWindowChatMemory; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SaaLLMConfig { + + private final String QWEN_MODEL = "qwen-plus"; + + @Bean(name = "qwen") + public ChatModel qwen() { + DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(System.getenv("ALI_AI_KEY")).build(); + DashScopeChatOptions options = DashScopeChatOptions.builder().withModel(QWEN_MODEL).build(); + return DashScopeChatModel.builder().dashScopeApi(dashScopeApi).defaultOptions(options).build(); + } + + @Bean(name = "qwenChatClient") + public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwen, + RedisChatMemoryRepository redisChatMemoryRepository) { + MessageWindowChatMemory windowChatMemory = MessageWindowChatMemory.builder() + .chatMemoryRepository(redisChatMemoryRepository) + .maxMessages(10) + .build(); + ChatOptions options = ChatOptions.builder().model(QWEN_MODEL).build(); + return ChatClient.builder(qwen) + .defaultOptions(options) + .defaultAdvisors(MessageChatMemoryAdvisor.builder(windowChatMemory).build()) + .build(); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/java/com/atguigu/study/controller/ChatMemory4RedisController.java b/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/java/com/atguigu/study/controller/ChatMemory4RedisController.java new file mode 100644 index 00000000..314646d0 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/java/com/atguigu/study/controller/ChatMemory4RedisController.java @@ -0,0 +1,25 @@ +package com.atguigu.study.controller; + +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +public class ChatMemory4RedisController +{ + @Resource(name = "qwenChatClient") + private ChatClient qwenChatClient; + + @GetMapping("/memory/chat") + public String memoryChat(String question, String userId) + { + return qwenChatClient.prompt(question) + .advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, userId)) + .call() + .content(); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/resources/application.properties new file mode 100644 index 00000000..4482561a --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-08Persistent/src/main/resources/application.properties @@ -0,0 +1,21 @@ +server.port=8008 + +# \u8BBE\u7F6E\u54CD\u5E94\u7684\u5B57\u7B26\u7F16\u7801 +server.servlet.encoding.charset=utf-8 +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true + +spring.application.name=SAA-08Persistent + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} + + +# ==========redis config =============== +spring.data.redis.host=127.0.0.1 +spring.data.redis.port=6379 +spring.data.redis.database=0 +spring.data.redis.connect-timeout=3 +spring.data.redis.timeout=2 + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-09Text2image/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-09Text2image/pom.xml new file mode 100644 index 00000000..a1d9562b --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-09Text2image/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + + SAA-09Text2image + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-09Text2image/src/main/java/com/atguigu/study/Saa09Text2imageApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-09Text2image/src/main/java/com/atguigu/study/Saa09Text2imageApplication.java new file mode 100644 index 00000000..cf045d05 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-09Text2image/src/main/java/com/atguigu/study/Saa09Text2imageApplication.java @@ -0,0 +1,15 @@ +package com.atguigu.study; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa09Text2imageApplication +{ + + public static void main(String[] args) + { + SpringApplication.run(Saa09Text2imageApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-09Text2image/src/main/java/com/atguigu/study/controller/Text2ImageController.java b/project/spingai/spring-ai-alibaba-01/SAA-09Text2image/src/main/java/com/atguigu/study/controller/Text2ImageController.java new file mode 100644 index 00000000..57456c2d --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-09Text2image/src/main/java/com/atguigu/study/controller/Text2ImageController.java @@ -0,0 +1,30 @@ +package com.atguigu.study.controller; + +import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions; +import jakarta.annotation.Resource; +import org.springframework.ai.image.ImageModel; +import org.springframework.ai.image.ImagePrompt; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +public class Text2ImageController { + public static final String IMAGE_MODEL = "wanx2.1-t2i-turbo"; + + @Resource + private ImageModel imageModel; + + @GetMapping("/text2image") + public String text2image(@RequestParam(name = "prompt", defaultValue = "刺猬")String prompt) { + DashScopeImageOptions options = DashScopeImageOptions.builder() + .withModel(IMAGE_MODEL) + .build(); + ImagePrompt imagePrompt = new ImagePrompt(prompt, options); + return imageModel.call(imagePrompt).getResult().getOutput().getUrl(); + + } + + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-09Text2image/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-09Text2image/src/main/resources/application.properties new file mode 100644 index 00000000..34a95b15 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-09Text2image/src/main/resources/application.properties @@ -0,0 +1,17 @@ +server.port=8009 + +# \u8BBE\u7F6E\u54CD\u5E94\u7684\u5B57\u7B26\u7F16\u7801 +server.servlet.encoding.charset=utf-8 +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true + +spring.application.name=SAA-09Text2image + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} + +spring.ai.dashscope.image.retry.max-attempts=5 +spring.ai.dashscope.image.retry.backoff.initial-interval=2000 +spring.ai.dashscope.image.retry.backoff.multiplier=2.0 + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-10Text2voice/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-10Text2voice/pom.xml new file mode 100644 index 00000000..40e615a0 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-10Text2voice/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + + SAA-10Text2voice + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-10Text2voice/src/main/java/com/atguigu/study/Saa10Text2voiceApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-10Text2voice/src/main/java/com/atguigu/study/Saa10Text2voiceApplication.java new file mode 100644 index 00000000..bac77196 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-10Text2voice/src/main/java/com/atguigu/study/Saa10Text2voiceApplication.java @@ -0,0 +1,15 @@ +package com.atguigu.study; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa10Text2voiceApplication +{ + + public static void main(String[] args) + { + SpringApplication.run(Saa10Text2voiceApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-10Text2voice/src/main/java/com/atguigu/study/controller/Text2VoiceController.java b/project/spingai/spring-ai-alibaba-01/SAA-10Text2voice/src/main/java/com/atguigu/study/controller/Text2VoiceController.java new file mode 100644 index 00000000..b3b9d68a --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-10Text2voice/src/main/java/com/atguigu/study/controller/Text2VoiceController.java @@ -0,0 +1,43 @@ +package com.atguigu.study.controller; + +import com.alibaba.cloud.ai.dashscope.audio.DashScopeSpeechSynthesisOptions; +import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisModel; +import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisPrompt; +import com.alibaba.cloud.ai.dashscope.audio.synthesis.SpeechSynthesisResponse; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.io.FileOutputStream; +import java.nio.ByteBuffer; +import java.util.UUID; + +@Slf4j +@RestController +public class Text2VoiceController { + + @Resource + private SpeechSynthesisModel speechsynthesisModel; + + /** + * http://localhost:8010/t2v/voice + */ + @GetMapping("/t2v/voice") + public String voice(@RequestParam(name = "msg", defaultValue = "温馨提示,支付宝到账100万元,请注意查收")String msg) { + String filePath = "D:\\" + UUID.randomUUID() + ".mp3"; + DashScopeSpeechSynthesisOptions options = DashScopeSpeechSynthesisOptions.builder() + .model("cosyvoice-v2") + .voice("longyingcui") + .build(); + SpeechSynthesisResponse response = speechsynthesisModel.call(new SpeechSynthesisPrompt(msg, options)); + ByteBuffer byteBuffer = response.getResult().getOutput().getAudio(); + try (FileOutputStream fileOutputStream = new FileOutputStream(filePath)) { + fileOutputStream.write(byteBuffer.array()); + } catch (Exception ex) { + log.error("", ex); + } + return filePath; + } +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-10Text2voice/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-10Text2voice/src/main/resources/application.properties new file mode 100644 index 00000000..49816ca1 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-10Text2voice/src/main/resources/application.properties @@ -0,0 +1,11 @@ +server.port=8010 + +# \u8BBE\u7F6E\u54CD\u5E94\u7684\u5B57\u7B26\u7F16\u7801 +server.servlet.encoding.charset=utf-8 +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true + +spring.application.name=SAA-10Text2voice + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-11Embed2vector/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-11Embed2vector/pom.xml new file mode 100644 index 00000000..47740bc2 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-11Embed2vector/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + SAA-11Embed2vector + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.springframework.ai + spring-ai-starter-vector-store-redis + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-11Embed2vector/src/main/java/com/atguigu/study/Saa11Embed2vectorApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-11Embed2vector/src/main/java/com/atguigu/study/Saa11Embed2vectorApplication.java new file mode 100644 index 00000000..43822d4d --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-11Embed2vector/src/main/java/com/atguigu/study/Saa11Embed2vectorApplication.java @@ -0,0 +1,15 @@ +package com.atguigu.study; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa11Embed2vectorApplication +{ + + public static void main(String[] args) + { + SpringApplication.run(Saa11Embed2vectorApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-11Embed2vector/src/main/java/com/atguigu/study/controller/Embed2VectorController.java b/project/spingai/spring-ai-alibaba-01/SAA-11Embed2vector/src/main/java/com/atguigu/study/controller/Embed2VectorController.java new file mode 100644 index 00000000..f9c1e5d5 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-11Embed2vector/src/main/java/com/atguigu/study/controller/Embed2VectorController.java @@ -0,0 +1,54 @@ +package com.atguigu.study.controller; + +import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.document.Document; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.EmbeddingRequest; +import org.springframework.ai.embedding.EmbeddingResponse; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + + +@RestController +@Slf4j +public class Embed2VectorController { + + @Resource + private EmbeddingModel embeddingModel; + @Resource + private VectorStore vectorStore; + + @GetMapping("/text2embed") + public EmbeddingResponse text2embed(String text) { + EmbeddingRequest embeddingRequest = new EmbeddingRequest(List.of(text), DashScopeEmbeddingOptions.builder().withModel("text-embedding-v3").build()); + EmbeddingResponse response = embeddingModel.call(embeddingRequest); + log.info("########{}", response.getResult().getOutput()); + return response; + } + + @GetMapping("/embed2vector/add") + public void add() { + List documents = List.of( + new Document("与LLM有关"), + new Document("小说内容"), + new Document("i love java"), + new Document("春风若有怜花意") + ); + vectorStore.add(documents); + } + + @GetMapping("/embed2vector/query") + public List query(String text) { + SearchRequest request = SearchRequest.builder().query(text).topK(3).build(); + List documents = vectorStore.similaritySearch(request); + return documents; + } + + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-11Embed2vector/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-11Embed2vector/src/main/resources/application.properties new file mode 100644 index 00000000..735f68e8 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-11Embed2vector/src/main/resources/application.properties @@ -0,0 +1,27 @@ +server.port=8011 + +# \u8BBE\u7F6E\u54CD\u5E94\u7684\u5B57\u7B26\u7F16\u7801 +server.servlet.encoding.charset=utf-8 +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true + +spring.application.name=SAA-11Embed2vector + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} +spring.ai.dashscope.chat.options.model=qwen-plus +spring.ai.dashscope.embedding.options.model=text-embedding-v3 +# ====SpringAIAlibaba Config============= +spring.data.redis.host=192.168.1.221 +spring.data.redis.port=6379 + +spring.ai.vectorstore.redis.initialize-schema=true +spring.ai.vectorstore.redis.index-name=custom-index +spring.ai.vectorstore.redis.prefix=custom-prefix + + + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/pom.xml new file mode 100644 index 00000000..30b7be34 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + + SAA-12RAG4AiOps + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.springframework.ai + spring-ai-starter-vector-store-redis + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/Saa12Rag4AiOpsApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/Saa12Rag4AiOpsApplication.java new file mode 100644 index 00000000..d9ee0235 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/Saa12Rag4AiOpsApplication.java @@ -0,0 +1,16 @@ +package com.atguigu.study; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa12Rag4AiOpsApplication +{ + + public static void main(String[] args) + { + + SpringApplication.run(Saa12Rag4AiOpsApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/config/InitVectorDatabaseConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/config/InitVectorDatabaseConfig.java new file mode 100644 index 00000000..ec388390 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/config/InitVectorDatabaseConfig.java @@ -0,0 +1,52 @@ +package com.atguigu.study.config; + +import cn.hutool.crypto.SecureUtil; +import jakarta.annotation.PostConstruct; +import org.springframework.ai.document.Document; +import org.springframework.ai.reader.TextReader; +import org.springframework.ai.transformer.splitter.TokenTextSplitter; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.data.redis.core.RedisTemplate; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.List; + +@Configuration +public class InitVectorDatabaseConfig { + + @Autowired + private VectorStore vectorStore; + @Autowired + private RedisTemplate redisTemplate; + @Value("classpath:ops.txt") + private Resource opsTxt; + + @PostConstruct + public void init() { + //1 读取文件 + TextReader textReader = new TextReader(opsTxt); + textReader.setCharset(Charset.defaultCharset()); + + //2 文件转换为向量(开启分词) + List list = new TokenTextSplitter().transform(textReader.read()); + String fileMd5 = ""; + try { + fileMd5 = SecureUtil.md5(opsTxt.getFile()); + } catch (IOException e) { + throw new RuntimeException(e); + } + String redisKey = "vector-opstxt:" + fileMd5; + // 判断是否存入过,redisKey如果可以成功插入表示以前没有过,可以假如向量数据 + Boolean retFlag = redisTemplate.opsForValue().setIfAbsent(redisKey, "1"); + if (Boolean.TRUE.equals(retFlag)) { + // 写入向量数据库RedisStack + vectorStore.add(list); + } + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/config/RedisConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/config/RedisConfig.java new file mode 100644 index 00000000..db9fb2aa --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/config/RedisConfig.java @@ -0,0 +1,49 @@ +package com.atguigu.study.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@Slf4j +public class RedisConfig +{ + /** + * RedisTemplate配置 + * redis序列化的工具配置类,下面这个请一定开启配置 + * 127.0.0.1:6379> keys * + * 1) "ord:102" 序列化过 + * 2) "\xac\xed\x00\x05t\x00\aord:102" 野生,没有序列化过 + * this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法 + * this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法 + * this.redisTemplate.opsForSet(); //提供了操作set的所有方法 + * this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法 + * this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法 + * @param redisConnectionFactor + * @return + */ + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactor) + { + RedisTemplate redisTemplate = new RedisTemplate<>(); + + redisTemplate.setConnectionFactory(redisConnectionFactor); + //设置key序列化方式string + redisTemplate.setKeySerializer(new StringRedisSerializer()); + //设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化 + redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); + + redisTemplate.afterPropertiesSet(); + + return redisTemplate; + } +} + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/config/SaaLLMConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/config/SaaLLMConfig.java new file mode 100644 index 00000000..27b7f378 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/config/SaaLLMConfig.java @@ -0,0 +1,67 @@ +package com.atguigu.study.config; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SaaLLMConfig { + + private final String DEEPSEEK_MODEL = "deepseek-v3"; + private final String QWEN_MODEL = "qwen-plus"; + + + @Bean(name = "deepseek") + public ChatModel deepSeek() + { + return DashScopeChatModel.builder() + .dashScopeApi(DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build()) + .defaultOptions( + DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build() + ) + .build(); + } + + @Bean(name = "qwen") + public ChatModel qwen() + { + return DashScopeChatModel.builder().dashScopeApi(DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build()) + .defaultOptions( + DashScopeChatOptions.builder() + .withModel(QWEN_MODEL) + .build() + ) + .build(); + } + + @Bean(name = "deepseekChatClient") + public ChatClient deepseekChatClient(@Qualifier("deepseek") ChatModel deepSeek) + { + return ChatClient.builder(deepSeek) + .defaultOptions(ChatOptions.builder() + .model(DEEPSEEK_MODEL) + .build()) + .build(); + } + + + @Bean(name = "qwenChatClient") + public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwen) + { + return ChatClient.builder(qwen) + .defaultOptions(ChatOptions.builder() + .model(QWEN_MODEL) + .build()) + .build(); + } +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/controller/RagController.java b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/controller/RagController.java new file mode 100644 index 00000000..9f8eef41 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/java/com/atguigu/study/controller/RagController.java @@ -0,0 +1,43 @@ +package com.atguigu.study.controller; + +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor; +import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + +@RestController +public class RagController +{ + + @Resource(name = "qwenChatClient") + private ChatClient chatClient; + @Resource + private VectorStore vectorStore; + + /** + * http://localhost:8012/rag1?msg=00000 + * http://localhost:8012/rag1?msg=C2222 + */ + @GetMapping("/rag1") + public Flux rag1(String msg) { + String systemInfo = """ + 你是一个运维工程师,按照给出的编码给出对应故障解释,否则回复找不到信息。 + """; + VectorStoreDocumentRetriever documentRetriever = VectorStoreDocumentRetriever.builder().vectorStore(vectorStore).build(); + RetrievalAugmentationAdvisor advisor = RetrievalAugmentationAdvisor.builder() + .documentRetriever(documentRetriever) + .build(); + return chatClient + .prompt() + .system(systemInfo) + .user(msg) + .advisors(advisor) + .stream() + .content(); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/resources/application.properties new file mode 100644 index 00000000..800f1e7b --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/resources/application.properties @@ -0,0 +1,21 @@ +server.port=8012 + +# \u8BBE\u7F6E\u5168\u5C40\u7F16\u7801\u683C\u5F0F +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.application.name=SAA-12RAG4AiDatabase + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} +spring.ai.dashscope.chat.options.model=deepseek-r1 +spring.ai.dashscope.embedding.options.model=text-embedding-v3 + + +# =======Redis Stack========== +spring.data.redis.host=192.168.1.221 +spring.data.redis.port=6379 +spring.ai.vectorstore.redis.initialize-schema=true +spring.ai.vectorstore.redis.index-name=custom-index +spring.ai.vectorstore.redis.prefix=custom-prefix diff --git a/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/resources/ops.txt b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/resources/ops.txt new file mode 100644 index 00000000..dd2e6259 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-12RAG4AiOps/src/main/resources/ops.txt @@ -0,0 +1,5 @@ +00000 系统OK正确执行后的返回 +A0001 用户端错误一级宏观错误码 +A0100 用户注册错误二级宏观错误码 +B1111 支付接口超时 +C2222 Kafka消息解压严重 \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/pom.xml new file mode 100644 index 00000000..5d574154 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + + SAA-13ToolCalling + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/java/com/atguigu/study/Saa13ToolCallingApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/java/com/atguigu/study/Saa13ToolCallingApplication.java new file mode 100644 index 00000000..cf90b3b6 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/java/com/atguigu/study/Saa13ToolCallingApplication.java @@ -0,0 +1,15 @@ +package com.atguigu.study; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa13ToolCallingApplication +{ + + public static void main(String[] args) + { + SpringApplication.run(Saa13ToolCallingApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/java/com/atguigu/study/config/SaaLLMConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/java/com/atguigu/study/config/SaaLLMConfig.java new file mode 100644 index 00000000..10b429d4 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/java/com/atguigu/study/config/SaaLLMConfig.java @@ -0,0 +1,40 @@ +package com.atguigu.study.config; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SaaLLMConfig +{ + @Bean + public ChatClient chatClient(ChatModel chatModel) + { + return ChatClient.builder(chatModel).build(); + } +} + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/java/com/atguigu/study/controller/ToolCallingController.java b/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/java/com/atguigu/study/controller/ToolCallingController.java new file mode 100644 index 00000000..f4ced5fd --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/java/com/atguigu/study/controller/ToolCallingController.java @@ -0,0 +1,36 @@ +package com.atguigu.study.controller; + +import com.atguigu.study.utils.DateTimeTools; +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + +@RestController +public class ToolCallingController +{ + + @Resource + private ChatClient qwenChatClient; + + // http://localhost:8013/not/toolCalling?msg=你是谁,现在几点了 + @GetMapping("/not/toolCalling") + public Flux notToolCalling(String msg) + { + return qwenChatClient.prompt(msg) + .stream() + .content(); + } + + // http://localhost:8013/toolCalling?msg=你是谁,现在几点了 + @GetMapping("/toolCalling") + public Flux toolCalling(String msg) + { + return qwenChatClient.prompt(msg) + .tools(new DateTimeTools()) + .stream() + .content(); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/java/com/atguigu/study/utils/DateTimeTools.java b/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/java/com/atguigu/study/utils/DateTimeTools.java new file mode 100644 index 00000000..d53c9618 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/java/com/atguigu/study/utils/DateTimeTools.java @@ -0,0 +1,18 @@ +package com.atguigu.study.utils; + +import org.springframework.ai.tool.annotation.Tool; + +public class DateTimeTools { + + /** + * 1.定义 function call(tool call) + * 2. returnDirect + * true = tool直接返回不走大模型,直接给客户 + * false = 默认值,拿到tool返回的结果,给大模型,最后由大模型回复 + */ + @Tool(description = "获取当前时间", returnDirect = false) + public String getCurrentTime() { + return "现在时间是:" + java.time.LocalDateTime.now(); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/resources/application.properties new file mode 100644 index 00000000..91c04b73 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-13ToolCalling/src/main/resources/application.properties @@ -0,0 +1,11 @@ +server.port=8013 + +# \u8BBE\u7F6E\u5168\u5C40\u7F16\u7801\u683C\u5F0F +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.application.name=SAA-13ToolCalling + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/pom.xml new file mode 100644 index 00000000..0f434be5 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + + SAA-14LocalMcpServer + + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.ai + spring-ai-starter-mcp-server-webflux + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/src/main/java/com/atguigu/study/Saa14LocalMcpServerApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/src/main/java/com/atguigu/study/Saa14LocalMcpServerApplication.java new file mode 100644 index 00000000..af6a5d83 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/src/main/java/com/atguigu/study/Saa14LocalMcpServerApplication.java @@ -0,0 +1,15 @@ +package com.atguigu.study; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa14LocalMcpServerApplication +{ + + public static void main(String[] args) + { + SpringApplication.run(Saa14LocalMcpServerApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/src/main/java/com/atguigu/study/config/McpServerConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/src/main/java/com/atguigu/study/config/McpServerConfig.java new file mode 100644 index 00000000..41efeb38 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/src/main/java/com/atguigu/study/config/McpServerConfig.java @@ -0,0 +1,22 @@ +package com.atguigu.study.config; + +import com.atguigu.study.service.WeatherService; +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.ai.tool.method.MethodToolCallbackProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class McpServerConfig { + + /** + * 将工具方法暴露给外部 mcp client 调用 + */ + @Bean + public ToolCallbackProvider weatherTools(WeatherService weatherService) { + return MethodToolCallbackProvider.builder() + .toolObjects(weatherService) + .build(); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/src/main/java/com/atguigu/study/service/WeatherService.java b/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/src/main/java/com/atguigu/study/service/WeatherService.java new file mode 100644 index 00000000..d342009f --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/src/main/java/com/atguigu/study/service/WeatherService.java @@ -0,0 +1,22 @@ +package com.atguigu.study.service; + +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Service +public class WeatherService { + + @Tool(description = "根据城市名称获取天气预报") + public String getWeatherForecast(String city) { + Map map = Map.of( + "北京", "天气晴转多云,温度 18℃", + "上海", "天气阴转雷阵雨,温度 17℃", + "广州", "天气多云转阴,温度 19℃", + "深圳", "天气雷阵雨转小雨,温度 16℃" + ); + return map.getOrDefault(city, "没有找到该城市的天气信息"); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/src/main/resources/application.properties new file mode 100644 index 00000000..9c725be1 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-14LocalMcpServer/src/main/resources/application.properties @@ -0,0 +1,14 @@ +server.port=8014 + +# \u8BBE\u7F6E\u5168\u5C40\u7F16\u7801\u683C\u5F0F +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.application.name=SAA-14LocalMcpServer + + +# ====mcp-server Config============= +spring.ai.mcp.server.type=async +spring.ai.mcp.server.name=local-mcp-1 +spring.ai.mcp.server.version=1.0.0 \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/pom.xml new file mode 100644 index 00000000..8b983f27 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + + SAA-15LocalMcpClient + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.springframework.ai + spring-ai-starter-mcp-client + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/src/main/java/com/atguigu/study/Saa15LocalMcpClientApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/src/main/java/com/atguigu/study/Saa15LocalMcpClientApplication.java new file mode 100644 index 00000000..1f253655 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/src/main/java/com/atguigu/study/Saa15LocalMcpClientApplication.java @@ -0,0 +1,15 @@ +package com.atguigu.study; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa15LocalMcpClientApplication +{ + + public static void main(String[] args) + { + SpringApplication.run(Saa15LocalMcpClientApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/src/main/java/com/atguigu/study/config/SaaLLMConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/src/main/java/com/atguigu/study/config/SaaLLMConfig.java new file mode 100644 index 00000000..f97784c5 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/src/main/java/com/atguigu/study/config/SaaLLMConfig.java @@ -0,0 +1,17 @@ +package com.atguigu.study.config; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SaaLLMConfig { + + @Bean + public ChatClient chatClient(ChatModel chatModel, ToolCallbackProvider tools) { + return ChatClient.builder(chatModel).defaultToolCallbacks(tools.getToolCallbacks()).build(); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/src/main/java/com/atguigu/study/controller/McpClientController.java b/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/src/main/java/com/atguigu/study/controller/McpClientController.java new file mode 100644 index 00000000..37c10c3c --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/src/main/java/com/atguigu/study/controller/McpClientController.java @@ -0,0 +1,22 @@ +package com.atguigu.study.controller; + +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + +@RestController +public class McpClientController { + + @Resource + private ChatClient chatClient; + + // http://localhost:8015/mcpClient/chat?msg=上海 + @GetMapping("/mcpClient/chat") + public Flux chat(@RequestParam(name = "msg") String msg) { + return chatClient.prompt(msg).stream().content(); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/src/main/resources/application.properties new file mode 100644 index 00000000..5f0f91ce --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-15LocalMcpClient/src/main/resources/application.properties @@ -0,0 +1,18 @@ +server.port=8015 + +# \u8BBE\u7F6E\u5168\u5C40\u7F16\u7801\u683C\u5F0F +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.application.name=SAA-15LocalMcpClient + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} + +# ====mcp-client Config============= +spring.ai.mcp.client.type=async +spring.ai.mcp.client.request-timeout=60s +spring.ai.mcp.client.toolcallback.enabled=true +spring.ai.mcp.client.sse.connections.mcp-server1.url=http://localhost:8014 + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/pom.xml new file mode 100644 index 00000000..081ab7ea --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + SAA-16ClientCallBaiduMcpServer + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.springframework.ai + spring-ai-starter-mcp-client + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/java/com/atguigu/study/Saa16ClientCallBaiduMcpServerApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/java/com/atguigu/study/Saa16ClientCallBaiduMcpServerApplication.java new file mode 100644 index 00000000..525fece3 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/java/com/atguigu/study/Saa16ClientCallBaiduMcpServerApplication.java @@ -0,0 +1,15 @@ +package com.atguigu.study; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa16ClientCallBaiduMcpServerApplication +{ + + public static void main(String[] args) + { + SpringApplication.run(Saa16ClientCallBaiduMcpServerApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/java/com/atguigu/study/config/SaaLLMConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/java/com/atguigu/study/config/SaaLLMConfig.java new file mode 100644 index 00000000..d102c9e5 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/java/com/atguigu/study/config/SaaLLMConfig.java @@ -0,0 +1,20 @@ +package com.atguigu.study.config; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SaaLLMConfig +{ + @Bean + public ChatClient chatClient(ChatModel chatModel, ToolCallbackProvider tools) + { + return ChatClient.builder(chatModel) + //mcp协议,配置见yml文件,此处只赋能给ChatClient对象 + .defaultToolCallbacks(tools.getToolCallbacks()) + .build(); + } +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/java/com/atguigu/study/controller/McpClientCallBaiDuMcpController.java b/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/java/com/atguigu/study/controller/McpClientCallBaiDuMcpController.java new file mode 100644 index 00000000..ddfd7321 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/java/com/atguigu/study/controller/McpClientCallBaiDuMcpController.java @@ -0,0 +1,31 @@ +package com.atguigu.study.controller; + +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + + +@RestController +public class McpClientCallBaiDuMcpController +{ + + @Resource + private ChatClient chatClient; //添加了MCP调用能力 + + /** + * 添加了MCP调用能力 + * http://localhost:8016/mcp/chat?msg=查询北纬39.9042东经116.4074天气 + * http://localhost:8016/mcp/chat?msg=查询61.149.121.66归属地 + * http://localhost:8016/mcp/chat?msg=查询昌平到天安门路线规划 + * @param msg + * @return + */ + @GetMapping("/mcp/chat") + public Flux mapChat(String msg) { + return chatClient.prompt(msg).stream().content(); + } + +} + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/resources/application.properties new file mode 100644 index 00000000..d4266ff0 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/resources/application.properties @@ -0,0 +1,16 @@ +server.port=8016 + +# \u8BBE\u7F6E\u5168\u5C40\u7F16\u7801\u683C\u5F0F +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.application.name=SAA-16ClientCallBaiduMcpServer + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} + +# ====mcp-client Config============= +spring.ai.mcp.client.request-timeout=60s +spring.ai.mcp.client.toolcallback.enabled=true +spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-server.json5 \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/resources/mcp-server.json5 b/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/resources/mcp-server.json5 new file mode 100644 index 00000000..442837aa --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-16ClientCallBaiduMcpServer/src/main/resources/mcp-server.json5 @@ -0,0 +1,24 @@ +{ + "mcpServers": + { + "baidu-map": + { + + "command": "cmd", + "args": ["/c", "npx", "-y", "@baidumap/mcp-server-baidu-map"], + "env": {"BAIDU_MAP_API_KEY": "xx"} + } + } +} + +// mcp-server-baidu-map地址 https://mcp.so/zh/server/baidu-map/baidu-maps + +// 构建McpTransport协议 + +//cmd:启动 Windows 命令行解释器。 +///c:告诉 cmd 执行完后面的命令后关闭自身。 +//npx:npx = npm execute package,Node.js 的一个工具,用于执行 npm 包中的可执行文件。 +//-y 或 --yes:自动确认操作(类似于默认接受所有提示)。 +//@baidumap/mcp-server-baidu-map:要通过 npx 执行的 npm 包名 +//BAIDU_MAP_API_KEY 是访问百度地图开放平台API的AK + diff --git a/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/pom.xml new file mode 100644 index 00000000..0fcc394c --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + + SAA-17BailianRAG + + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/src/main/java/com/atguigu/study/Saa17BailianRagApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/src/main/java/com/atguigu/study/Saa17BailianRagApplication.java new file mode 100644 index 00000000..113a1361 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/src/main/java/com/atguigu/study/Saa17BailianRagApplication.java @@ -0,0 +1,15 @@ +package com.atguigu.study; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa17BailianRagApplication +{ + + public static void main(String[] args) + { + SpringApplication.run(Saa17BailianRagApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/src/main/java/com/atguigu/study/config/DashScopeConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/src/main/java/com/atguigu/study/config/DashScopeConfig.java new file mode 100644 index 00000000..e2066f85 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/src/main/java/com/atguigu/study/config/DashScopeConfig.java @@ -0,0 +1,32 @@ +package com.atguigu.study.config; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class DashScopeConfig { + + @Value("${spring.ai.dashscope.api-key}") + private String apiKey; + + @Bean(name = "dashScopeAPi") + public DashScopeApi dashScopeAPi() { + // workSpaceId是阿里云百炼平台的业务空间ID + // https://bailian.console.aliyun.com/?tab=app#/knowledge-base/detail/6aovdv07rm + return DashScopeApi.builder() + .apiKey(apiKey) + .workSpaceId("llm-p01mpsp4k19pjldd") + .build(); + } + + + @Bean(name = "chatClient") + public ChatClient chatClient(ChatModel chatModel) { + return ChatClient.builder(chatModel).build(); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/src/main/java/com/atguigu/study/controller/BailianRagController.java b/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/src/main/java/com/atguigu/study/controller/BailianRagController.java new file mode 100644 index 00000000..8b168793 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/src/main/java/com/atguigu/study/controller/BailianRagController.java @@ -0,0 +1,41 @@ +package com.atguigu.study.controller; + +import com.alibaba.cloud.ai.advisor.DocumentRetrievalAdvisor; +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.rag.DashScopeDocumentRetriever; +import com.alibaba.cloud.ai.dashscope.rag.DashScopeDocumentRetrieverOptions; +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + + +@RestController +public class BailianRagController +{ + + @Resource + private ChatClient chatClient; + @Resource + private DashScopeApi dashScopeApi; + + /** + * http://localhost:8017/bailian/rag/chat?msg=A0001 + */ + @GetMapping("/bailian/rag/chat") + public Flux chat(@RequestParam(name = "msg", defaultValue = "00000错误信息")String msg) { + // indexName在阿里云百炼平台 -》知识库的名称 + // https://bailian.console.aliyun.com/?tab=app#/knowledge-base/detail/6aovdv07rm + DashScopeDocumentRetrieverOptions options = DashScopeDocumentRetrieverOptions.builder().withIndexName("智能运维").build(); + DashScopeDocumentRetriever retriever = new DashScopeDocumentRetriever(dashScopeApi, options); + return chatClient.prompt() + .user(msg) + .advisors(new DocumentRetrievalAdvisor(retriever)) + .stream() + .content(); + } + + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/src/main/resources/application.properties new file mode 100644 index 00000000..b39fd00a --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-17BailianRAG/src/main/resources/application.properties @@ -0,0 +1,12 @@ +server.port=8017 + +# \u8BBE\u7F6E\u5168\u5C40\u7F16\u7801\u683C\u5F0F +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.application.name=SAA-17BailianRAG + + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/pom.xml b/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/pom.xml new file mode 100644 index 00000000..6238fa4b --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + + + org.cpq + + SAA-18TodayMenu + + + + org.springframework.boot + spring-boot-starter-web + + + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + org.projectlombok + lombok + 1.18.38 + + + + cn.hutool + hutool-all + 5.8.22 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -parameters + + 21 + 21 + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/src/main/java/com/atguigu/study/Saa18TodayMenuApplication.java b/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/src/main/java/com/atguigu/study/Saa18TodayMenuApplication.java new file mode 100644 index 00000000..9c72dc9b --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/src/main/java/com/atguigu/study/Saa18TodayMenuApplication.java @@ -0,0 +1,15 @@ +package com.atguigu.study; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Saa18TodayMenuApplication +{ + + public static void main(String[] args) + { + SpringApplication.run(Saa18TodayMenuApplication.class, args); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/src/main/java/com/atguigu/study/config/DashScopeConfig.java b/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/src/main/java/com/atguigu/study/config/DashScopeConfig.java new file mode 100644 index 00000000..fbb8c652 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/src/main/java/com/atguigu/study/config/DashScopeConfig.java @@ -0,0 +1,40 @@ +package com.atguigu.study.config; + +import com.alibaba.cloud.ai.dashscope.agent.DashScopeAgent; +import com.alibaba.cloud.ai.dashscope.api.DashScopeAgentApi; +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class DashScopeConfig +{ + + @Value("${spring.ai.dashscope.api-key}") + private String apiKey; + + @Bean + public DashScopeApi dashScopeApi() + { + return DashScopeApi.builder() + .apiKey(apiKey) + .workSpaceId("llm-p01mpsp4k19pjldd") + .build(); + } + + @Bean + public ChatClient chatClient(ChatModel chatModel) + { + return ChatClient.builder(chatModel).build(); + } + + @Bean + public DashScopeAgent dashScopeAgent(DashScopeAgentApi dashScopeAgentApi) { + return new DashScopeAgent(dashScopeAgentApi); + } + + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/src/main/java/com/atguigu/study/controller/MenuCallAgentController.java b/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/src/main/java/com/atguigu/study/controller/MenuCallAgentController.java new file mode 100644 index 00000000..2a516497 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/src/main/java/com/atguigu/study/controller/MenuCallAgentController.java @@ -0,0 +1,32 @@ +package com.atguigu.study.controller; + +import com.alibaba.cloud.ai.dashscope.agent.DashScopeAgent; +import com.alibaba.cloud.ai.dashscope.agent.DashScopeAgentOptions; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + + +@RestController +public class MenuCallAgentController +{ + + @Value("${spring.ai.dashscope.agent.options.app-id}") + private String appId; + + @Autowired + private DashScopeAgent dashScopeAgent; + + @GetMapping(value = "/eatAgent", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public Flux eatAgent(@RequestParam(name = "msg", defaultValue = "今天吃什么") String msg) { + DashScopeAgentOptions options = DashScopeAgentOptions.builder().withAppId(appId).build(); + Prompt prompt = new Prompt(msg, options); + return dashScopeAgent.stream(prompt).map(response -> response.getResult().getOutput().getText()); + } + +} diff --git a/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/src/main/resources/application.properties b/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/src/main/resources/application.properties new file mode 100644 index 00000000..ff00d42a --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/SAA-18TodayMenu/src/main/resources/application.properties @@ -0,0 +1,15 @@ +server.port=8018 + +# \u8BBE\u7F6E\u5168\u5C40\u7F16\u7801\u683C\u5F0F +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + +spring.application.name=SAA-18TodayMenu + + +# ====SpringAIAlibaba Config============= +spring.ai.dashscope.api-key=${ALI_AI_KEY} + +# \u963F\u91CC\u4E91\u767E\u70BC\u5E73\u53F0\u5E94\u7528 -> \u5E94\u7528\u7BA1\u7406 -\u300B\u5E94\u7528id +spring.ai.dashscope.agent.options.app-id=6bad56de04904e768db17ae68cb078c5 \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-01/pom.xml b/project/spingai/spring-ai-alibaba-01/pom.xml new file mode 100644 index 00000000..b562b5bb --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/pom.xml @@ -0,0 +1,98 @@ + + 4.0.0 + + org.cpq + spring-ai-alibaba-01 + 1.0-SNAPSHOT + pom + + + UTF-8 + UTF-8 + 21 + 21 + 21 + + 3.5.5 + + 1.0.0 + + 1.0.0.2 + + + + SAA-01HelloWorld + SAA-02Ollama + SAA-03ChatModelChatClient + SAA-04StreamingOutput + SAA-05Prompt + SAA-06PromptTemplate + SAA-07StructuredOutput + SAA-08Persistent + SAA-09Text2image + SAA-10Text2voice + SAA-11Embed2vector + SAA-12RAG4AiOps + SAA-13ToolCalling + SAA-14LocalMcpServer + SAA-15LocalMcpClient + SAA-16ClientCallBaiduMcpServer + SAA-17BailianRAG + SAA-18TodayMenu + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + com.alibaba.cloud.ai + spring-ai-alibaba-bom + ${SpringAIAlibaba.version} + pom + import + + + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + diff --git a/project/spingai/spring-ai-alibaba-01/readme.md b/project/spingai/spring-ai-alibaba-01/readme.md new file mode 100644 index 00000000..2a5fd50c --- /dev/null +++ b/project/spingai/spring-ai-alibaba-01/readme.md @@ -0,0 +1,4 @@ +阿里云百炼平台: + +https://bailian.console.aliyun.com/?tab=model#/api-key + diff --git a/project/spingai/spring-ai-alibaba-examples/a01-chatbot/README.md b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/README.md new file mode 100644 index 00000000..e80d8785 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/README.md @@ -0,0 +1,39 @@ +# ReAct Agent Example + +This example showcases basic ReactAgent usage in Spring AI Alibaba. + +## Quick Start + +### Prerequisites + +* Requires JDK 17+. +* Choose your LLM provider and get the API-KEY. + +```shell +export AI_DASHSCOPE_API_KEY=your-api-key +``` + +### Run the ChatBot + +1. Download the code. + +```shell +git clone https://github.com/alibaba/spring-ai-alibaba.git +cd examples/chatbot +``` + +2. Start the ChatBot. + +```shell +mvn spring-boot:run +``` + +3. Chat with ChatBot. +Open the browser and visit [http://localhost:8080/chatui/index.html](http://localhost:8080/chatui/index.html) to chat with the ChatBot. + +

+ chatbot-ui +

+ +## More Examples +Check [spring-ai-alibaba-examples](https://github.com/spring-ai-alibaba/examples/tree/main/spring-ai-alibaba-agent-example) for more sophisticated examples. diff --git a/project/spingai/spring-ai-alibaba-examples/a01-chatbot/pom.xml b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/pom.xml new file mode 100644 index 00000000..11c18f9f --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/pom.xml @@ -0,0 +1,140 @@ + + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.7 + + + + com.alibaba.cloud.ai + a01-chatbot + 0.0.1-SNAPSHOT + Examples::ChatBot + ChatBot example using Spring AI Alibaba + + + 17 + 1.1.0 + 1.1.0.0-RC1 + 1.1.0.0-RC1 + + 24.2.1 + + + + + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + + + com.alibaba.cloud.ai + spring-ai-alibaba-extensions-bom + ${spring-ai-alibaba-extensions.version} + pom + import + + + com.alibaba.cloud.ai + spring-ai-alibaba-bom + ${spring-ai-alibaba.version} + pom + import + + + + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + com.alibaba.cloud.ai + spring-ai-alibaba-agent-framework + + + com.alibaba.cloud.ai + spring-ai-alibaba-studio + + + org.springframework.boot + spring-boot-starter + + + + + org.graalvm.polyglot + polyglot + ${graalvm.polyglot.version} + true + + + org.graalvm.polyglot + python-community + ${graalvm.polyglot.version} + pom + true + + + + org.projectlombok + lombok + 1.18.38 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + + false + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + false + + + true + always + + sonatype-snapshots + Sonatype Snapshot Repository + https://oss.sonatype.org/content/repositories/snapshots + + + + diff --git a/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/AgentStaticLoader.java b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/AgentStaticLoader.java new file mode 100644 index 00000000..efbd1054 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/AgentStaticLoader.java @@ -0,0 +1,73 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.cloud.ai.examples.chatbot; + +import com.alibaba.cloud.ai.agent.studio.loader.AgentLoader; +import com.alibaba.cloud.ai.graph.GraphRepresentation; +import com.alibaba.cloud.ai.graph.agent.Agent; +import jakarta.annotation.Nonnull; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * Static Agent Loader for programmatically provided agents. + * + *

This loader takes a static list of pre-created agent instances and makes them available + * through the AgentLoader interface. Perfect for cases where you already have agent instances and + * just need a convenient way to wrap them in an AgentLoader. + * + *

This class is not a Spring component by itself - instances are created programmatically and + * then registered as beans via factory methods. + */ +@Component +class AgentStaticLoader implements AgentLoader { + + private final Map agents = new ConcurrentHashMap<>(); + + public AgentStaticLoader(Agent agent) { + + GraphRepresentation representation = agent.getAndCompileGraph().stateGraph.getGraph(GraphRepresentation.Type.PLANTUML); + System.out.println(representation.content()); + + this.agents.put("research_agent", agent); + } + + @Override + @Nonnull + public List listAgents() { + return agents.keySet().stream().toList(); + } + + @Override + public Agent loadAgent(String name) { + if (name == null || name.trim().isEmpty()) { + throw new IllegalArgumentException("Agent name cannot be null or empty"); + } + + Agent agent = agents.get(name); + if (agent == null) { + throw new NoSuchElementException("Agent not found: " + name); + } + + return agent; + } +} diff --git a/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/ChatbotAgent.java b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/ChatbotAgent.java new file mode 100644 index 00000000..62ce173a --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/ChatbotAgent.java @@ -0,0 +1,126 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.chatbot; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; +import com.alibaba.cloud.ai.examples.chatbot.tool.UserLocationTool; +import com.alibaba.cloud.ai.examples.chatbot.tool.WeatherForLocationTool; +import com.alibaba.cloud.ai.examples.chatbot.tool.WeatherTool; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.hook.Hook; +import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook; +import com.alibaba.cloud.ai.graph.agent.hook.hip.ToolConfig; +import com.alibaba.cloud.ai.graph.agent.hook.modelcalllimit.ModelCallLimitHook; +import com.alibaba.cloud.ai.graph.agent.hook.summarization.SummarizationHook; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Slf4j +@Configuration +public class ChatbotAgent { + + private final String QWEN_MODEL = "qwen-plus"; + + String customSchema = """ + 请按照以下JSON格式输出: + { + "title": "标题", + "content": "内容", + "style": "风格" + } + """; + + + ToolCallback weatherTool = FunctionToolCallback.builder("get_weather", new WeatherTool()) + .description("Get weather for a given city") + .inputType(String.class) + .build(); + + ToolCallback getWeatherTool = FunctionToolCallback + .builder("getWeatherForLocation", new WeatherForLocationTool()) + .description("Get weather for a given city") + .inputType(String.class) + .build(); + + ToolCallback getUserLocationTool = FunctionToolCallback + .builder("getUserLocation", new UserLocationTool()) + .description("Retrieve user location based on user ID") + .inputType(String.class) + .build(); + + Hook humanInTheLoopHook = HumanInTheLoopHook.builder() + .approvalOn("getWeatherTool", + ToolConfig.builder().description("Please confirm tool execution.").build()) + .build(); + + @Bean("dashScopeApi") + public DashScopeApi dashScopeApi() { + return DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + } + + /** + * 自定义的ChatModel必须填写.model(模型名称)参数 + * @param dashScopeApi + * @return + */ + @Bean("chatModel") + public ChatModel chatModel(@Qualifier("dashScopeApi") DashScopeApi dashScopeApi) { + return DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .defaultOptions(DashScopeChatOptions.builder() + .model(QWEN_MODEL) // 控制随机性 + .withTemperature(0.7) // 控制随机性 + .withMaxToken(2000) // 最大输出长度 + .withTopP(0.9) // 核采样参数 + .build()) + .build(); + } + + @Bean + public ReactAgent agent(@Qualifier("chatModel") ChatModel chatModel) { + SummarizationHook summarizationHook = SummarizationHook.builder() + .model(chatModel) + .maxTokensBeforeSummary(4000) + .messagesToKeep(20) + .build(); + ModelCallLimitHook modelCallLimitHook = ModelCallLimitHook.builder().runLimit(5).build(); + return ReactAgent.builder() + .name("schema_agent") + .model(chatModel) + .tools(getWeatherTool) + .saver(new MemorySaver()) + .hooks(modelCallLimitHook, summarizationHook) + .build(); + } + + @Bean + public MemorySaver memorySaver() { + return new MemorySaver(); + } + + +} + diff --git a/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/ChatbotApplication.java b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/ChatbotApplication.java new file mode 100644 index 00000000..1e7b1737 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/ChatbotApplication.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.chatbot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; + +@SpringBootApplication +public class ChatbotApplication { + public static void main(String[] args) { + SpringApplication.run(ChatbotApplication.class, args); + } + + @Bean + public ApplicationListener applicationReadyEventListener(Environment environment) { + return event -> { + String port = environment.getProperty("server.port", "8080"); + String contextPath = environment.getProperty("server.servlet.context-path", ""); + String accessUrl = "http://localhost:" + port + contextPath + "/chatui/index.html"; + System.out.println("\n🎉========================================🎉"); + System.out.println("✅ Application is ready!"); + System.out.println("🚀 Chat with you agent: " + accessUrl); + System.out.println("🎉========================================🎉\n"); + }; + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/controller/ChatController.java b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/controller/ChatController.java new file mode 100644 index 00000000..7072325f --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/controller/ChatController.java @@ -0,0 +1,29 @@ +package com.alibaba.cloud.ai.examples.chatbot.controller; + +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import jakarta.annotation.Resource; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +@RequestMapping("chat") +public class ChatController { + + + @Resource + private ReactAgent agent; + + /** + * @Description: 聊天 + SSE返回 + */ + @PostMapping("doChat") + public void doChat() throws Exception { + AssistantMessage response = agent.call("分析这段文本:春天来了,万物复苏。"); +// 输出会遵循 PoemOutput 的结构 + System.out.println(response.getText()); + } + +} diff --git a/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/resp/PoemOutput.java b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/resp/PoemOutput.java new file mode 100644 index 00000000..e1f95d0b --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/resp/PoemOutput.java @@ -0,0 +1,17 @@ +package com.alibaba.cloud.ai.examples.chatbot.resp; + +public class PoemOutput { + private String title; + private String content; + private String style; + + // Getters and Setters + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + + public String getContent() { return content; } + public void setContent(String content) { this.content = content; } + + public String getStyle() { return style; } + public void setStyle(String style) { this.style = style; } +} \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/resp/ResponseFormat.java b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/resp/ResponseFormat.java new file mode 100644 index 00000000..6e638053 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/resp/ResponseFormat.java @@ -0,0 +1,27 @@ +package com.alibaba.cloud.ai.examples.chatbot.resp; + +// 使用 Java 类定义响应格式 +public class ResponseFormat { + // 一个双关语响应(始终必需) + private String punnyResponse; + + // 如果可用的话,关于天气的任何有趣信息 + private String weatherConditions; + + // Getters and Setters + public String getPunnyResponse() { + return punnyResponse; + } + + public void setPunnyResponse(String punnyResponse) { + this.punnyResponse = punnyResponse; + } + + public String getWeatherConditions() { + return weatherConditions; + } + + public void setWeatherConditions(String weatherConditions) { + this.weatherConditions = weatherConditions; + } +} diff --git a/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/resp/TextAnalysisResult.java b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/resp/TextAnalysisResult.java new file mode 100644 index 00000000..0ba4ed66 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/resp/TextAnalysisResult.java @@ -0,0 +1,21 @@ +package com.alibaba.cloud.ai.examples.chatbot.resp; + +import java.util.List; + +public class TextAnalysisResult { + private String summary; + private List keywords; + private String sentiment; + private Double confidence; + + // Getters and Setters + public String getSummary() { return summary; } + public void setSummary(String summary) { this.summary = summary; } + public List getKeywords() { return keywords; } + public void setKeywords(List keywords) { this.keywords = keywords; } + public String getSentiment() { return sentiment; } + public void setSentiment(String sentiment) { this.sentiment = sentiment; } + public Double getConfidence() { return confidence; } + public void setConfidence(Double confidence) { this.confidence = confidence; } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/tool/UserLocationTool.java b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/tool/UserLocationTool.java new file mode 100644 index 00000000..ad945339 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/tool/UserLocationTool.java @@ -0,0 +1,16 @@ +package com.alibaba.cloud.ai.examples.chatbot.tool; + +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.tool.annotation.ToolParam; +import java.util.function.BiFunction; + +public class UserLocationTool implements BiFunction { + @Override + public String apply( + @ToolParam(description = "User query") String query, + ToolContext toolContext) { + // 从上下文中获取用户信息 + String userId = (String) toolContext.getContext().get("user_id"); + return "1".equals(userId) ? "Florida" : "San Francisco"; + } +} diff --git a/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/tool/WeatherForLocationTool.java b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/tool/WeatherForLocationTool.java new file mode 100644 index 00000000..57ffd614 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/tool/WeatherForLocationTool.java @@ -0,0 +1,15 @@ +package com.alibaba.cloud.ai.examples.chatbot.tool; + +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.tool.annotation.ToolParam; + +import java.util.function.BiFunction; + +public class WeatherForLocationTool implements BiFunction { + @Override + public String apply( + @ToolParam(description = "The city name") String city, + ToolContext toolContext) { + return "It's always sunny in " + city + "!"; + } +} diff --git a/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/tool/WeatherTool.java b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/tool/WeatherTool.java new file mode 100644 index 00000000..7103f5c4 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/java/com/alibaba/cloud/ai/examples/chatbot/tool/WeatherTool.java @@ -0,0 +1,12 @@ +package com.alibaba.cloud.ai.examples.chatbot.tool; + +import org.springframework.ai.chat.model.ToolContext; + +import java.util.function.BiFunction; + +public class WeatherTool implements BiFunction { + @Override + public String apply(String city, ToolContext toolContext) { + return "It's always sunny in " + city + "!"; + } +} diff --git a/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/resources/application.yml b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/resources/application.yml new file mode 100644 index 00000000..151e2075 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/a01-chatbot/src/main/resources/application.yml @@ -0,0 +1,4 @@ +spring: + ai: + dashscope: + api-key: ${ALI_AI_KEY} \ No newline at end of file diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/README.md b/project/spingai/spring-ai-alibaba-examples/documentation/README.md new file mode 100644 index 00000000..582d5bf8 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/README.md @@ -0,0 +1 @@ +This directory contains the full code examples for documentations published on https://java2ai.com diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/pom.xml b/project/spingai/spring-ai-alibaba-examples/documentation/pom.xml new file mode 100644 index 00000000..8594f05e --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/pom.xml @@ -0,0 +1,170 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.7 + + + com.alibaba.cloud.ai + documentation + 0.0.1-SNAPSHOT + Examples::Documentation + Examples for Website. + + + + + + + + + + + + + + + + 17 + 1.1.0 + 1.1.0.0-RC2 + 1.1.0.0-RC2 + + 3.22.0 + + + + + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + + + com.alibaba.cloud.ai + spring-ai-alibaba-extensions-bom + ${spring-ai-alibaba-extensions.version} + pom + import + + + com.alibaba.cloud.ai + spring-ai-alibaba-bom + ${spring-ai-alibaba.version} + pom + import + + + + + + + com.alibaba.cloud.ai + spring-ai-alibaba-studio + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + + + com.alibaba.cloud.ai + spring-ai-alibaba-agent-framework + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-a2a-nacos + + + + org.springframework.ai + spring-ai-starter-model-openai + + + + org.springframework.ai + spring-ai-starter-model-deepseek + + + + org.springframework.ai + spring-ai-starter-mcp-client + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.redisson + redisson + ${redission.version} + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + + false + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + false + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + + + false + + + true + always + + snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + + + false + + + true + always + + sonatype-snapshots + Sonatype Snapshot Repository + https://oss.sonatype.org/content/repositories/snapshots + + + + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/AgentToolExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/AgentToolExample.java new file mode 100644 index 00000000..f377a345 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/AgentToolExample.java @@ -0,0 +1,513 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.advanced; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.agent.AgentTool; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; + +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.converter.BeanOutputConverter; + +import java.util.List; +import java.util.Optional; + +/** + * 智能体作为工具(Agent Tool)示例 + * + * 演示 Multi-agent 工具调用模式,包括: + * 1. 将子Agent作为工具使用 + * 2. 自定义输入和输出Schema + * 3. 类型化的Agent工具调用 + * 4. 完整的工具调用示例 + * + * 参考文档: advanced_doc/agent-tool.md + */ +public class AgentToolExample { + + private final ChatModel chatModel; + + public AgentToolExample(ChatModel chatModel) { + this.chatModel = chatModel; + } + + /** + * Main方法:运行所有示例 + * + * 注意:需要配置ChatModel实例才能运行 + */ + public static void main(String[] args) { + // 创建 DashScope API 实例 + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + // 创建 ChatModel + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + if (chatModel == null) { + System.err.println("错误:请先配置ChatModel实例"); + System.err.println("请设置 AI_DASHSCOPE_API_KEY 环境变量"); + return; + } + + // 创建示例实例 + AgentToolExample example = new AgentToolExample(chatModel); + + // 运行所有示例 + example.runAllExamples(); + } + + /** + * 示例1:基础 Agent Tool 调用 + * + * 主Agent将子Agent作为工具调用,子Agent执行特定任务并返回结果 + */ + public void example1_basicAgentTool() throws GraphRunnerException { + // 创建子Agent - 作为工具使用 + ReactAgent writerAgent = ReactAgent.builder() + .name("writer_agent") + .model(chatModel) + .description("可以写文章") + .instruction("你是一个知名的作家,擅长写作和创作。请根据用户的提问进行回答。") + .build(); + + // 创建主Agent,将子Agent作为工具 + ReactAgent blogAgent = ReactAgent.builder() + .name("blog_agent") + .model(chatModel) + .instruction("根据用户给定的主题写一篇文章。使用写作工具来完成任务。") + .tools(AgentTool.getFunctionToolCallback(writerAgent)) + .build(); + + // 使用 + Optional result = blogAgent.invoke("帮我写一个100字左右的散文"); + + if (result.isPresent()) { + System.out.println("文章生成成功"); + // 处理结果 + } + } + + /** + * 示例2:使用 inputSchema 控制子Agent的输入 + * + * 通过定义输入Schema,使子Agent能够接收结构化的输入信息 + */ + public void example2_agentToolWithInputSchema() throws GraphRunnerException { + // 定义子Agent的输入Schema + String writerInputSchema = """ + { + "type": "object", + "properties": { + "topic": { + "type": "string" + }, + "wordCount": { + "type": "integer" + }, + "style": { + "type": "string" + } + }, + "required": ["topic", "wordCount", "style"] + } + """; + + ReactAgent writerAgent = ReactAgent.builder() + .name("structured_writer_agent") + .model(chatModel) + .description("根据结构化输入写文章") + .instruction("你是一个专业作家。请严格按照输入的主题、字数和风格要求创作文章。") + .inputSchema(writerInputSchema) + .build(); + + ReactAgent coordinatorAgent = ReactAgent.builder() + .name("coordinator_agent") + .model(chatModel) + .instruction("你需要调用写作工具来完成用户的写作请求。请根据用户需求,使用结构化的参数调用写作工具。") + .tools(AgentTool.getFunctionToolCallback(writerAgent)) + .build(); + + Optional result = coordinatorAgent.invoke("请写一篇关于春天的散文,大约150字"); + + if (result.isPresent()) { + System.out.println("结构化输入示例执行成功"); + } + } + + /** + * 示例3:使用 inputType 定义类型化输入 + * + * 使用 Java 类型定义输入,框架会自动生成 JSON Schema + */ + public void example3_agentToolWithInputType() throws GraphRunnerException { + // 定义输入类型 + record ArticleRequest(String topic, int wordCount, String style) { } + + ReactAgent writerAgent = ReactAgent.builder() + .name("typed_writer_agent") + .model(chatModel) + .description("根据类型化输入写文章") + .instruction("你是一个专业作家。请严格按照输入的 topic(主题)、wordCount(字数)和 style(风格)要求创作文章。") + .inputType(ArticleRequest.class) + .build(); + + ReactAgent coordinatorAgent = ReactAgent.builder() + .name("coordinator_with_type_agent") + .model(chatModel) + .instruction("你需要调用写作工具来完成用户的写作请求。工具接收 JSON 格式的参数。") + .tools(AgentTool.getFunctionToolCallback(writerAgent)) + .build(); + + Optional result = coordinatorAgent.invoke("请写一篇关于秋天的现代诗,大约100字"); + + if (result.isPresent()) { + System.out.println("类型化输入示例执行成功"); + } + } + + /** + * 示例4:使用 outputSchema 控制子Agent的输出 + * + * 定义输出Schema,使子Agent返回结构化的输出格式 + */ + public void example4_agentToolWithOutputSchema() throws GraphRunnerException { + // Use BeanOutputConverter to generate outputSchema + BeanOutputConverter outputConverter = new BeanOutputConverter<>(ArticleOutput.class); + String format = outputConverter.getFormat(); + + ReactAgent writerAgent = ReactAgent.builder() + .name("writer_with_output_schema") + .model(chatModel) + .description("写文章并返回结构化输出") + .instruction("你是一个专业作家。请创作文章并严格按照指定的JSON格式返回结果。") + .outputSchema(format) + .build(); + + ReactAgent coordinatorAgent = ReactAgent.builder() + .name("coordinator_output_schema") + .model(chatModel) + .instruction("调用写作工具完成用户请求,工具会返回结构化的文章数据。") + .tools(AgentTool.getFunctionToolCallback(writerAgent)) + .build(); + + Optional result = coordinatorAgent.invoke("写一篇关于冬天的短文"); + + if (result.isPresent()) { + System.out.println("结构化输出示例执行成功"); + } + } + + /** + * 示例5:使用 outputType 定义类型化输出 + * + * 使用 Java 类型定义输出,框架会自动生成输出 schema + */ + public void example5_agentToolWithOutputType() throws GraphRunnerException { + // 定义输出类型 + class ArticleOutput { + private String title; + private String content; + private int characterCount; + + // getters and setters + public String getTitle() { + return title; + } + + public String getContent() { + return content; + } + + public int getCharacterCount() { + return characterCount; + } + + public void setTitle(String title) { + this.title = title; + } + + + public void setContent(String content) { + this.content = content; + } + + + public void setCharacterCount(int characterCount) { + this.characterCount = characterCount; + } + } + + ReactAgent writerAgent = ReactAgent.builder() + .name("writer_with_output_type") + .model(chatModel) + .description("写文章并返回类型化输出") + .instruction("你是一个专业作家。请创作文章并返回包含 title、content 和 characterCount 的结构化结果。") + .outputType(ArticleOutput.class) + .build(); + + ReactAgent coordinatorAgent = ReactAgent.builder() + .name("coordinator_output_type") + .model(chatModel) + .instruction("调用写作工具完成用户请求。") + .tools(AgentTool.getFunctionToolCallback(writerAgent)) + .build(); + + Optional result = coordinatorAgent.invoke("写一篇关于夏天的小诗"); + + if (result.isPresent()) { + System.out.println("类型化输出示例执行成功"); + } + } + + /** + * 示例6:完整类型化示例 + * + * 同时使用 inputType 和 outputType 进行完整的类型化Agent工具调用 + */ + public void example6_fullTypedAgentTool() throws GraphRunnerException { + // 定义输入和输出类型 + record ArticleRequest(String topic, int wordCount, String style) { } + + class ArticleOutput { + private String title; + private String content; + private int characterCount; + + public String getTitle() { + return title; + } + + public String getContent() { + return content; + } + + public int getCharacterCount() { + return characterCount; + } + + public void setTitle(String title) { + this.title = title; + } + + + public void setContent(String content) { + this.content = content; + } + + + public void setCharacterCount(int characterCount) { + this.characterCount = characterCount; + } + } + + class ReviewOutput { + private String comment; + private boolean approved; + private List suggestions; + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public boolean isApproved() { + return approved; + } + + public void setApproved(boolean approved) { + this.approved = approved; + } + + public List getSuggestions() { + return suggestions; + } + + public void setSuggestions(List suggestions) { + this.suggestions = suggestions; + } + } + + // 创建完整类型化的Agent + ReactAgent writerAgent = ReactAgent.builder() + .name("full_typed_writer") + .model(chatModel) + .description("完整类型化的写作工具") + .instruction("根据结构化输入(topic、wordCount、style)创作文章,并返回结构化输出(title、content、characterCount)。") + .inputType(ArticleRequest.class) + .outputType(ArticleOutput.class) + .build(); + + ReactAgent reviewerAgent = ReactAgent.builder() + .name("typed_reviewer") + .model(chatModel) + .description("完整类型化的评审工具") + .instruction("对文章进行评审,返回评审意见(comment、approved、suggestions)。") + .outputType(ReviewOutput.class) + .build(); + + ReactAgent orchestratorAgent = ReactAgent.builder() + .name("orchestrator") + .model(chatModel) + .instruction("协调写作和评审流程。先调用写作工具创作文章,然后调用评审工具进行评审。") + .tools( + AgentTool.getFunctionToolCallback(writerAgent), + AgentTool.getFunctionToolCallback(reviewerAgent) + ) + .build(); + + Optional result = orchestratorAgent.invoke("请写一篇关于友谊的散文,约200字,需要评审"); + + if (result.isPresent()) { + System.out.println("完整类型化示例执行成功"); + } + } + + /** + * 示例7:多个子Agent作为工具 + * + * 主Agent可以访问多个不同的子Agent工具,根据需要调用 + */ + public void example7_multipleAgentTools() throws GraphRunnerException { + // 创建写作Agent + ReactAgent writerAgent = ReactAgent.builder() + .name("writer_agent") + .model(chatModel) + .description("专门负责创作文章和内容生成") + .instruction("你是一个专业作家,擅长各类文章创作。") + .build(); + + // 创建翻译Agent + ReactAgent translatorAgent = ReactAgent.builder() + .name("translator_agent") + .model(chatModel) + .description("专门负责文本翻译工作") + .instruction("你是一个专业翻译,能够准确翻译多种语言。") + .build(); + + // 创建总结Agent + ReactAgent summarizerAgent = ReactAgent.builder() + .name("summarizer_agent") + .model(chatModel) + .description("专门负责内容总结和提炼") + .instruction("你是一个内容总结专家,擅长提炼关键信息。") + .build(); + + // 创建主Agent,集成多个工具 + ReactAgent multiToolAgent = ReactAgent.builder() + .name("multi_tool_coordinator") + .model(chatModel) + .instruction("你可以访问多个专业工具:写作、翻译和总结。" + + "根据用户需求选择合适的工具来完成任务。") + .tools( + AgentTool.getFunctionToolCallback(writerAgent), + AgentTool.getFunctionToolCallback(translatorAgent), + AgentTool.getFunctionToolCallback(summarizerAgent) + ) + .build(); + + // 测试不同的请求 + multiToolAgent.invoke("请写一篇关于AI的文章,然后翻译成英文,最后给出摘要"); + + System.out.println("多工具Agent示例执行成功"); + } + + /** + * 文章输出类 - 用于示例4和示例5 + */ + public static class ArticleOutput { + private String title; + private String content; + private int characterCount; + + // Getters and Setters + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public int getCharacterCount() { + return characterCount; + } + + public void setCharacterCount(int characterCount) { + this.characterCount = characterCount; + } + } + + /** + * 运行所有示例 + */ + public void runAllExamples() { + System.out.println("=== 智能体作为工具(Agent Tool)示例 ===\n"); + + try { + System.out.println("示例1: 基础 Agent Tool 调用"); + example1_basicAgentTool(); + System.out.println(); + + System.out.println("示例2: 使用 inputSchema 控制输入"); + example2_agentToolWithInputSchema(); + System.out.println(); + + System.out.println("示例3: 使用 inputType 定义类型化输入"); + example3_agentToolWithInputType(); + System.out.println(); + + System.out.println("示例4: 使用 outputSchema 控制输出"); + example4_agentToolWithOutputSchema(); + System.out.println(); + + System.out.println("示例5: 使用 outputType 定义类型化输出"); + example5_agentToolWithOutputType(); + System.out.println(); + + System.out.println("示例6: 完整类型化示例"); + example6_fullTypedAgentTool(); + System.out.println(); + + System.out.println("示例7: 多个子Agent作为工具"); + example7_multipleAgentTools(); + System.out.println(); + + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/ContextEngineeringExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/ContextEngineeringExample.java new file mode 100644 index 00000000..80d5e4ce --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/ContextEngineeringExample.java @@ -0,0 +1,562 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.advanced; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; +import com.alibaba.cloud.ai.graph.agent.hook.HookPositions; +import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand; +import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesModelHook; +import com.alibaba.cloud.ai.graph.agent.hook.messages.UpdatePolicy; +import com.alibaba.cloud.ai.graph.agent.interceptor.ModelCallHandler; +import com.alibaba.cloud.ai.graph.agent.interceptor.ModelInterceptor; +import com.alibaba.cloud.ai.graph.agent.interceptor.ModelRequest; +import com.alibaba.cloud.ai.graph.agent.interceptor.ModelResponse; +import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.tool.ToolCallback; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 上下文工程(Context Engineering)示例 + * + * 演示如何通过上下文工程提高Agent的可靠性,包括: + * 1. 模型上下文:系统提示、消息历史、工具、模型选择、响应格式 + * 2. 工具上下文:工具访问和修改状态 + * 3. 生命周期上下文:Hook机制 + * + * 参考文档: advanced_doc/context-engineering.md + */ +public class ContextEngineeringExample { + + private final ChatModel chatModel; + + public ContextEngineeringExample(ChatModel chatModel) { + this.chatModel = chatModel; + } + + /** + * Main方法:运行所有示例 + * + * 注意:需要配置ChatModel实例才能运行 + */ + public static void main(String[] args) { + // 创建 DashScope API 实例 + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + // 创建 ChatModel + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + if (chatModel == null) { + System.err.println("错误:请先配置ChatModel实例"); + System.err.println("请设置 AI_DASHSCOPE_API_KEY 环境变量"); + return; + } + + // 创建示例实例 + ContextEngineeringExample example = new ContextEngineeringExample(chatModel); + + // 运行所有示例 + example.runAllExamples(); + } + + /** + * 示例1:基于状态的动态提示 + * + * 根据对话长度调整系统提示 + */ + public void example1_stateAwarePrompt() throws GraphRunnerException { + // 创建一个模型拦截器,根据对话长度调整系统提示 + class StateAwarePromptInterceptor extends ModelInterceptor { + @Override + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { + List messages = request.getMessages(); + int messageCount = messages.size(); + + // 基础提示 + String basePrompt = "你是一个有用的助手。"; + + // 根据消息数量调整提示 + if (messageCount > 10) { + basePrompt += "\n这是一个长对话 - 请尽量保持精准简捷。"; + } + + // 更新系统消息(参考 TodoListInterceptor 的实现方式) + SystemMessage enhancedSystemMessage; + if (request.getSystemMessage() == null) { + enhancedSystemMessage = new SystemMessage(basePrompt); + } + else { + enhancedSystemMessage = new SystemMessage( + request.getSystemMessage().getText() + "\n\n" + basePrompt + ); + } + + // 创建增强的请求 + ModelRequest enhancedRequest = ModelRequest.builder(request) + .systemMessage(enhancedSystemMessage) + .build(); + + // 调用处理器 + return handler.call(enhancedRequest); + } + + @Override + public String getName() { + return "StateAwarePromptInterceptor"; + } + } + + // 使用拦截器创建Agent + ReactAgent agent = ReactAgent.builder() + .name("context_aware_agent") + .model(chatModel) + .interceptors(new StateAwarePromptInterceptor()) + .build(); + + // 测试 + agent.invoke("你好"); + System.out.println("基于状态的动态提示示例执行完成"); + } + + /** + * 示例2:基于存储的个性化提示 + * + * 从长期记忆加载用户偏好并生成个性化提示 + */ + public void example2_personalizedPrompt() throws GraphRunnerException { + // 用户偏好类 + class UserPreferences { + private String communicationStyle; + private String language; + private List interests; + + public UserPreferences(String style, String lang, List interests) { + this.communicationStyle = style; + this.language = lang; + this.interests = interests; + } + + public String getCommunicationStyle() { + return communicationStyle; + } + + public String getLanguage() { + return language; + } + + public List getInterests() { + return interests; + } + } + + // 简单的用户偏好存储 + class UserPreferenceStore { + private Map store = new HashMap<>(); + + public UserPreferences getPreferences(String userId) { + return store.getOrDefault(userId, + new UserPreferences("专业", "中文", List.of())); + } + + public void savePreferences(String userId, UserPreferences prefs) { + store.put(userId, prefs); + } + } + + UserPreferenceStore store = new UserPreferenceStore(); + store.savePreferences("user_001", + new UserPreferences("友好轻松", "中文", List.of("技术", "阅读"))); + + // 从长期记忆加载用户偏好 + class PersonalizedPromptInterceptor extends ModelInterceptor { + private final UserPreferenceStore store; + + public PersonalizedPromptInterceptor(UserPreferenceStore store) { + this.store = store; + } + + @Override + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { + // 从运行时上下文获取用户ID + String userId = getUserIdFromContext(request); + + // 从存储加载用户偏好 + UserPreferences prefs = store.getPreferences(userId); + + // 构建个性化提示 + String personalizedPrompt = buildPersonalizedPrompt(prefs); + + // 更新系统消息(参考 TodoListInterceptor 的实现方式) + SystemMessage enhancedSystemMessage; + if (request.getSystemMessage() == null) { + enhancedSystemMessage = new SystemMessage(personalizedPrompt); + } + else { + enhancedSystemMessage = new SystemMessage( + request.getSystemMessage().getText() + "\n\n" + personalizedPrompt + ); + } + + // 创建增强的请求 + ModelRequest enhancedRequest = ModelRequest.builder(request) + .systemMessage(enhancedSystemMessage) + .build(); + + // 调用处理器 + return handler.call(enhancedRequest); + } + + private String getUserIdFromContext(ModelRequest request) { + // 从请求上下文提取用户ID + return "user_001"; // 简化示例 + } + + private String buildPersonalizedPrompt(UserPreferences prefs) { + StringBuilder prompt = new StringBuilder("你是一个有用的助手。"); + + if (prefs.getCommunicationStyle() != null) { + prompt.append("\n沟通风格:").append(prefs.getCommunicationStyle()); + } + + if (prefs.getLanguage() != null) { + prompt.append("\n使用语言:").append(prefs.getLanguage()); + } + + if (!prefs.getInterests().isEmpty()) { + prompt.append("\n用户兴趣:").append(String.join(", ", prefs.getInterests())); + } + + return prompt.toString(); + } + + @Override + public String getName() { + return "PersonalizedPromptInterceptor"; + } + } + + ReactAgent agent = ReactAgent.builder() + .name("personalized_agent") + .model(chatModel) + .interceptors(new PersonalizedPromptInterceptor(store)) + .build(); + + agent.invoke("介绍一下最新的AI技术"); + System.out.println("个性化提示示例执行完成"); + } + + /** + * 示例3:消息过滤 + * + * 只保留最近的N条消息,避免上下文过长 + */ + public void example3_messageFilter() { + class MessageFilterInterceptor extends ModelInterceptor { + private final int maxMessages; + + public MessageFilterInterceptor(int maxMessages) { + this.maxMessages = maxMessages; + } + + @Override + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler next) { + List messages = request.getMessages(); + + // 只保留最近的N条消息 + if (messages.size() > maxMessages) { + List filtered = new ArrayList<>(); + + // 添加系统消息 + messages.stream() + .filter(m -> m instanceof SystemMessage) + .findFirst() + .ifPresent(filtered::add); + + // 添加最近的消息 + int startIndex = Math.max(0, messages.size() - maxMessages + 1); + filtered.addAll(messages.subList(startIndex, messages.size())); + + messages = filtered; + } + + ModelRequest updatedRequest = ModelRequest.builder(request) + .messages(messages) + .build(); + + return next.call(updatedRequest); + } + + @Override + public String getName() { + return "MessageFilterInterceptor"; + } + } + + ReactAgent agent = ReactAgent.builder() + .name("message_filter_agent") + .model(chatModel) + .interceptors(new MessageFilterInterceptor(10)) + .build(); + + System.out.println("消息过滤示例执行完成"); + } + + /** + * 示例4:基于上下文的工具选择 + * + * 根据用户角色动态选择可用工具 + */ + public void example4_contextualToolSelection() { + class ContextualToolInterceptor extends ModelInterceptor { + private final Map> roleBasedTools; + + public ContextualToolInterceptor(Map> roleBasedTools) { + this.roleBasedTools = roleBasedTools; + } + + @Override + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler next) { + // 从上下文获取用户角色 + String userRole = getUserRole(request); + + // 根据角色选择工具 + List allowedTools = roleBasedTools.getOrDefault( + userRole, + Collections.emptyList() + ); + + // 更新工具选项(注:实际实现需要根据框架API调整) + // 这里展示概念性代码 + System.out.println("为角色 " + userRole + " 选择了 " + allowedTools.size() + " 个工具"); + + return next.call(request); + } + + private String getUserRole(ModelRequest request) { + // 从请求上下文提取用户角色 + return "user"; // 简化示例 + } + + @Override + public String getName() { + return "ContextualToolInterceptor"; + } + } + + // 配置基于角色的工具(示例) + Map> roleTools = Map.of( + "admin", List.of(/* readTool, writeTool, deleteTool */), + "user", List.of(/* readTool */), + "guest", List.of() + ); + + ReactAgent agent = ReactAgent.builder() + .name("role_based_agent") + .model(chatModel) + .interceptors(new ContextualToolInterceptor(roleTools)) + .build(); + + System.out.println("基于上下文的工具选择示例执行完成"); + } + + /** + * 示例5:日志记录 Hook + * + * 使用MessagesModelHook在模型调用前后记录日志 + */ + public void example5_loggingHook() throws GraphRunnerException { + @HookPositions({HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL}) + class LoggingHook extends MessagesModelHook { + @Override + public String getName() { + return "logging_hook"; + } + + @Override + public AgentCommand beforeModel(List previousMessages, RunnableConfig config) { + // 在模型调用前记录 + System.out.println("模型调用前 - 消息数: " + previousMessages.size()); + // 不修改消息,返回原始消息 + return new AgentCommand(previousMessages); + } + + @Override + public AgentCommand afterModel(List previousMessages, RunnableConfig config) { + // 在模型调用后记录 + System.out.println("模型调用后 - 响应已生成"); + // 不修改消息,返回原始消息 + return new AgentCommand(previousMessages); + } + } + + // 使用Hook + ReactAgent agent = ReactAgent.builder() + .name("logged_agent") + .model(chatModel) + .hooks(new LoggingHook()) + .build(); + + agent.invoke("测试日志记录"); + System.out.println("日志记录Hook示例执行完成"); + } + + /** + * 示例6:消息摘要 Hook + * + * 当对话过长时自动生成摘要 + * 使用MessagesModelHook实现 + */ + public void example6_summarizationHook() { + @HookPositions({HookPosition.BEFORE_MODEL}) + class SummarizationHook extends MessagesModelHook { + private final ChatModel summarizationModel; + private final int triggerLength; + + public SummarizationHook(ChatModel model, int triggerLength) { + this.summarizationModel = model; + this.triggerLength = triggerLength; + } + + @Override + public String getName() { + return "summarization_hook"; + } + + @Override + public AgentCommand beforeModel(List previousMessages, RunnableConfig config) { + if (previousMessages.size() <= triggerLength) { + // 如果消息数量未超过阈值,无需总结 + return new AgentCommand(previousMessages); + } + + // 生成对话摘要 + String summary = generateSummary(previousMessages); + + // 查找是否已存在 SystemMessage + SystemMessage existingSystemMessage = null; + for (Message msg : previousMessages) { + if (msg instanceof SystemMessage) { + existingSystemMessage = (SystemMessage) msg; + break; + } + } + + // 创建摘要 SystemMessage + String summaryText = "之前对话摘要:" + summary; + SystemMessage summarySystemMessage; + if (existingSystemMessage != null) { + // 如果存在 SystemMessage,追加摘要信息 + summarySystemMessage = new SystemMessage( + existingSystemMessage.getText() + "\n\n" + summaryText + ); + } + else { + // 如果不存在,创建新的 + summarySystemMessage = new SystemMessage(summaryText); + } + + // 保留最近的几条消息 + int recentCount = Math.min(5, previousMessages.size()); + List recentMessages = previousMessages.subList( + previousMessages.size() - recentCount, + previousMessages.size() + ); + + // 构建新的消息列表 + List newMessages = new ArrayList<>(); + newMessages.add(summarySystemMessage); + // 添加最近的消息,排除旧的 SystemMessage(如果存在) + for (Message msg : recentMessages) { + if (msg != existingSystemMessage) { + newMessages.add(msg); + } + } + + // 使用 REPLACE 策略替换所有消息 + return new AgentCommand(newMessages, UpdatePolicy.REPLACE); + } + + private String generateSummary(List messages) { + // 使用另一个模型生成摘要 + String conversation = messages.stream() + .map(Message::getText) + .collect(Collectors.joining("\n")); + + // 简化示例:返回固定摘要 + return "之前讨论了多个主题..."; + } + } + + ReactAgent agent = ReactAgent.builder() + .name("summarizing_agent") + .model(chatModel) + .hooks(new SummarizationHook(chatModel, 20)) + .build(); + + System.out.println("消息摘要Hook示例执行完成"); + } + + /** + * 运行所有示例 + */ + public void runAllExamples() { + System.out.println("=== 上下文工程(Context Engineering)示例 ===\n"); + + try { + // System.out.println("示例1: 基于状态的动态提示"); + // example1_stateAwarePrompt(); + // System.out.println(); + // + // System.out.println("示例2: 基于存储的个性化提示"); + // example2_personalizedPrompt(); + // System.out.println(); + // + // System.out.println("示例3: 消息过滤"); + // example3_messageFilter(); + // System.out.println(); + // + // System.out.println("示例4: 基于上下文的工具选择"); + // example4_contextualToolSelection(); + // System.out.println(); + // + // System.out.println("示例5: 日志记录Hook"); + example5_loggingHook(); + // System.out.println(); + // + // System.out.println("示例6: 消息摘要Hook"); + // example6_summarizationHook(); + // System.out.println(); + + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/HumanInTheLoopExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/HumanInTheLoopExample.java new file mode 100644 index 00000000..9efd0862 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/HumanInTheLoopExample.java @@ -0,0 +1,739 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.advanced; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.*; +import com.alibaba.cloud.ai.graph.action.InterruptionMetadata; +import com.alibaba.cloud.ai.graph.action.NodeAction; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook; +import com.alibaba.cloud.ai.graph.agent.hook.hip.ToolConfig; +import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 人工介入(Human-in-the-Loop)示例 + * + * 演示如何使用人工介入Hook为Agent工具调用添加人工监督,包括: + * 1. 配置中断和审批 + * 2. 批准(approve)决策 + * 3. 编辑(edit)决策 + * 4. 拒绝(reject)决策 + * 5. 处理多个工具调用 + * 6. Workflow中嵌套ReactAgent的人工中断 + * 7. 实用工具方法 + * + * 参考文档: advanced_doc/human-in-the-loop.md + */ +public class HumanInTheLoopExample { + + private final ChatModel chatModel; + + public HumanInTheLoopExample(ChatModel chatModel) { + this.chatModel = chatModel; + } + + /** + * 实用工具方法:批准所有工具调用 + */ + public static InterruptionMetadata approveAll(InterruptionMetadata interruptionMetadata) { + InterruptionMetadata.Builder builder = InterruptionMetadata.builder() + .nodeId(interruptionMetadata.node()) + .state(interruptionMetadata.state()); + + interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> { + builder.addToolFeedback( + InterruptionMetadata.ToolFeedback.builder(toolFeedback) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED) + .build() + ); + }); + + return builder.build(); + } + + /** + * 实用工具方法:拒绝所有工具调用 + */ + public static InterruptionMetadata rejectAll(InterruptionMetadata interruptionMetadata, String reason) { + InterruptionMetadata.Builder builder = InterruptionMetadata.builder() + .nodeId(interruptionMetadata.node()) + .state(interruptionMetadata.state()); + + interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> { + builder.addToolFeedback( + InterruptionMetadata.ToolFeedback.builder(toolFeedback) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.REJECTED) + .description(reason) + .build() + ); + }); + + return builder.build(); + } + + /** + * 实用工具方法:编辑特定工具的参数 + */ + public static InterruptionMetadata editTool( + InterruptionMetadata interruptionMetadata, + String toolName, + String newArguments) { + InterruptionMetadata.Builder builder = InterruptionMetadata.builder() + .nodeId(interruptionMetadata.node()) + .state(interruptionMetadata.state()); + + interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> { + if (toolFeedback.getName().equals(toolName)) { + builder.addToolFeedback( + InterruptionMetadata.ToolFeedback.builder(toolFeedback) + .arguments(newArguments) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.EDITED) + .build() + ); + } + else { + builder.addToolFeedback( + InterruptionMetadata.ToolFeedback.builder(toolFeedback) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED) + .build() + ); + } + }); + + return builder.build(); + } + + /** + * Main方法:运行所有示例 + * + * 注意:需要配置ChatModel实例才能运行 + */ + public static void main(String[] args) { + // 创建 DashScope API 实例 + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + // 创建 ChatModel + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + if (chatModel == null) { + System.err.println("错误:请先配置ChatModel实例"); + System.err.println("请设置 AI_DASHSCOPE_API_KEY 环境变量"); + return; + } + + // 创建示例实例 + HumanInTheLoopExample example = new HumanInTheLoopExample(chatModel); + + // 运行所有示例 + example.runAllExamples(); + } + + /** + * 示例1:配置中断和基本使用 + * + * 为特定工具配置人工审批 + */ + public void example1_basicConfiguration() throws Exception { + // 配置检查点保存器(人工介入需要检查点来处理中断) + MemorySaver memorySaver = new MemorySaver(); + + // 创建工具回调(示例) + ToolCallback writeFileTool = FunctionToolCallback.builder("write_file", (args) -> "文件已写入") + .description("写入文件") + .inputType(String.class) + .build(); + + ToolCallback executeSqlTool = FunctionToolCallback.builder("execute_sql", (args) -> "SQL已执行") + .description("执行SQL语句") + .inputType(String.class) + .build(); + + ToolCallback readDataTool = FunctionToolCallback.builder("read_data", (args) -> "数据已读取") + .description("读取数据") + .inputType(String.class) + .build(); + + // 创建人工介入Hook + HumanInTheLoopHook humanInTheLoopHook = HumanInTheLoopHook.builder() + .approvalOn("write_file", ToolConfig.builder() + .description("文件写入操作需要审批") + .build()) + .approvalOn("execute_sql", ToolConfig.builder() + .description("SQL执行操作需要审批") + .build()) + .build(); + + // 创建Agent + ReactAgent agent = ReactAgent.builder() + .name("approval_agent") + .model(chatModel) + .tools(writeFileTool, executeSqlTool, readDataTool) + .hooks(List.of(humanInTheLoopHook)) + .saver(memorySaver) + .build(); + + AssistantMessage message = agent.call("执行SQL语句:select * from user limit 1"); + System.out.println(message.getText()); + + System.out.println("人工介入Hook配置示例完成"); + } + + /** + * 示例2:批准(approve)决策 + * + * 人工批准工具调用并继续执行 + */ + public void example2_approveDecision() throws Exception { + MemorySaver memorySaver = new MemorySaver(); + + ToolCallback poetTool = FunctionToolCallback.builder("poem", (args) -> "春江潮水连海平,海上明月共潮生...") + .description("写诗工具") + .inputType(String.class) + .build(); + + HumanInTheLoopHook humanInTheLoopHook = HumanInTheLoopHook.builder() + .approvalOn("poem", ToolConfig.builder() + .description("请确认诗歌创作操作") + .build()) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("poet_agent") + .model(chatModel) + .tools(List.of(poetTool)) + .hooks(List.of(humanInTheLoopHook)) + .saver(memorySaver) + .build(); + + String threadId = "user-session-001"; + RunnableConfig config = RunnableConfig.builder() + .threadId(threadId) + .build(); + + // 第一次调用 - 触发中断 + System.out.println("=== 第一次调用:期望中断 ==="); + Optional result = agent.invokeAndGetOutput( + "帮我写一首100字左右的诗", + config + ); + + // 检查中断并处理 + if (result.isPresent() && result.get() instanceof InterruptionMetadata) { + InterruptionMetadata interruptionMetadata = (InterruptionMetadata) result.get(); + + System.out.println("检测到中断,需要人工审批"); + List toolFeedbacks = + interruptionMetadata.toolFeedbacks(); + + for (InterruptionMetadata.ToolFeedback feedback : toolFeedbacks) { + System.out.println("工具: " + feedback.getName()); + System.out.println("参数: " + feedback.getArguments()); + System.out.println("描述: " + feedback.getDescription()); + } + + // 构建批准反馈 + InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder() + .nodeId(interruptionMetadata.node()) + .state(interruptionMetadata.state()); + + // 对每个工具调用设置批准决策 + interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> { + InterruptionMetadata.ToolFeedback approvedFeedback = + InterruptionMetadata.ToolFeedback.builder(toolFeedback) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED) + .build(); + feedbackBuilder.addToolFeedback(approvedFeedback); + }); + + InterruptionMetadata approvalMetadata = feedbackBuilder.build(); + + // 使用批准决策恢复执行 + RunnableConfig resumeConfig = RunnableConfig.builder() + .threadId(threadId) // 相同的线程ID以恢复暂停的对话 + .addMetadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY, approvalMetadata) + .build(); + + // 第二次调用以恢复执行 + System.out.println("\n=== 第二次调用:使用批准决策恢复 ==="); + Optional finalResult = agent.invokeAndGetOutput("", resumeConfig); + + if (finalResult.isPresent()) { + System.out.println("执行完成"); + } + } + + System.out.println("批准决策示例执行完成"); + } + + /** + * 示例3:编辑(edit)决策 + * + * 人工编辑工具参数后继续执行 + */ + public void example3_editDecision() throws Exception { + MemorySaver memorySaver = new MemorySaver(); + + ToolCallback executeSqlTool = FunctionToolCallback.builder("execute_sql", (args) -> "SQL执行结果") + .description("执行SQL语句") + .inputType(String.class) + .build(); + + HumanInTheLoopHook humanInTheLoopHook = HumanInTheLoopHook.builder() + .approvalOn("execute_sql", ToolConfig.builder() + .description("SQL执行操作需要审批") + .build()) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("sql_agent") + .model(chatModel) + .tools(executeSqlTool) + .hooks(List.of(humanInTheLoopHook)) + .saver(memorySaver) + .build(); + + String threadId = "sql-session-001"; + RunnableConfig config = RunnableConfig.builder() + .threadId(threadId) + .build(); + + // 第一次调用 - 触发中断 + Optional result = agent.invokeAndGetOutput( + "删除数据库中的旧记录", + config + ); + + if (result.isPresent() && result.get() instanceof InterruptionMetadata) { + InterruptionMetadata interruptionMetadata = (InterruptionMetadata) result.get(); + + // 构建编辑反馈 + InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder() + .nodeId(interruptionMetadata.node()) + .state(interruptionMetadata.state()); + + interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> { + // 修改工具参数 + String editedArguments = toolFeedback.getArguments() + .replace("DELETE FROM records", "DELETE FROM old_records"); + + InterruptionMetadata.ToolFeedback editedFeedback = + InterruptionMetadata.ToolFeedback.builder(toolFeedback) + .arguments(editedArguments) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.EDITED) + .build(); + feedbackBuilder.addToolFeedback(editedFeedback); + }); + + InterruptionMetadata editMetadata = feedbackBuilder.build(); + + // 使用编辑决策恢复执行 + RunnableConfig resumeConfig = RunnableConfig.builder() + .threadId(threadId) + .addMetadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY, editMetadata) + .build(); + + Optional finalResult = agent.invokeAndGetOutput("", resumeConfig); + + System.out.println("编辑决策示例执行完成"); + } + } + + /** + * 示例4:拒绝(reject)决策 + * + * 人工拒绝工具调用并终止当前流程 + */ + public void example4_rejectDecision() throws Exception { + MemorySaver memorySaver = new MemorySaver(); + + ToolCallback deleteTool = FunctionToolCallback.builder("delete_data", (args) -> "数据已删除") + .description("删除数据") + .inputType(String.class) + .build(); + + HumanInTheLoopHook humanInTheLoopHook = HumanInTheLoopHook.builder() + .approvalOn("delete_data", ToolConfig.builder() + .description("删除操作需要审批") + .build()) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("delete_agent") + .model(chatModel) + .tools(deleteTool) + .hooks(List.of(humanInTheLoopHook)) + .saver(memorySaver) + .build(); + + String threadId = "delete-session-001"; + RunnableConfig config = RunnableConfig.builder() + .threadId(threadId) + .build(); + + // 第一次调用 - 触发中断 + Optional result = agent.invokeAndGetOutput( + "删除所有用户数据", + config + ); + + if (result.isPresent() && result.get() instanceof InterruptionMetadata) { + InterruptionMetadata interruptionMetadata = (InterruptionMetadata) result.get(); + + // 构建拒绝反馈 + InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder() + .nodeId(interruptionMetadata.node()) + .state(interruptionMetadata.state()); + + interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> { + InterruptionMetadata.ToolFeedback rejectedFeedback = + InterruptionMetadata.ToolFeedback.builder(toolFeedback) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.REJECTED) + .description("不允许删除操作,请使用归档功能代替。") + .build(); + feedbackBuilder.addToolFeedback(rejectedFeedback); + }); + + InterruptionMetadata rejectMetadata = feedbackBuilder.build(); + + // 使用拒绝决策恢复执行 + RunnableConfig resumeConfig = RunnableConfig.builder() + .threadId(threadId) + .addMetadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY, rejectMetadata) + .build(); + + Optional finalResult = agent.invokeAndGetOutput("", resumeConfig); + + System.out.println("拒绝决策示例执行完成"); + } + } + + /** + * 示例5:处理多个工具调用 + * + * 一次性处理多个需要审批的工具调用 + */ + public void example5_multipleTools() throws Exception { + MemorySaver memorySaver = new MemorySaver(); + + ToolCallback tool1 = FunctionToolCallback.builder("tool1", (args) -> "工具1结果") + .description("工具1") + .inputType(String.class) + .build(); + + ToolCallback tool2 = FunctionToolCallback.builder("tool2", (args) -> "工具2结果") + .description("工具2") + .inputType(String.class) + .build(); + + ToolCallback tool3 = FunctionToolCallback.builder("tool3", (args) -> "工具3结果") + .description("工具3") + .inputType(String.class) + .build(); + + HumanInTheLoopHook humanInTheLoopHook = HumanInTheLoopHook.builder() + .approvalOn("tool1", ToolConfig.builder().description("工具1需要审批").build()) + .approvalOn("tool2", ToolConfig.builder().description("工具2需要审批").build()) + .approvalOn("tool3", ToolConfig.builder().description("工具3需要审批").build()) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("multi_tool_agent") + .model(chatModel) + .tools(tool1, tool2, tool3) + .hooks(List.of(humanInTheLoopHook)) + .saver(memorySaver) + .build(); + + String threadId = "multi-session-001"; + RunnableConfig config = RunnableConfig.builder() + .threadId(threadId) + .build(); + + Optional result = agent.invokeAndGetOutput("执行所有工具", config); + + if (result.isPresent() && result.get() instanceof InterruptionMetadata) { + InterruptionMetadata interruptionMetadata = (InterruptionMetadata) result.get(); + + InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder() + .nodeId(interruptionMetadata.node()) + .state(interruptionMetadata.state()); + + List feedbacks = interruptionMetadata.toolFeedbacks(); + + // 第一个工具:批准 + if (feedbacks.size() > 0) { + feedbackBuilder.addToolFeedback( + InterruptionMetadata.ToolFeedback.builder(feedbacks.get(0)) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED) + .build() + ); + } + + // 第二个工具:编辑 + if (feedbacks.size() > 1) { + feedbackBuilder.addToolFeedback( + InterruptionMetadata.ToolFeedback.builder(feedbacks.get(1)) + .arguments("{\"param\": \"new_value\"}") + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.EDITED) + .build() + ); + } + + // 第三个工具:拒绝 + if (feedbacks.size() > 2) { + feedbackBuilder.addToolFeedback( + InterruptionMetadata.ToolFeedback.builder(feedbacks.get(2)) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.REJECTED) + .description("不允许此操作") + .build() + ); + } + + InterruptionMetadata decisionsMetadata = feedbackBuilder.build(); + + RunnableConfig resumeConfig = RunnableConfig.builder() + .threadId(threadId) + .addMetadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY, decisionsMetadata) + .build(); + + Optional outputOptional = agent.invokeAndGetOutput("", resumeConfig); + + System.out.println("多个决策示例执行完成,最终状态:\n\n" + outputOptional.get().state()); + } + } + + /** + * 示例6:Workflow中嵌套ReactAgent的人工中断 + * + * 演示如何在StateGraph工作流中嵌套带有HumanInTheLoopHook的ReactAgent, + * 并处理工作流执行过程中的中断和恢复 + */ + public void example6_workflowWithHumanInTheLoop() throws Exception { + // 创建工具回调 + ToolCallback searchTool = FunctionToolCallback + .builder("search", (args) -> "搜索结果:AI Agent是能够感知环境、自主决策并采取行动的智能系统。") + .description("搜索工具,用于查找相关信息") + .inputType(String.class) + .build(); + + // 配置检查点保存器(人工介入需要检查点来处理中断) + MemorySaver saver = new MemorySaver(); + + // 创建带有人工介入Hook的ReactAgent + ReactAgent qaAgent = ReactAgent.builder() + .name("qa_agent") + .model(chatModel) + .instruction("你是一个问答专家,负责回答用户的问题。如果需要搜索信息,请使用search工具。\n用户问题:{cleaned_input}") + .outputKey("qa_result") + .saver(saver) + .hooks(HumanInTheLoopHook.builder() + .approvalOn("search", ToolConfig.builder() + .description("搜索操作需要人工审批,请确认是否执行搜索") + .build()) + .build()) + .tools(searchTool) + .enableLogging(true) + .build(); + + // 创建预处理Node:清理输入 + class PreprocessorNode implements NodeAction { + @Override + public Map apply(OverAllState state) throws Exception { + String input = state.value("input", "").toString(); + String cleaned = input.trim(); + System.out.println("预处理节点:清理输入 -> " + cleaned); + return Map.of("cleaned_input", cleaned); + } + } + + // 创建验证Node:验证结果质量 + class ValidatorNode implements NodeAction { + @Override + public Map apply(OverAllState state) throws Exception { + Optional qaResultOpt = state.value("qa_result"); + if (qaResultOpt.isPresent() && qaResultOpt.get() instanceof Message message) { + boolean isValid = message.getText().length() > 30; // 简单验证:答案长度需大于30 + System.out.println("验证节点:结果验证 -> " + (isValid ? "通过" : "不通过")); + return Map.of("is_valid", isValid); + } + return Map.of("is_valid", false); + } + } + + // 定义状态管理策略 + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("input", new ReplaceStrategy()); + strategies.put("cleaned_input", new ReplaceStrategy()); + strategies.put("qa_result", new ReplaceStrategy()); + strategies.put("is_valid", new ReplaceStrategy()); + return strategies; + }; + + // 构建工作流 + StateGraph workflow = new StateGraph(keyStrategyFactory); + + // 添加普通Node + workflow.addNode("preprocess", node_async(new PreprocessorNode())); + workflow.addNode("validate", node_async(new ValidatorNode())); + + // 添加Agent Node(嵌套的ReactAgent) + workflow.addNode(qaAgent.name(), qaAgent.asNode( + true, // includeContents: 传递父图的消息历史 + false // includeReasoning: 不返回推理过程 + )); + + // 定义流程:预处理 -> Agent处理 -> 验证 + workflow.addEdge(StateGraph.START, "preprocess"); + workflow.addEdge("preprocess", qaAgent.name()); + workflow.addEdge(qaAgent.name(), "validate"); + + // 条件边:验证通过则结束,否则重新处理 + workflow.addConditionalEdges( + "validate", + edge_async(state -> { + Boolean isValid = (Boolean) state.value("is_valid", false); + return isValid ? "end" : qaAgent.name(); + }), + Map.of( + "end", StateGraph.END, + qaAgent.name(), qaAgent.name() + ) + ); + + // 编译工作流 + CompiledGraph compiledGraph = workflow.compile( + CompileConfig.builder() + .saverConfig(SaverConfig.builder().register(saver).build()) + .build() + ); + + String threadId = "workflow-hilt-001"; + Map input = Map.of("input", "请解释量子计算的基本原理"); + + // 第一次调用 - 可能触发中断 + System.out.println("=== 第一次调用工作流:可能触发中断 ==="); + Optional nodeOutputOptional = compiledGraph.invokeAndGetOutput( + input, + RunnableConfig.builder().threadId(threadId).build() + ); + + // 检查是否发生中断 + if (nodeOutputOptional.isPresent() && nodeOutputOptional.get() instanceof InterruptionMetadata interruptionMetadata) { + System.out.println("\n工作流被中断,等待人工审核。"); + System.out.println("中断节点: " + interruptionMetadata.node()); + System.out.println("中断状态: " + interruptionMetadata.state()); + + List feedbacks = interruptionMetadata.toolFeedbacks(); + System.out.println("需要审批的工具调用数量: " + feedbacks.size()); + + // 显示所有需要审批的工具调用 + for (InterruptionMetadata.ToolFeedback feedback : feedbacks) { + System.out.println("\n工具名称: " + feedback.getName()); + System.out.println("工具参数: " + feedback.getArguments()); + System.out.println("工具描述: " + feedback.getDescription()); + } + + // 构建人工反馈(批准所有工具调用) + InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder() + .nodeId(interruptionMetadata.node()) + .state(interruptionMetadata.state()); + + // 对每个工具调用设置批准决策 + feedbacks.forEach(toolFeedback -> { + feedbackBuilder.addToolFeedback( + InterruptionMetadata.ToolFeedback.builder(toolFeedback) + .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED) + .build() + ); + }); + + InterruptionMetadata approvalMetadata = feedbackBuilder.build(); + + // 使用批准决策恢复执行 + System.out.println("\n=== 第二次调用:使用批准决策恢复工作流 ==="); + RunnableConfig resumableConfig = RunnableConfig.builder() + .threadId(threadId) + .addHumanFeedback(approvalMetadata) + .build(); + + nodeOutputOptional = compiledGraph.invokeAndGetOutput(Map.of(), resumableConfig); + System.out.println("\n工作流中嵌套ReactAgent的人工中断示例执行完成"); + + } + + } + + /** + * 运行所有示例 + */ + public void runAllExamples() { + System.out.println("=== 人工介入(Human-in-the-Loop)示例 ===\n"); + + try { +// System.out.println("示例1: 配置中断和基本使用"); +// example1_basicConfiguration(); +// System.out.println(); +// +// System.out.println("示例2: 批准(approve)决策"); +// example2_approveDecision(); +// System.out.println(); +// +// System.out.println("示例3: 编辑(edit)决策"); +// example3_editDecision(); +// System.out.println(); +// +// System.out.println("示例4: 拒绝(reject)决策"); +// example4_rejectDecision(); +// System.out.println(); +// +// System.out.println("示例5: 处理多个工具调用决策"); +// example5_multipleTools(); +// System.out.println(); + + // System.out.println("示例6: Workflow中嵌套ReactAgent的人工中断"); + example6_workflowWithHumanInTheLoop(); + // System.out.println(); + + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/MemoryExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/MemoryExample.java new file mode 100644 index 00000000..737cb8f0 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/MemoryExample.java @@ -0,0 +1,661 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.advanced; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; +import com.alibaba.cloud.ai.graph.agent.hook.HookPositions; +import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand; +import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesModelHook; +import com.alibaba.cloud.ai.graph.agent.hook.messages.UpdatePolicy; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; +import com.alibaba.cloud.ai.graph.store.Store; +import com.alibaba.cloud.ai.graph.store.StoreItem; +import com.alibaba.cloud.ai.graph.store.stores.MemoryStore; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; + +import java.util.*; +import java.util.function.BiFunction; + +/** + * 记忆管理(Memory)示例 + * + * 演示如何在Agent中使用记忆管理功能,包括: + * 1. 在工具中读取长期记忆 + * 2. 在工具中写入长期记忆 + * 3. 使用ModelHook管理长期记忆 + * 4. 结合短期和长期记忆 + * 5. 跨会话记忆 + * 6. 用户偏好学习 + * + * 参考文档: advanced_doc/memory.md + */ +public class MemoryExample { + + private final ChatModel chatModel; + + public MemoryExample(ChatModel chatModel) { + this.chatModel = chatModel; + } + + /** + * Main方法:运行所有示例 + * + * 注意:需要配置ChatModel实例才能运行 + */ + public static void main(String[] args) { + // 创建 DashScope API 实例 + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + // 创建 ChatModel + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + if (chatModel == null) { + System.err.println("错误:请先配置ChatModel实例"); + System.err.println("请设置 AI_DASHSCOPE_API_KEY 环境变量"); + return; + } + + // 创建示例实例 + MemoryExample example = new MemoryExample(chatModel); + + // 运行所有示例 + example.runAllExamples(); + } + + private static void mockInsertToStore(MemoryStore store) { + // 向存储中写入示例数据 + Map userData = new HashMap<>(); + userData.put("name", "张三"); + userData.put("language", "中文"); + + StoreItem userItem = StoreItem.of(List.of("users"), "user_123", userData); + store.putItem(userItem); + } + + /** + * 示例1:在工具中读取长期记忆 + * + * 创建一个工具,让Agent能够查询用户信息 + */ + public void example1_readMemoryInTool() throws GraphRunnerException { + // 定义请求和响应记录 + record GetMemoryRequest(List namespace, String key) { } + record MemoryResponse(String message, Map value) { } + + // 创建获取用户信息的工具 + BiFunction getUserInfoFunction = + (request, context) -> { + RunnableConfig runnableConfig = (RunnableConfig) context.getContext().get("config"); + Store store = runnableConfig.store(); + Optional itemOpt = store.getItem(request.namespace(), request.key()); + if (itemOpt.isPresent()) { + Map value = itemOpt.get().getValue(); + return new MemoryResponse("找到用户信息", value); + } + return new MemoryResponse("未找到用户", Map.of()); + }; + + ToolCallback getUserInfoTool = FunctionToolCallback.builder("getUserInfo", getUserInfoFunction) + .description("查询用户信息") + .inputType(GetMemoryRequest.class) + .build(); + + // 创建Agent + ReactAgent agent = ReactAgent.builder() + .name("memory_agent") + .model(chatModel) + .tools(getUserInfoTool) + .saver(new MemorySaver()) + .build(); + + + // 创建内存存储 + MemoryStore store = new MemoryStore(); + // 在Store中放入模拟数据,实际应用中,存储可能是其他流程中生成 + mockInsertToStore(store); + // 运行Agent + RunnableConfig config = RunnableConfig.builder() + .threadId("session_001") + .addMetadata("user_id", "user_123") + .store(store) + .build(); + + agent.invoke("查询用户信息,namespace=['users'], key='user_123'", config); + + System.out.println("工具读取长期记忆示例执行完成"); + } + + /** + * 示例2:在工具中写入长期记忆 + * + * 创建一个更新用户信息的工具 + */ + public void example2_writeMemoryInTool() throws GraphRunnerException { + // 定义请求记录 + record SaveMemoryRequest(List namespace, String key, Map value) { } + record MemoryResponse(String message, Map value) { } + + // 创建保存用户信息的工具 + BiFunction saveUserInfoFunction = + (request, context) -> { + RunnableConfig runnableConfig = (RunnableConfig) context.getContext().get("config"); + Store store = runnableConfig.store(); + StoreItem item = StoreItem.of(request.namespace(), request.key(), request.value()); + store.putItem(item); + return new MemoryResponse("成功保存用户信息", request.value()); + }; + + ToolCallback saveUserInfoTool = FunctionToolCallback.builder("saveUserInfo", saveUserInfoFunction) + .description("保存用户信息") + .inputType(SaveMemoryRequest.class) + .build(); + + // 创建Agent + ReactAgent agent = ReactAgent.builder() + .name("save_memory_agent") + .model(chatModel) + .tools(saveUserInfoTool) + .saver(new MemorySaver()) + .build(); + + // 创建内存存储 + MemoryStore store = new MemoryStore(); + RunnableConfig config = RunnableConfig.builder() + .threadId("session_001") + .addMetadata("user_id", "user_123") + .store(store) + .build(); + // 运行Agent + agent.invoke( + "我叫张三,请保存我的信息。使用 saveUserInfo 工具,namespace=['users'], key='user_123', value={'name': '张三'}", + config + ); + + // 可以直接访问存储获取值 + Optional savedItem = store.getItem(List.of("users"), "user_123"); + if (savedItem.isPresent()) { + Map savedValue = savedItem.get().getValue(); + System.out.println("保存的数据: " + savedValue); + } + + System.out.println("工具写入长期记忆示例执行完成"); + } + + /** + * 示例3:使用MessagesModelHook管理长期记忆 + * + * 在模型调用前后自动加载和保存长期记忆 + */ + public void example3_memoryWithModelHook() throws GraphRunnerException { + // 创建记忆拦截器 + @HookPositions({HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL}) + class MemoryInterceptor extends MessagesModelHook { + @Override + public String getName() { + return "memory_interceptor"; + } + + @Override + public AgentCommand beforeModel(List previousMessages, RunnableConfig config) { + // 从配置中获取用户ID + String userId = (String) config.metadata("user_id").orElse(null); + if (userId == null) { + return new AgentCommand(previousMessages); + } + + Store store = config.store(); + // 从记忆存储中加载用户画像 + Optional itemOpt = store.getItem(List.of("user_profiles"), userId); + if (itemOpt.isPresent()) { + Map profile = itemOpt.get().getValue(); + + // 将用户上下文注入系统消息 + String userContext = String.format( + "用户信息:姓名=%s, 年龄=%s, 邮箱=%s, 偏好=%s", + profile.get("name"), + profile.get("age"), + profile.get("email"), + profile.get("preferences") + ); + + // 查找是否已存在 SystemMessage + SystemMessage existingSystemMessage = null; + int systemMessageIndex = -1; + for (int i = 0; i < previousMessages.size(); i++) { + Message msg = previousMessages.get(i); + if (msg instanceof SystemMessage) { + existingSystemMessage = (SystemMessage) msg; + systemMessageIndex = i; + break; + } + } + + // 如果找到 SystemMessage,更新它;否则创建新的 + SystemMessage enhancedSystemMessage; + if (existingSystemMessage != null) { + // 更新现有的 SystemMessage + enhancedSystemMessage = new SystemMessage( + existingSystemMessage.getText() + "\n\n" + userContext + ); + } + else { + // 创建新的 SystemMessage + enhancedSystemMessage = new SystemMessage(userContext); + } + + // 构建新的消息列表 + List newMessages = new ArrayList<>(); + if (systemMessageIndex >= 0) { + // 如果找到了 SystemMessage,替换它 + for (int i = 0; i < previousMessages.size(); i++) { + if (i == systemMessageIndex) { + newMessages.add(enhancedSystemMessage); + } + else { + newMessages.add(previousMessages.get(i)); + } + } + } + else { + // 如果没有找到 SystemMessage,在开头添加新的 + newMessages.add(enhancedSystemMessage); + newMessages.addAll(previousMessages); + } + + // 使用 REPLACE 策略替换所有消息 + return new AgentCommand(newMessages, UpdatePolicy.REPLACE); + } + + return new AgentCommand(previousMessages); + } + + @Override + public AgentCommand afterModel(List previousMessages, RunnableConfig config) { + // 可以在这里实现对话后的记忆保存逻辑 + // 不修改消息,返回原始消息 + return new AgentCommand(previousMessages); + } + } + + MessagesModelHook memoryInterceptor = new MemoryInterceptor(); + + // 创建带有记忆拦截器的Agent + ReactAgent agent = ReactAgent.builder() + .name("memory_agent") + .model(chatModel) + .hooks(memoryInterceptor) + .saver(new MemorySaver()) + .build(); + + + // 创建内存存储 + MemoryStore memoryStore = new MemoryStore(); + + // 模拟数据,预先填充用户画像 + Map profileData = new HashMap<>(); + profileData.put("name", "王小明"); + profileData.put("age", 28); + profileData.put("email", "wang@example.com"); + profileData.put("preferences", List.of("喜欢咖啡", "喜欢阅读")); + + StoreItem profileItem = StoreItem.of(List.of("user_profiles"), "user_001", profileData); + memoryStore.putItem(profileItem); + RunnableConfig config = RunnableConfig.builder() + .threadId("session_001") + .addMetadata("user_id", "user_001") + .store(memoryStore) + .build(); + + // Agent会自动加载用户画像信息 + agent.invoke("请介绍一下我的信息。", config); + + System.out.println("ModelHook管理长期记忆示例执行完成"); + } + + /** + * 示例4:结合短期和长期记忆 + * + * 短期记忆用于存储对话上下文,长期记忆用于存储持久化数据 + * 使用MessagesModelHook实现 + */ + public void example4_combinedMemory() throws GraphRunnerException { + // 创建组合记忆Hook + @HookPositions({HookPosition.BEFORE_MODEL}) + class CombinedMemoryHook extends MessagesModelHook { + @Override + public String getName() { + return "combined_memory"; + } + + @Override + public AgentCommand beforeModel(List previousMessages, RunnableConfig config) { + Optional userIdOpt = config.metadata("user_id"); + if (userIdOpt.isEmpty()) { + return new AgentCommand(previousMessages); + } + String userId = (String) userIdOpt.get(); + + Store memoryStore = config.store(); + // 从长期记忆加载 + Optional profileOpt = memoryStore.getItem(List.of("profiles"), userId); + if (profileOpt.isEmpty()) { + return new AgentCommand(previousMessages); + } + + Map profile = profileOpt.get().getValue(); + String contextInfo = String.format("长期记忆:用户 %s, 职业: %s", + profile.get("name"), profile.get("occupation")); + + // 查找是否已存在 SystemMessage + SystemMessage existingSystemMessage = null; + int systemMessageIndex = -1; + for (int i = 0; i < previousMessages.size(); i++) { + Message msg = previousMessages.get(i); + if (msg instanceof SystemMessage) { + existingSystemMessage = (SystemMessage) msg; + systemMessageIndex = i; + break; + } + } + + // 如果找到 SystemMessage,更新它;否则创建新的 + SystemMessage enhancedSystemMessage; + if (existingSystemMessage != null) { + // 更新现有的 SystemMessage + enhancedSystemMessage = new SystemMessage( + existingSystemMessage.getText() + "\n\n" + contextInfo + ); + } + else { + // 创建新的 SystemMessage + enhancedSystemMessage = new SystemMessage(contextInfo); + } + + // 构建新的消息列表 + List newMessages = new ArrayList<>(); + if (systemMessageIndex >= 0) { + // 如果找到了 SystemMessage,替换它 + for (int i = 0; i < previousMessages.size(); i++) { + if (i == systemMessageIndex) { + newMessages.add(enhancedSystemMessage); + } + else { + newMessages.add(previousMessages.get(i)); + } + } + } + else { + // 如果没有找到 SystemMessage,在开头添加新的 + newMessages.add(enhancedSystemMessage); + newMessages.addAll(previousMessages); + } + + // 使用 REPLACE 策略替换所有消息 + return new AgentCommand(newMessages, UpdatePolicy.REPLACE); + } + } + + MessagesModelHook combinedMemoryHook = new CombinedMemoryHook(); + + // 创建Agent + ReactAgent agent = ReactAgent.builder() + .name("combined_memory_agent") + .model(chatModel) + .hooks(combinedMemoryHook) + .saver(new MemorySaver()) // 短期记忆 + .build(); + + // 创建记忆存储 + MemoryStore memoryStore = new MemoryStore(); + // 设置长期记忆 + Map userProfile = new HashMap<>(); + userProfile.put("name", "李工程师"); + userProfile.put("occupation", "软件工程师"); + StoreItem profileItem = StoreItem.of(List.of("profiles"), "user_002", userProfile); + memoryStore.putItem(profileItem); + + RunnableConfig config = RunnableConfig.builder() + .threadId("combined_thread") + .addMetadata("user_id", "user_002") + .store(memoryStore) + .build(); + + // 短期记忆:在对话中记住 + agent.invoke("我今天在做一个 Spring 项目。", config); + + // 提出需要同时使用两种记忆的问题 + agent.invoke("根据我的职业和今天的工作,给我一些建议。", config); + // 响应会同时使用长期记忆(职业)和短期记忆(Spring项目) + + System.out.println("结合短期和长期记忆示例执行完成"); + } + + /** + * 示例5:跨会话记忆 + * + * 同一用户在不同会话中应该能够访问相同的长期记忆 + */ + public void example5_crossSessionMemory() throws GraphRunnerException { + record SaveMemoryRequest(List namespace, String key, Map value) { } + record GetMemoryRequest(List namespace, String key) { } + record MemoryResponse(String message, Map value) { } + + + ToolCallback saveMemoryTool = FunctionToolCallback.builder("saveMemory", + (BiFunction) (request, context) -> { + StoreItem item = StoreItem.of(request.namespace(), request.key(), request.value()); + RunnableConfig runnableConfig = (RunnableConfig) context.getContext().get("config"); + Store memoryStore = runnableConfig.store(); + memoryStore.putItem(item); + return new MemoryResponse("已保存", request.value()); + }) + .description("保存到长期记忆") + .inputType(SaveMemoryRequest.class) + .build(); + + ToolCallback getMemoryTool = FunctionToolCallback.builder("getMemory", + (BiFunction) (request, context) -> { + RunnableConfig runnableConfig = (RunnableConfig) context.getContext().get("config"); + Store memoryStore = runnableConfig.store(); + Optional itemOpt = memoryStore.getItem(request.namespace(), request.key()); + return new MemoryResponse( + itemOpt.isPresent() ? "找到" : "未找到", + itemOpt.map(StoreItem::getValue).orElse(Map.of()) + ); + }) + .description("从长期记忆获取") + .inputType(GetMemoryRequest.class) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("session_agent") + .model(chatModel) + .tools(saveMemoryTool, getMemoryTool) + .saver(new MemorySaver()) + .build(); + + // 创建记忆存储和工具 + MemoryStore memoryStore = new MemoryStore(); + // 会话1:保存信息 + RunnableConfig session1 = RunnableConfig.builder() + .threadId("session_morning") + .addMetadata("user_id", "user_003") + .store(memoryStore) + .build(); + + agent.invoke( + "记住我的密码是 secret123。用 saveMemory 保存,namespace=['credentials'], key='user_003_password', value={'password': 'secret123'}。", + session1 + ); + + // 会话2:检索信息(不同的线程,同一用户) + RunnableConfig session2 = RunnableConfig.builder() + .threadId("session_afternoon") + .addMetadata("user_id", "user_003") + .store(memoryStore) + .build(); + + agent.invoke( + "我的密码是什么?用 getMemory 获取,namespace=['credentials'], key='user_003_password'。", + session2 + ); + // 长期记忆在不同会话间持久化 + + System.out.println("跨会话记忆示例执行完成"); + } + + /** + * 示例6:用户偏好学习 + * + * Agent可以随着时间的推移学习并存储用户偏好 + * 使用MessagesModelHook实现 + */ + public void example6_preferLearning() throws GraphRunnerException { + MemoryStore memoryStore = new MemoryStore(); + + @HookPositions({HookPosition.AFTER_MODEL}) + class PreferenceLearningHook extends MessagesModelHook { + private final MemoryStore store; + + public PreferenceLearningHook(MemoryStore store) { + this.store = store; + } + + @Override + public String getName() { + return "preference_learning"; + } + + @Override + public AgentCommand afterModel(List previousMessages, RunnableConfig config) { + String userId = (String) config.metadata("user_id").orElse(null); + if (userId == null) { + return new AgentCommand(previousMessages); + } + + // 提取用户输入 + if (previousMessages.isEmpty()) { + return new AgentCommand(previousMessages); + } + + // 加载现有偏好 + Optional prefsOpt = store.getItem(List.of("user_data"), userId + "_preferences"); + List prefs = new ArrayList<>(); + if (prefsOpt.isPresent()) { + Map prefsData = prefsOpt.get().getValue(); + prefs = (List) prefsData.getOrDefault("items", new ArrayList<>()); + } + + // 简单的偏好提取(实际应用中使用NLP) + for (Message msg : previousMessages) { + String content = msg.getText().toLowerCase(); + if (content.contains("喜欢") || content.contains("偏好")) { + prefs.add(msg.getText()); + + Map prefsData = new HashMap<>(); + prefsData.put("items", prefs); + StoreItem item = StoreItem.of(List.of("user_data"), userId + "_preferences", prefsData); + store.putItem(item); + + System.out.println("学习到用户偏好 " + userId + ": " + msg.getText()); + } + } + + // 不修改消息,返回原始消息 + return new AgentCommand(previousMessages); + } + } + + MessagesModelHook preferenceLearningHook = new PreferenceLearningHook(memoryStore); + + ReactAgent agent = ReactAgent.builder() + .name("learning_agent") + .model(chatModel) + .hooks(preferenceLearningHook) + .saver(new MemorySaver()) + .build(); + + RunnableConfig config = RunnableConfig.builder() + .threadId("learning_thread") + .addMetadata("user_id", "user_004") + .build(); + + // 用户表达偏好 + agent.invoke("我喜欢喝绿茶。", config); + agent.invoke("我偏好早上运动。", config); + + // 验证偏好已被存储 + Optional savedPrefs = memoryStore.getItem(List.of("user_data"), "user_004_preferences"); + if (savedPrefs.isPresent()) { + System.out.println("已保存的偏好: " + savedPrefs.get().getValue()); + } + + System.out.println("用户偏好学习示例执行完成"); + } + + /** + * 运行所有示例 + */ + public void runAllExamples() { + System.out.println("=== 记忆管理(Memory)示例 ===\n"); + + try { + System.out.println("示例1: 在工具中读取长期记忆"); + example1_readMemoryInTool(); + System.out.println(); + // + // System.out.println("示例2: 在工具中写入长期记忆"); + // example2_writeMemoryInTool(); + // System.out.println(); + // + // System.out.println("示例3: 使用ModelHook管理长期记忆"); + // example3_memoryWithModelHook(); + // System.out.println(); + // + // System.out.println("示例4: 结合短期和长期记忆"); + // example4_combinedMemory(); + // System.out.println(); + // + // System.out.println("示例5: 跨会话记忆"); + // example5_crossSessionMemory(); + // System.out.println(); + // + // System.out.println("示例6: 用户偏好学习"); + // example6_preferLearning(); + // System.out.println(); + + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/MultiAgentExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/MultiAgentExample.java new file mode 100644 index 00000000..7e741aa9 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/MultiAgentExample.java @@ -0,0 +1,936 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.advanced; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.GraphResponse; +import com.alibaba.cloud.ai.graph.GraphRepresentation; +import com.alibaba.cloud.ai.graph.NodeOutput; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.flow.agent.LlmRoutingAgent; +import com.alibaba.cloud.ai.graph.agent.flow.agent.ParallelAgent; +import com.alibaba.cloud.ai.graph.agent.flow.agent.SequentialAgent; +import com.alibaba.cloud.ai.graph.agent.flow.agent.SupervisorAgent; +import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; + +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.model.ChatModel; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; + +import reactor.core.publisher.Flux; + +/** + * 多智能体(Multi-agent)示例 + * + * 演示不同的 Multi-agent 协作模式,包括: + * 1. 顺序执行(Sequential Agent) + * 2. 并行执行(Parallel Agent) + * 3. LLM路由(LlmRoutingAgent) + * 4. 自定义合并策略 + * 5. 监督者模式(SupervisorAgent) + * + * 参考文档: advanced_doc/multi-agent.md + */ +public class MultiAgentExample { + + private final ChatModel chatModel; + + public MultiAgentExample(ChatModel chatModel) { + this.chatModel = chatModel; + } + + /** + * Main方法:运行所有示例 + * + * 注意:需要配置ChatModel实例才能运行 + */ + public static void main(String[] args) { + // 创建 DashScope API 实例 + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + // 创建 ChatModel + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + if (chatModel == null) { + System.err.println("错误:请先配置ChatModel实例"); + System.err.println("请设置 AI_DASHSCOPE_API_KEY 环境变量"); + return; + } + + // 创建示例实例 + MultiAgentExample example = new MultiAgentExample(chatModel); + + // 运行所有示例 + example.runAllExamples(); + } + + /** + * 示例1:顺序执行(Sequential Agent) + * + * 多个Agent按预定义的顺序依次执行,每个Agent的输出成为下一个Agent的输入 + */ + public void example1_sequentialAgent() throws Exception { + // 创建专业化的子Agent + ReactAgent writerAgent = ReactAgent.builder() + .name("writer_agent") + .model(chatModel) + .description("专业写作Agent") + .instruction("你是一个知名的作家,擅长写作和创作。请根据用户的提问进行回答:{input}。") + .outputKey("article") + .build(); + + ReactAgent reviewerAgent = ReactAgent.builder() + .name("reviewer_agent") + .model(chatModel) + .description("专业评审Agent") + .instruction("你是一个知名的评论家,擅长对文章进行评论和修改。" + + "对于散文类文章,请确保文章中必须包含对于西湖风景的描述。待评论文章:\n\n {article}" + + "最终只返回修改后的文章,不要包含任何评论信息。") + .outputKey("reviewed_article") + .build(); + + // 创建顺序Agent + SequentialAgent blogAgent = SequentialAgent.builder() + .name("blog_agent") + .description("根据用户给定的主题写一篇文章,然后将文章交给评论员进行评论") + .subAgents(List.of(writerAgent, reviewerAgent)) + .build(); + + // 使用 + Optional result = blogAgent.invoke("帮我写一个100字左右的散文"); + + if (result.isPresent()) { + OverAllState state = result.get(); + + // 访问第一个Agent的输出 + state.value("article").ifPresent(article -> { + if (article instanceof AssistantMessage) { + System.out.println("原始文章: " + ((AssistantMessage) article).getText()); + } + }); + + // 访问第二个Agent的输出 + state.value("reviewed_article").ifPresent(reviewedArticle -> { + if (reviewedArticle instanceof AssistantMessage) { + System.out.println("评审后文章: " + ((AssistantMessage) reviewedArticle).getText()); + } + }); + } + } + + /** + * 示例2:控制推理内容 + * + * 使用 returnReasoningContents 控制是否在消息历史中包含中间推理 + */ + public void example2_controlReasoningContents() throws Exception { + ReactAgent writerAgent = ReactAgent.builder() + .name("writer_agent") + .model(chatModel) + .returnReasoningContents(true) // 返回推理过程 + .outputKey("article") + .build(); + + ReactAgent reviewerAgent = ReactAgent.builder() + .name("reviewer_agent") + .model(chatModel) + .instruction("请对文章进行评审修正:\n{article},最终返回评审修正后的文章内容") + .includeContents(true) // 包含上一个Agent的推理内容 + .returnReasoningContents(true) // 返回推理过程 + .outputKey("reviewed_article") + .build(); + + + // 每个子agent的推理内容,下一个执行的子agent会看到上一个子agent的推理内容 + SequentialAgent blogAgent = SequentialAgent.builder() + .name("blog_agent") + .subAgents(List.of(writerAgent, reviewerAgent)) + .build(); + + Optional result = blogAgent.invoke("帮我写一个100字左右的散文"); + + if (result.isPresent()) { + // 消息历史将包含所有工具调用和推理过程 + List messages = (List) result.get().value("messages").orElse(List.of()); + System.out.println("消息数量: " + messages.size()); // 包含所有中间步骤 + } + } + + /** + * 示例3:并行执行(Parallel Agent) + * + * 多个Agent同时处理相同的输入,它们的结果被收集并合并 + */ + public void example3_parallelAgent() throws Exception { + // 创建多个专业化Agent + ReactAgent proseWriterAgent = ReactAgent.builder() + .name("prose_writer_agent") + .model(chatModel) + .description("专门写散文的AI助手") + .instruction("你是一个知名的散文作家,擅长写优美的散文。" + + "用户会给你一个主题:{input},你只需要创作一篇100字左右的散文。") + .outputKey("prose_result") + .enableLogging(true) + .build(); + + ReactAgent poemWriterAgent = ReactAgent.builder() + .name("poem_writer_agent") + .model(chatModel) + .description("专门写现代诗的AI助手") + .instruction("你是一个知名的现代诗人,擅长写现代诗。" + + "用户会给你的主题是:{input},你只需要创作一首现代诗。") + .outputKey("poem_result") + .enableLogging(true) + .build(); + + ReactAgent summaryAgent = ReactAgent.builder() + .name("summary_agent") + .model(chatModel) + .description("专门做内容总结的AI助手") + .instruction("你是一个专业的内容分析师,擅长对主题进行总结和提炼。" + + "用户会给你一个主题:{input},你只需要对这个主题进行简要总结。") + .outputKey("summary_result") + .enableLogging(true) + .build(); + + // 创建并行Agent + ParallelAgent parallelAgent = ParallelAgent.builder() + .name("parallel_creative_agent") + .description("并行执行多个创作任务,包括写散文、写诗和做总结") + .mergeOutputKey("merged_results") + .subAgents(List.of(proseWriterAgent, poemWriterAgent, summaryAgent)) + .mergeStrategy(new ParallelAgent.DefaultMergeStrategy()) + .build(); + + ExecutorService executorService = Executors.newFixedThreadPool(3); + // 使用 + Flux flux = parallelAgent.stream("以'西湖'为主题", RunnableConfig.builder().addParallelNodeExecutor("parallel_creative_agent", executorService).build()); + + AtomicReference lastOutput = new AtomicReference<>(); + flux.doOnNext(nodeOutput -> { + System.out.println("节点输出: " + nodeOutput); + lastOutput.set(nodeOutput); + }).doOnError(error -> { + System.err.println("执行出错: " + error.getMessage()); + }).doOnComplete(() -> { + System.out.println("并行Agent流式执行完成\n\n"); + + NodeOutput output = lastOutput.get(); + if (output == null) { + System.out.println("未收到任何输出,无法展示结果。"); + return; + } + + OverAllState state = output.state(); + // 访问各个Agent的输出 + state.value("prose_result").ifPresent(r -> + System.out.println("散文: " + r)); + state.value("poem_result").ifPresent(r -> + System.out.println("诗歌: " + r)); + state.value("summary_result").ifPresent(r -> + System.out.println("总结: " + r)); + + // 访问合并后的结果 + state.value("merged_results").ifPresent(r -> + System.out.println("合并结果: " + r)); + }).blockLast(); + + } + + /** + * 示例4:自定义合并策略 + * + * 实现自定义的合并策略来控制如何组合多个Agent的输出 + */ + public void example4_customMergeStrategy() throws Exception { + // 自定义合并策略 + class CustomMergeStrategy implements ParallelAgent.MergeStrategy { + @Override + public Map merge(Map mergedState, OverAllState state) { + // 从每个Agent的状态中提取输出 + state.data().forEach((key, value) -> { + // 检查key不为null且以"_result"结尾 + if (key != null && key.endsWith("_result")) { + String resultText = ""; + if (value instanceof GraphResponse graphResponse) { + if (graphResponse.resultValue().isPresent()) { + resultText = graphResponse.resultValue().get().toString(); + } + } else if (value != null) { + resultText = value.toString(); + } + Object existing = mergedState.get("all_results"); + if (existing == null) { + mergedState.put("all_results", resultText); + } + else { + mergedState.put("all_results", existing + "\n\n---\n\n" + resultText); + } + } + }); + return mergedState; + } + } + + // 创建Agent + ReactAgent agent1 = ReactAgent.builder() + .name("agent1") + .model(chatModel) + .outputKey("agent1_result") + .build(); + + ReactAgent agent2 = ReactAgent.builder() + .name("agent2") + .model(chatModel) + .outputKey("agent2_result") + .build(); + + ReactAgent agent3 = ReactAgent.builder() + .name("agent3") + .model(chatModel) + .outputKey("agent3_result") + .build(); + + // 使用自定义合并策略 + ParallelAgent parallelAgent = ParallelAgent.builder() + .name("parallel_agent") + .subAgents(List.of(agent1, agent2, agent3)) + .mergeStrategy(new CustomMergeStrategy()) + .mergeOutputKey("all_results") + .build(); + + Optional result = parallelAgent.invoke("分析这个主题"); + + if (result.isPresent()) { + OverAllState state = result.get(); + state.value("all_results").ifPresent(mergeResult -> { + System.out.println("合并结果: " + mergeResult); + }); + System.out.println("自定义合并策略示例执行成功"); + } + } + + /** + * 示例5:LLM路由(LlmRoutingAgent) + * + * 使用大语言模型动态决定将请求路由到哪个子Agent + */ + public void example5_llmRoutingAgent() throws Exception { + // 创建专业化的子Agent + ReactAgent writerAgent = ReactAgent.builder() + .name("writer_agent") + .model(chatModel) + .description("擅长创作各类文章,包括散文、诗歌等文学作品") + .instruction("你是一个知名的作家,擅长写作和创作。请根据用户的提问进行回答。") + .outputKey("writer_output") + .build(); + + ReactAgent reviewerAgent = ReactAgent.builder() + .name("reviewer_agent") + .model(chatModel) + .description("擅长对文章进行评论、修改和润色") + .instruction("你是一个知名的评论家,擅长对文章进行评论和修改。" + + "对于散文类文章,请确保文章中必须包含对于西湖风景的描述。") + .outputKey("reviewer_output") + .build(); + + ReactAgent translatorAgent = ReactAgent.builder() + .name("translator_agent") + .model(chatModel) + .description("擅长将文章翻译成各种语言") + .instruction("你是一个专业的翻译家,能够准确地将文章翻译成目标语言。") + .outputKey("translator_output") + .build(); + + // 创建路由Agent + LlmRoutingAgent routingAgent = LlmRoutingAgent.builder() + .name("content_routing_agent") + .description("根据用户需求智能路由到合适的专家Agent") + .model(chatModel) + .subAgents(List.of(writerAgent, reviewerAgent, translatorAgent)) + .build(); + + // 使用 - LLM会自动选择最合适的Agent + System.out.println("路由测试1: 写作请求"); + Optional result1 = routingAgent.invoke("帮我写一篇关于春天的散文"); + // LLM会路由到 writerAgent + + System.out.println("路由测试2: 修改请求"); + Optional result2 = routingAgent.invoke("请帮我修改这篇文章:春天来了,花开了。"); + // LLM会路由到 reviewerAgent + + System.out.println("路由测试3: 翻译请求"); + Optional result3 = routingAgent.invoke("请将以下内容翻译成英文:春暖花开"); + // LLM会路由到 translatorAgent + + System.out.println("LLM路由示例执行完成"); + } + + /** + * 示例6:优化路由准确性 + * + * 通过提供清晰明确的Agent描述来提高路由的准确性 + */ + public void example6_optimizedRouting() throws Exception { + // 1. 提供清晰明确的Agent描述 + ReactAgent codeAgent = ReactAgent.builder() + .name("code_agent") + .model(chatModel) + .description("专门处理编程相关问题,包括代码编写、调试、重构和优化。" + + "擅长Java、Python、JavaScript等主流编程语言。") + .instruction("你是一个资深的软件工程师...") + .build(); + + // 2. 明确Agent的职责边界 + ReactAgent businessAgent = ReactAgent.builder() + .name("business_agent") + .model(chatModel) + .description("专门处理商业分析、市场研究和战略规划问题。" + + "不处理技术实现细节。") + .instruction("你是一个资深的商业分析师...") + .build(); + + ReactAgent writerAgent = ReactAgent.builder() + .name("writer_agent") + .model(chatModel) + .description("专门处理内容创作,包括文章、报告、文案等写作任务。") + .instruction("你是一个专业作家...") + .build(); + + // 3. 使用不同领域的Agent避免重叠 + LlmRoutingAgent routingAgent = LlmRoutingAgent.builder() + .name("multi_domain_router") + .model(chatModel) + .subAgents(List.of(codeAgent, businessAgent, writerAgent)) + .build(); + + // 测试路由 + routingAgent.invoke("如何用Java实现单例模式?"); + routingAgent.invoke("分析一下这个市场的竞争态势"); + routingAgent.invoke("写一篇产品介绍文案"); + + System.out.println("优化路由示例执行完成"); + } + + /** + * 示例7:混合模式 - 结合顺序、并行和路由 + * + * 组合不同的模式创建复杂的工作流 + */ + public void example7_hybridPattern() throws Exception { + // 创建研究Agent(并行执行) + ReactAgent webResearchAgent = ReactAgent.builder() + .name("web_research") + .model(chatModel) + .description("从互联网搜索信息") + .instruction("请搜索并收集关于以下主题的信息:{input}") + .outputKey("web_data") + .build(); + + ReactAgent dbResearchAgent = ReactAgent.builder() + .name("db_research") + .model(chatModel) + .description("从数据库查询信息") + .instruction("请从数据库中查询并收集关于以下主题的信息:{input}") + .outputKey("db_data") + .build(); + + ParallelAgent researchAgent = ParallelAgent.builder() + .name("parallel_research") + .description("并行收集多个数据源的信息") + .subAgents(List.of(webResearchAgent, dbResearchAgent)) + .mergeOutputKey("research_data") + .build(); + + // 创建分析Agent + ReactAgent analysisAgent = ReactAgent.builder() + .name("analysis_agent") + .model(chatModel) + .description("分析研究数据") + .instruction("请分析以下收集到的数据并提供见解:{research_data}") + .outputKey("analysis_result") + .build(); + + // 创建报告Agent(路由选择格式) + ReactAgent pdfReportAgent = ReactAgent.builder() + .name("pdf_report") + .model(chatModel) + .description("生成PDF格式报告") + .instruction(""" + 请根据研究结果和分析结果生成一份PDF格式的报告。 + + 研究结果:{research_data} + 分析结果:{analysis_result} + """) + .outputKey("pdf_report") + .build(); + + ReactAgent htmlReportAgent = ReactAgent.builder() + .name("html_report") + .model(chatModel) + .description("生成HTML格式报告") + .instruction(""" + 请根据研究结果和分析结果生成一份HTML格式的报告。 + + 研究结果:{research_data} + 分析结果:{analysis_result} + """) + .outputKey("html_report") + .build(); + + LlmRoutingAgent reportAgent = LlmRoutingAgent.builder() + .name("report_router") + .description("根据需求选择报告格式") + .model(chatModel) + .subAgents(List.of(pdfReportAgent, htmlReportAgent)) + .build(); + + // 组合成顺序工作流 + SequentialAgent hybridWorkflow = SequentialAgent.builder() + .name("research_workflow") + .description("完整的研究工作流:并行收集 -> 分析 -> 路由生成报告") + .subAgents(List.of(researchAgent, analysisAgent, reportAgent)) + .build(); + + + // 打印工作流图表 + System.out.println("\n=== 混合模式工作流图表 ==="); + printGraphRepresentation(hybridWorkflow); + System.out.println("=========================\n"); + + Optional result = hybridWorkflow.invoke("研究AI技术趋势并生成HTML报告"); + + if (result.isPresent()) { + System.out.println("混合模式示例执行成功"); + } + } + + /** + * 示例8:监督者模式(SupervisorAgent) + * + * SupervisorAgent 与 LlmRoutingAgent 类似,但有以下关键区别: + * 1. 子Agent处理完成后会返回到Supervisor,而不是直接结束 + * 2. Supervisor可以决定继续路由到其他子Agent,或者标记任务完成(FINISH) + * 3. 支持嵌套Agent(如SequentialAgent、ParallelAgent)作为子Agent + * + * 这个示例展示了如何使用SupervisorAgent管理包含普通ReactAgent和嵌套SequentialAgent的复杂工作流 + */ + public void example8_supervisorAgent() throws Exception { + // 定义专业的监督者指令(如果不定义,则使用系统默认的提示词) + final String SUPERVISOR_INSTRUCTION = """ + 你是一个智能的内容管理监督者,负责协调和管理多个专业Agent来完成用户的内容处理需求。 + + ## 你的职责 + 1. 分析用户需求,将其分解为合适的子任务 + 2. 根据任务特性,选择合适的Agent进行处理 + 3. 监控任务执行状态,决定是否需要继续处理或完成任务 + 4. 当所有任务完成时,返回FINISH结束流程 + + ## 可用的子Agent及其职责 + + ### writer_agent + - **功能**: 擅长创作各类文章,包括散文、诗歌等文学作品 + - **适用场景**: + * 用户需要创作新文章、散文、诗歌等原创内容 + * 简单的写作任务,不需要后续评审或修改 + - **输出**: writer_output + + ### translator_agent + - **功能**: 擅长将文章翻译成各种语言 + - **适用场景**: + * 用户需要将内容翻译成其他语言 + * 翻译任务通常是单一操作,不需要多步骤处理 + - **输出**: translator_output + + ### writing_workflow_agent + - **功能**: 完整的写作工作流,包含两个步骤:先写文章,然后进行评审和修改 + - **适用场景**: + * 用户需要高质量的文章,要求经过评审和修改 + * 任务明确要求"确保质量"、"需要评审"、"需要修改"等 + * 需要多步骤处理的复杂写作任务 + - **工作流程**: + 1. article_writer: 根据用户需求创作文章 + 2. reviewer: 对文章进行评审和修改,确保质量 + - **输出**: reviewed_article + + ## 决策规则 + + 1. **单一任务判断**: + - 如果用户只需要翻译,选择 translator_agent + - 如果用户只需要简单写作,选择 writer_agent + - 如果用户需要高质量文章或明确要求评审,选择 writing_workflow_agent + + 2. **多步骤任务处理**: + - 如果用户需求包含多个步骤(如"先写文章,然后翻译"),需要分步处理 + - 先路由到第一个合适的Agent,等待其完成 + - 完成后,根据剩余需求继续路由到下一个Agent + - 直到所有步骤完成,返回FINISH + + 3. **任务完成判断**: + - 当用户的所有需求都已满足时,返回FINISH + - 如果还有未完成的任务,继续路由到相应的Agent + + ## 响应格式 + 只返回Agent名称(writer_agent、translator_agent、writing_workflow_agent)或FINISH,不要包含其他解释。 + """; + // 1. 创建普通的ReactAgent子Agent + ReactAgent writerAgent = ReactAgent.builder() + .name("writer_agent") + .model(chatModel) + .description("擅长创作各类文章,包括散文、诗歌等文学作品") + .instruction("你是一个知名的作家,擅长写作和创作。请根据用户的提问进行回答:\n\n {input}。") + .outputKey("writer_output") + .build(); + + ReactAgent translatorAgent = ReactAgent.builder() + .name("translator_agent") + .model(chatModel) + .description("擅长将文章翻译成各种语言") + .instruction("你是一个专业的翻译家,能够准确地将文章翻译成目标语言。" + + "如果待翻译的内容已存在于状态中,请使用:\n\n {writer_output}。") + .outputKey("translator_output") + .build(); + + // 2. 创建嵌套的SequentialAgent作为子Agent + // 这个SequentialAgent包含多个步骤:先写文章,再评审 + ReactAgent articleWriterAgent = ReactAgent.builder() + .name("article_writer") + .model(chatModel) + .description("专业写作Agent") + .instruction("你是一个知名的作家,擅长写作和创作。请根据用户的提问进行回答:{input}。") + .outputKey("article") + .build(); + + ReactAgent reviewerAgent = ReactAgent.builder() + .name("reviewer") + .model(chatModel) + .description("专业评审Agent") + .instruction("你是一个知名的评论家,擅长对文章进行评论和修改。" + + "对于散文类文章,请确保文章中必须包含对于西湖风景的描述。待评论文章:\n\n {article}" + + "最终只返回修改后的文章,不要包含任何评论信息。") + .outputKey("reviewed_article") + .build(); + + // 创建嵌套的SequentialAgent + SequentialAgent writingWorkflowAgent = SequentialAgent.builder() + .name("writing_workflow_agent") + .description("完整的写作工作流:先写文章,然后进行评审和修改") + .subAgents(List.of(articleWriterAgent, reviewerAgent)) + .build(); + + // 3. 创建SupervisorAgent,包含普通Agent和嵌套Agent + SupervisorAgent supervisorAgent = SupervisorAgent.builder() + .name("content_supervisor") + .description("内容管理监督者,负责协调写作、翻译和完整写作工作流等任务") + .model(chatModel) + .systemPrompt(SUPERVISOR_INSTRUCTION) + .subAgents(List.of(writerAgent, translatorAgent, writingWorkflowAgent)) + .build(); + + // 使用示例 + System.out.println("监督者测试1: 简单写作任务"); + Optional result1 = supervisorAgent.invoke("帮我写一篇关于春天的短文"); + // Supervisor会路由到writer_agent,处理完成后返回Supervisor,Supervisor判断完成返回FINISH + if (result1.isPresent()) { + result1.get().value("writer_output").ifPresent(output -> + System.out.println("写作结果: " + output)); + } + + System.out.println("\n监督者测试2: 需要完整工作流的任务"); + Optional result2 = supervisorAgent.invoke("帮我写一篇关于西湖的散文,并确保质量"); + // Supervisor会路由到writing_workflow_agent(嵌套SequentialAgent), + // 该Agent会先写文章,然后评审,完成后返回Supervisor,Supervisor判断完成返回FINISH + if (result2.isPresent()) { + result2.get().value("reviewed_article").ifPresent(output -> + System.out.println("评审后文章: " + output)); + } + + System.out.println("\n监督者测试3: 翻译任务"); + Optional result3 = supervisorAgent.invoke("请将以下内容翻译成英文:春暖花开"); + // Supervisor会路由到translator_agent,处理完成后返回Supervisor,Supervisor判断完成返回FINISH + if (result3.isPresent()) { + result3.get().value("translator_output").ifPresent(output -> + System.out.println("翻译结果: " + output)); + } + + System.out.println("\n监督者测试4: 多步骤任务(可能需要多次路由)"); + Optional result4 = supervisorAgent.invoke("先帮我写一篇关于春天的文章,然后翻译成英文"); + // Supervisor可能会: + // 1. 先路由到writer_agent写文章,完成后返回Supervisor + // 2. Supervisor判断还需要翻译,路由到translator_agent + // 3. 翻译完成后返回Supervisor,Supervisor判断所有任务完成,返回FINISH + if (result4.isPresent()) { + result4.get().value("writer_output").ifPresent(output -> + System.out.println("写作结果: " + output)); + result4.get().value("translator_output").ifPresent(output -> + System.out.println("翻译结果: " + output)); + } + + // 打印工作流图表 + System.out.println("\n=== SupervisorAgent 工作流图表 ==="); + printGraphRepresentation(supervisorAgent); + System.out.println("==================================\n"); + + // 示例5:SupervisorAgent作为SequentialAgent的子Agent,使用占位符 + System.out.println("\n监督者测试5: SupervisorAgent作为SequentialAgent的子Agent(使用占位符)"); + example8_supervisorAgentAsSequentialSubAgent(); + System.out.println(); + + System.out.println("SupervisorAgent示例执行完成"); + } + + /** + * 示例8.1:SupervisorAgent作为SequentialAgent的子Agent,使用占位符 + * + * 这个示例展示了: + * 1. SupervisorAgent可以作为SequentialAgent的子Agent + * 2. SupervisorAgent的instruction可以使用占位符引用前序Agent的输出 + * 3. SupervisorAgent的子Agent的instruction也可以使用占位符引用前序Agent的输出 + */ + private void example8_supervisorAgentAsSequentialSubAgent() throws Exception { + // 1. 创建第一个Agent,用于生成文章内容 + ReactAgent articleWriterAgent = ReactAgent.builder() + .name("article_writer") + .model(chatModel) + .description("专业写作Agent,负责创作文章") + .instruction("你是一个知名的作家,擅长写作和创作。请根据用户的提问进行回答:{input}。") + .outputKey("article_content") + .build(); + + // 2. 创建SupervisorAgent的子Agent + ReactAgent translatorAgent = ReactAgent.builder() + .name("translator_agent") + .model(chatModel) + .description("擅长将文章翻译成各种语言") + .instruction("你是一个专业的翻译家,能够准确地将文章翻译成目标语言。待翻译文章:\n\n {article_content}。") + .outputKey("translator_output") + .build(); + + ReactAgent reviewerAgent = ReactAgent.builder() + .name("reviewer_agent") + .model(chatModel) + .description("擅长对文章进行评审和修改") + .instruction("你是一个知名的评论家,擅长对文章进行评论和修改。待评审文章:\n\n {article_content}。" + + "请对文章进行评审,指出优点和需要改进的地方,并返回评审后的改进版本。") + .outputKey("reviewer_output") + .build(); + + // 3. 定义SupervisorAgent的instruction,使用占位符引用前序Agent的输出 + // 这个instruction包含 {article_content} 占位符,会被替换为第一个Agent的输出 + final String SUPERVISOR_INSTRUCTION = """ + 你是一个智能的内容处理监督者,你可以看到前序Agent的聊天历史与任务处理记录。当前,你收到了以下文章内容: + + {article_content} + + 请根据文章内容的特点和用户需求,决定是进行翻译还是评审: + - 如果用户要求翻译或文章需要翻译成其他语言,选择 translator_agent + - 如果用户要求评审、改进或优化文章,选择 reviewer_agent + - 如果任务完成,返回 FINISH + """; + + final String SUPERVISOR_SYSTEM_PROMPT = """ + 你是一个智能的内容处理监督者,负责协调翻译和评审任务。 + + ## 可用的子Agent及其职责 + + ### translator_agent + - **功能**: 擅长将文章翻译成各种语言 + - **适用场景**: 当文章需要翻译成其他语言时 + - **输出**: translator_output + + ### reviewer_agent + - **功能**: 擅长对文章进行评审和修改 + - **适用场景**: 当文章需要评审、改进或优化时 + - **输出**: reviewer_output + + ## 决策规则 + + 1. **根据文章内容和用户需求判断**: + - 如果用户要求翻译或文章需要翻译成其他语言,选择 translator_agent + - 如果用户要求评审、改进或优化文章,选择 reviewer_agent + + 2. **任务完成判断**: + - 当所有任务完成时,返回 FINISH + + ## 响应格式 + 只返回Agent名称(translator_agent、reviewer_agent)或FINISH,不要包含其他解释。 + """; + + // 4. 创建SupervisorAgent,其instruction使用占位符 + SupervisorAgent supervisorAgent = SupervisorAgent.builder() + .name("content_supervisor") + .description("内容处理监督者,根据前序Agent的输出决定翻译或评审") + .model(chatModel) + .systemPrompt(SUPERVISOR_SYSTEM_PROMPT) + .instruction(SUPERVISOR_INSTRUCTION) // 这个instruction包含 {article_content} 占位符 + .subAgents(List.of(translatorAgent, reviewerAgent)) + .build(); + + // 5. 创建SequentialAgent,先执行articleWriterAgent,然后执行supervisorAgent + SequentialAgent sequentialAgent = SequentialAgent.builder() + .name("content_processing_workflow") + .description("内容处理工作流:先写文章,然后根据文章内容决定翻译或评审") + .subAgents(List.of(articleWriterAgent, supervisorAgent)) + .build(); + + // 测试场景1:写文章后翻译 + System.out.println("场景1: 写文章后翻译"); + Optional result1 = sequentialAgent.invoke("帮我写一篇关于春天的短文,然后翻译成英文"); + if (result1.isPresent()) { + OverAllState state = result1.get(); + state.value("article_content").ifPresent(output -> { + if (output instanceof AssistantMessage) { + System.out.println("文章内容: " + ((AssistantMessage) output).getText()); + } + }); + state.value("translator_output").ifPresent(output -> { + if (output instanceof AssistantMessage) { + System.out.println("翻译结果: " + ((AssistantMessage) output).getText()); + } + }); + } + + // 测试场景2:写文章后评审 + System.out.println("\n场景2: 写文章后评审"); + Optional result2 = sequentialAgent.invoke("帮我写一篇关于春天的短文,然后进行评审和改进"); + if (result2.isPresent()) { + OverAllState state = result2.get(); + state.value("article_content").ifPresent(output -> { + if (output instanceof AssistantMessage) { + System.out.println("文章内容: " + ((AssistantMessage) output).getText()); + } + }); + state.value("reviewer_output").ifPresent(output -> { + if (output instanceof AssistantMessage) { + System.out.println("评审结果: " + ((AssistantMessage) output).getText()); + } + }); + } + } + + /** + * 打印工作流图表(支持SupervisorAgent) + */ + private void printGraphRepresentation(SupervisorAgent agent) { + GraphRepresentation representation = agent.getAndCompileGraph().getGraph(GraphRepresentation.Type.PLANTUML); + System.out.println(representation.content()); + } + + private void testRoutingSequentialEmbedding() throws GraphRunnerException { + ReactAgent reactAgent = ReactAgent.builder() + .name("weather_agent") + .description("根据用户的问题和提炼的位置信息查询天气。\n\n 用户问题:{input} \n\n 位置信息:{location}") + .model(chatModel) + .outputKey("weather") + .systemPrompt("你是一个天气查询专家").build(); + + ReactAgent locationAgent = ReactAgent.builder() + .name("location_agent") + .description("根据用户的问题,进行位置查询。\n 用户问题:{input}") + .model(chatModel) + .outputKey("location") + .systemPrompt("你是一个位置查询专家").build(); + + SequentialAgent sequentialAgent = SequentialAgent.builder() + .name("天气小助手") + .description("天气小助手") + .subAgents(List.of(locationAgent, reactAgent)) + .build(); + + LlmRoutingAgent routingAgent = LlmRoutingAgent.builder() + .name("用户小助手") + .description("帮助用户完成各种需求") +// .routingInstruction(""); // 可以提供详尽的说明,告知routing路由职责,如何选择子Agent等,用于替代系统默认的prompt。 + .model(chatModel) + .subAgents(List.of(sequentialAgent)).build(); + + Optional invoke = routingAgent.invoke("天气怎么样"); + System.out.println(invoke); + } + + /** + * 打印工作流图表 + * + * 使用PlantUML格式展示Agent工作流的结构 + */ + private void printGraphRepresentation(SequentialAgent agent) { + GraphRepresentation representation = agent.getAndCompileGraph().getGraph(GraphRepresentation.Type.PLANTUML); + System.out.println(representation.content()); + } + + /** + * 运行所有示例 + */ + public void runAllExamples() { + System.out.println("=== 多智能体(Multi-agent)示例 ===\n"); + + try { + System.out.println("示例1: 顺序执行(Sequential Agent)"); + example1_sequentialAgent(); + System.out.println(); + + System.out.println("示例2: 控制推理内容"); + example2_controlReasoningContents(); + System.out.println(); + + System.out.println("示例3: 并行执行(Parallel Agent)"); + example3_parallelAgent(); + System.out.println(); + + System.out.println("示例4: 自定义合并策略"); + example4_customMergeStrategy(); + System.out.println(); +// + System.out.println("示例5: LLM路由(LlmRoutingAgent)"); + example5_llmRoutingAgent(); + System.out.println(); + + System.out.println("示例6: 优化路由准确性"); + example6_optimizedRouting(); + System.out.println(); + + System.out.println("示例7: 混合模式"); + example7_hybridPattern(); + System.out.println(); + + System.out.println("示例8: 监督者模式(SupervisorAgent)"); + example8_supervisorAgent(); + System.out.println(); + + testRoutingSequentialEmbedding(); + + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/RAGExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/RAGExample.java new file mode 100644 index 00000000..0ef59784 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/RAGExample.java @@ -0,0 +1,430 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.advanced; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.document.Document; +import org.springframework.ai.reader.TextReader; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; +import org.springframework.ai.transformer.splitter.TokenTextSplitter; +import org.springframework.ai.vectorstore.VectorStore; + +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 检索增强生成(RAG)示例 + * + * 演示如何使用RAG技术为LLM提供外部知识,包括: + * 1. 构建知识库 + * 2. 两步RAG + * 3. Agentic RAG + * 4. 混合RAG + * + * 参考文档: advanced_doc/rag.md + */ +public class RAGExample { + + private final ChatModel chatModel; + private final VectorStore vectorStore; + + public RAGExample(ChatModel chatModel, VectorStore vectorStore) { + this.chatModel = chatModel; + this.vectorStore = vectorStore; + } + + /** + * Main方法:运行所有示例 + * + * 注意:需要配置ChatModel和VectorStore实例才能运行 + */ + public static void main(String[] args) { + // 创建 DashScope API 实例 + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + // 创建 ChatModel + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // TODO: 请配置您的VectorStore实例 + // 例如:VectorStore vectorStore = new YourVectorStoreImplementation(); + VectorStore vectorStore = null; // 请替换为实际的VectorStore实例 + + if (chatModel == null || vectorStore == null) { + System.err.println("错误:请先配置ChatModel和VectorStore实例"); + System.err.println("请设置 AI_DASHSCOPE_API_KEY 环境变量,并配置VectorStore实例"); + return; + } + + // 创建示例实例 + RAGExample example = new RAGExample(chatModel, vectorStore); + + // 运行所有示例 + example.runAllExamples(); + } + + /** + * 示例1:构建知识库 + * + * 从文档加载、分割、嵌入并存储到向量数据库 + */ + public void example1_buildKnowledgeBase() { + // 1. 加载文档 + Resource resource = new FileSystemResource("path/to/document.txt"); + TextReader textReader = new TextReader(resource); + List documents = textReader.get(); + + // 2. 分割文档为块 + TokenTextSplitter splitter = new TokenTextSplitter(); + List chunks = splitter.apply(documents); + + // 3. 将块添加到向量存储 + vectorStore.add(chunks); + + // 现在可以使用向量存储进行检索 + List results = vectorStore.similaritySearch("查询文本"); + + System.out.println("知识库构建完成,检索到 " + results.size() + " 个相关文档"); + } + + /** + * 示例2:两步RAG + * + * 检索步骤总是在生成步骤之前执行 + */ + public void example2_twoStepRAG() { + // 两步RAG:检索 -> 生成 + String userQuestion = "Spring AI Alibaba支持哪些模型?"; + + // Step 1: Retrieve relevant documents + List relevantDocs = vectorStore.similaritySearch(userQuestion); + + // Step 2: Build context from documents + String context = relevantDocs.stream() + .map(Document::getText) + .collect(Collectors.joining("\n\n")); + + // Step 3: Generate answer with context + ChatClient chatClient = ChatClient.builder(chatModel).build(); + String answer = chatClient.prompt() + .user(u -> u.text("基于以下上下文回答问题:\n\n上下文:\n" + context + "\n\n问题:" + userQuestion)) + .call() + .content(); + + System.out.println("答案: " + answer); + + // 检索到的文档作为上下文添加到提示中 + // ChatModel 使用增强的上下文生成答案 + + System.out.println("两步RAG示例执行完成"); + } + + /** + * 示例3:Agentic RAG + * + * Agent决定何时以及如何检索信息 + */ + public void example3_agenticRAG() throws Exception { + // 创建文档检索工具 + class DocumentSearchTool { + public Response search(Request request) { + // 从向量存储检索相关文档 + List docs = vectorStore.similaritySearch(request.query()); + + // 合并文档内容 + String combinedContent = docs.stream() + .map(Document::getText) + .collect(Collectors.joining("\n\n")); + + return new Response(combinedContent); + } + + public record Request(String query) { } + + public record Response(String content) { } + } + + DocumentSearchTool searchTool = new DocumentSearchTool(); + + // 创建工具回调 + ToolCallback searchCallback = FunctionToolCallback.builder("search_documents", + (Function) + request -> searchTool.search(request)) + .description("搜索文档以查找相关信息") + .inputType(DocumentSearchTool.Request.class) + .build(); + + // 创建带有检索工具的Agent + ReactAgent ragAgent = ReactAgent.builder() + .name("rag_agent") + .model(chatModel) + .instruction("你是一个智能助手。当需要查找信息时,使用search_documents工具。" + + "基于检索到的信息回答用户的问题,并引用相关片段。") + .tools(searchCallback) + .build(); + + // Agent会自动决定何时调用检索工具 + ragAgent.invoke("Spring AI Alibaba支持哪些向量数据库?"); + + System.out.println("Agentic RAG示例执行完成"); + } + + /** + * 示例4:多源RAG + * + * Agent可以从多个来源检索信息 + */ + public void example4_multiSourceRAG() throws Exception { + // 创建多个检索工具 + class WebSearchTool { + public Response search(Request request) { + return new Response("从网络搜索到的信息: " + request.query()); + } + + public record Request(String query) { } + + public record Response(String content) { } + } + + class DatabaseQueryTool { + public Response query(Request request) { + return new Response("从数据库查询到的信息: " + request.query()); + } + + public record Request(String query) { } + + public record Response(String content) { } + } + + class DocumentSearchTool { + public Response search(Request request) { + List docs = vectorStore.similaritySearch(request.query()); + String content = docs.stream() + .map(Document::getText) + .collect(Collectors.joining("\n\n")); + return new Response(content); + } + + public record Request(String query) { } + + public record Response(String content) { } + } + + WebSearchTool webSearchTool = new WebSearchTool(); + DatabaseQueryTool dbQueryTool = new DatabaseQueryTool(); + DocumentSearchTool docSearchTool = new DocumentSearchTool(); + + ToolCallback webSearchCallback = FunctionToolCallback.builder("web_search", + (Function) + req -> webSearchTool.search(req)) + .description("搜索互联网以获取最新信息") + .inputType(WebSearchTool.Request.class) + .build(); + + ToolCallback databaseQueryCallback = FunctionToolCallback.builder("database_query", + (Function) + req -> dbQueryTool.query(req)) + .description("查询内部数据库") + .inputType(DatabaseQueryTool.Request.class) + .build(); + + ToolCallback documentSearchCallback = FunctionToolCallback.builder("document_search", + (Function) + req -> docSearchTool.search(req)) + .description("搜索文档库") + .inputType(DocumentSearchTool.Request.class) + .build(); + + // Agent可以访问多个检索源 + ReactAgent multiSourceAgent = ReactAgent.builder() + .name("multi_source_rag_agent") + .model(chatModel) + .instruction("你可以访问多个信息源:" + + "1. web_search - 用于最新的互联网信息\n" + + "2. database_query - 用于内部数据\n" + + "3. document_search - 用于文档库\n" + + "根据问题选择最合适的工具。") + .tools(webSearchCallback, databaseQueryCallback, documentSearchCallback) + .build(); + + multiSourceAgent.invoke("比较我们的产品文档中的功能和最新的市场趋势"); + + System.out.println("多工具Agentic RAG示例执行完成"); + } + + /** + * 示例5:混合RAG + * + * 结合查询增强、检索验证和答案验证 + */ + public void example5_hybridRAG() { + class HybridRAGSystem { + private final ChatModel chatModel; + private final VectorStore vectorStore; + + public HybridRAGSystem(ChatModel chatModel, VectorStore vectorStore) { + this.chatModel = chatModel; + this.vectorStore = vectorStore; + } + + public String answer(String userQuestion) { + // 1. 查询增强 + String enhancedQuery = enhanceQuery(userQuestion); + + int maxAttempts = 3; + for (int attempt = 0; attempt < maxAttempts; attempt++) { + // 2. 检索文档 + List docs = vectorStore.similaritySearch(enhancedQuery); + + // 3. 检索验证 + if (!isRetrievalSufficient(docs)) { + enhancedQuery = refineQuery(enhancedQuery, docs); + continue; + } + + // 4. 生成答案 + String answer = generateAnswer(userQuestion, docs); + + // 5. 答案验证 + ValidationResult validation = validateAnswer(answer, docs); + if (validation.isValid()) { + return answer; + } + + // 6. 根据验证结果决定下一步 + if (validation.shouldRetry()) { + enhancedQuery = refineBasedOnValidation(enhancedQuery, validation); + } + else { + return answer; // 返回当前最佳答案 + } + } + + return "无法生成满意的答案"; + } + + private String enhanceQuery(String query) { + return query; // 实现查询增强逻辑 + } + + private boolean isRetrievalSufficient(List docs) { + return !docs.isEmpty() && calculateRelevanceScore(docs) > 0.7; + } + + private double calculateRelevanceScore(List docs) { + return 0.8; // 实现相关性评分逻辑 + } + + private String refineQuery(String query, List docs) { + return query; // 实现查询优化逻辑 + } + + private String generateAnswer(String question, List docs) { + String context = docs.stream() + .map(Document::getText) + .collect(Collectors.joining("\n\n")); + + ChatClient client = ChatClient.builder(chatModel).build(); + return client.prompt() + .system("基于以下上下文回答问题:\n" + context) + .user(question) + .call() + .content(); + } + + private ValidationResult validateAnswer(String answer, List docs) { + // 实现答案验证逻辑 + return new ValidationResult(true, false); + } + + private String refineBasedOnValidation(String query, ValidationResult validation) { + return query; // 基于验证结果优化查询 + } + + class ValidationResult { + private boolean valid; + private boolean shouldRetry; + + public ValidationResult(boolean valid, boolean shouldRetry) { + this.valid = valid; + this.shouldRetry = shouldRetry; + } + + public boolean isValid() { + return valid; + } + + public boolean shouldRetry() { + return shouldRetry; + } + } + } + + HybridRAGSystem hybridRAG = new HybridRAGSystem(chatModel, vectorStore); + String answer = hybridRAG.answer("解释一下Spring AI Alibaba的核心功能"); + + System.out.println("混合RAG答案: " + answer); + System.out.println("混合RAG示例执行完成"); + } + + /** + * 运行所有示例 + */ + public void runAllExamples() { + System.out.println("=== 检索增强生成(RAG)示例 ===\n"); + + try { + System.out.println("示例1: 构建知识库"); + // example1_buildKnowledgeBase(); // 需要实际文件路径 + System.out.println(); + + System.out.println("示例2: 两步RAG"); + example2_twoStepRAG(); + System.out.println(); + + System.out.println("示例3: Agentic RAG"); + example3_agenticRAG(); + System.out.println(); + + System.out.println("示例4: 多数据源RAG"); + example4_multiSourceRAG(); + System.out.println(); + + System.out.println("示例5: 混合RAG"); + example5_hybridRAG(); + System.out.println(); + + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/WorkflowExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/WorkflowExample.java new file mode 100644 index 00000000..ab37ef83 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/WorkflowExample.java @@ -0,0 +1,667 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.advanced; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.CompileConfig; +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.NodeOutput; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.action.NodeAction; +import com.alibaba.cloud.ai.graph.action.NodeActionWithConfig; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; +import com.alibaba.cloud.ai.graph.streaming.StreamingOutput; +import com.alibaba.cloud.ai.graph.utils.TypeRef; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.prompt.PromptTemplate; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 工作流(Workflow)示例 + * + * 演示如何使用StateGraph构建智能工作流,包括: + * 1. 定义自定义Node + * 2. Agent作为Node + * 3. 混合使用Agent Node和普通Node + * 4. 执行工作流 + * + * 参考文档: advanced_doc/workflow.md + */ +public class WorkflowExample { + + private final ChatModel chatModel; + + public WorkflowExample(ChatModel chatModel) { + this.chatModel = chatModel; + } + + /** + * Main方法:运行所有示例 + * + * 注意:需要配置ChatModel实例才能运行 + */ + public static void main(String[] args) { + // 创建 DashScope API 实例 + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + // 创建 ChatModel + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + if (chatModel == null) { + System.err.println("错误:请先配置ChatModel实例"); + System.err.println("请设置 AI_DASHSCOPE_API_KEY 环境变量"); + return; + } + + // 创建示例实例 + WorkflowExample example = new WorkflowExample(chatModel); + + // 运行所有示例 + example.runAllExamples(); + } + + /** + * 示例1:基础Node定义 + * + * 创建简单的文本处理Node + */ + public void example1_basicNode() { + class TextProcessorNode implements NodeAction { + @Override + public Map apply(OverAllState state) throws Exception { + // 1. 从状态中获取输入 + String input = state.value("query", "").toString(); + + // 2. 执行业务逻辑 + String processedText = input.toUpperCase().trim(); + + // 3. 返回更新后的状态 + Map result = new HashMap<>(); + result.put("processed_text", processedText); + return result; + } + } + + TextProcessorNode processor = new TextProcessorNode(); + System.out.println("基础Node定义示例完成"); + } + + /** + * 示例2:带配置的AI Node + * + * 创建调用LLM的Node + */ + public void example2_aiNode() { + class QueryExpanderNode implements NodeActionWithConfig { + private final ChatClient chatClient; + private final PromptTemplate promptTemplate; + + public QueryExpanderNode(ChatClient.Builder chatClientBuilder) { + this.chatClient = chatClientBuilder.build(); + this.promptTemplate = new PromptTemplate( + "你是一个搜索优化专家。请为以下查询生成 {number} 个不同的变体。\n" + + "原始查询:{query}\n\n" + + "查询变体:\n" + ); + } + + @Override + public Map apply(OverAllState state, RunnableConfig config) throws Exception { + // 获取输入参数 + String query = state.value("query", "").toString(); + Integer number = (Integer) state.value("expanderNumber", 3); + + // 调用 LLM + String result = chatClient.prompt() + .user(user -> user + .text(promptTemplate.getTemplate()) + .param("query", query) + .param("number", number)) + .call() + .content(); + + // 处理结果 + String[] variants = result.split("\n"); + + // 返回更新的状态 + Map output = new HashMap<>(); + output.put("queryVariants", Arrays.asList(variants)); + return output; + } + } + + QueryExpanderNode expander = new QueryExpanderNode(ChatClient.builder(chatModel)); + System.out.println("AI Node示例完成"); + } + + /** + * 示例3:条件评估Node + * + * 用于工作流中的条件分支判断 + */ + public void example3_conditionNode() { + class ConditionEvaluatorNode implements NodeAction { + @Override + public Map apply(OverAllState state) throws Exception { + String input = state.value("input", "").toString().toLowerCase(); + + // 根据输入内容决定路由 + String route; + if (input.contains("错误") || input.contains("异常")) { + route = "error_handling"; + } + else if (input.contains("数据") || input.contains("分析")) { + route = "data_processing"; + } + else if (input.contains("报告") || input.contains("总结")) { + route = "report_generation"; + } + else { + route = "default"; + } + + Map result = new HashMap<>(); + result.put("_condition_result", route); + return result; + } + } + + ConditionEvaluatorNode evaluator = new ConditionEvaluatorNode(); + System.out.println("条件评估Node示例完成"); + } + + /** + * 示例4:并行结果聚合Node + * + * 用于收集和聚合并行执行的多个Node的结果 + */ + public void example4_aggregatorNode() { + class ParallelResultAggregatorNode implements NodeAction { + private final String outputKey; + + public ParallelResultAggregatorNode(String outputKey) { + this.outputKey = outputKey; + } + + @Override + public Map apply(OverAllState state) throws Exception { + // 收集所有并行任务的结果 + List results = new ArrayList<>(); + + // 假设并行任务将结果存储在不同的键中 + state.value("result_1").ifPresent(r -> results.add(r.toString())); + state.value("result_2").ifPresent(r -> results.add(r.toString())); + state.value("result_3").ifPresent(r -> results.add(r.toString())); + + // 聚合结果 + String aggregatedResult = String.join("\n---\n", results); + + Map output = new HashMap<>(); + output.put(outputKey, aggregatedResult); + return output; + } + } + + ParallelResultAggregatorNode aggregator = new ParallelResultAggregatorNode("merged_results"); + System.out.println("聚合Node示例完成"); + } + + /** + * 示例5:集成自定义Node到StateGraph + * + * 构建包含自定义Node的工作流 + */ + public void example5_buildWorkflowWithCustomNodes() throws Exception { + // 定义状态管理策略 + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("query", new ReplaceStrategy()); + strategies.put("processed_text", new ReplaceStrategy()); + strategies.put("queryVariants", new ReplaceStrategy()); + strategies.put("final_result", new ReplaceStrategy()); + return strategies; + }; + + // 创建Node实例 + class TextProcessorNode implements NodeAction { + @Override + public Map apply(OverAllState state) throws Exception { + String input = state.value("query", "").toString(); + String processed = input.toUpperCase().trim(); + return Map.of("processed_text", processed); + } + } + + class ConditionNode implements NodeAction { + @Override + public Map apply(OverAllState state) throws Exception { + String input = state.value("processed_text", "").toString(); + String route = input.length() > 10 ? "long" : "short"; + return Map.of("_condition_result", route); + } + } + + // 构建 StateGraph + StateGraph graph = new StateGraph(keyStrategyFactory); + + // 添加自定义 Node + graph.addNode("processor", node_async(new TextProcessorNode())); + graph.addNode("condition", node_async(new ConditionNode())); + + // 定义边(流程连接) + graph.addEdge(StateGraph.START, "processor"); + graph.addEdge("processor", "condition"); + + // 条件边:根据 condition node 的结果路由 + graph.addConditionalEdges( + "condition", + edge_async(state -> state.value("_condition_result", "short").toString()), + Map.of( + "long", "processor", // 长文本重新处理 + "short", StateGraph.END // 短文本结束 + ) + ); + + System.out.println("自定义Node工作流构建完成"); + } + + /** + * 示例6:Agent作为SubGraph Node + * + * 将ReactAgent嵌入到工作流中 + */ + public void example6_agentAsNode() throws Exception { + // 创建专门的数据分析 Agent + ReactAgent analysisAgent = ReactAgent.builder() + .name("data_analyzer") + .model(chatModel) + .instruction("你是一个数据分析专家,负责分析数据并提供洞察,请分析以下输入数据:\n {input}") + .outputKey("analysis_result") + .build(); + + // 创建报告生成 Agent + ReactAgent reportAgent = ReactAgent.builder() + .name("report_generator") + .model(chatModel) + .instruction("你是一个报告生成专家,负责将分析结果 “{analysis_result}” 转化为专业报告") + .outputKey("final_report") + .build(); + + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("input", new ReplaceStrategy()); + return strategies; + }; + + // 构建包含 Agent 的工作流 + StateGraph workflow = new StateGraph(keyStrategyFactory); + + // 将 Agent 作为 SubGraph Node 添加 + workflow.addNode(analysisAgent.name(), analysisAgent.asNode( + true, // includeContents: 是否传递父图的消息历史 + false)); + + workflow.addNode(reportAgent.name(), reportAgent.asNode( + true, + false)); + + // 定义流程 + workflow.addEdge(StateGraph.START, analysisAgent.name()); + workflow.addEdge(analysisAgent.name(), reportAgent.name()); + workflow.addEdge(reportAgent.name(), StateGraph.END); + + CompiledGraph compiledGraph = workflow.compile(CompileConfig.builder().build()); + NodeOutput lastOutput = compiledGraph.stream(Map.of("input", "2025年全年销量100亿,毛利率 23%,净利率 13%。2024年全年销量80亿,毛利率 20%,净利率 8%。")).doOnNext(output -> { + if (output instanceof StreamingOutput streamingOutput) { + System.out.println("Output from node " + streamingOutput.node() + ": " + streamingOutput.message().getText()); + } + }).blockLast(); + + System.out.println("\n\n最终结果,包含所有节点状态:\n" + lastOutput.state().data()); + } + + /** + * 示例7:混合使用Agent Node和普通Node + * + * 在工作流中结合Agent和自定义Node + */ + public void example7_hybridWorkflow() throws Exception { + // 创建 Agent + ReactAgent qaAgent = ReactAgent.builder() + .name("qa_agent") + .model(chatModel) + .instruction("你是一个问答专家,负责回答用户的问题:\n {cleaned_input}") + .outputKey("qa_result") + .enableLogging(true) + .build(); + + // 创建自定义 Node + class PreprocessorNode implements NodeAction { + @Override + public Map apply(OverAllState state) throws Exception { + String input = state.value("input", "").toString(); + String cleaned = input.trim().toLowerCase(); + return Map.of("cleaned_input", cleaned); + } + } + + class ValidatorNode implements NodeAction { + @Override + public Map apply(OverAllState state) throws Exception { + Message message = (Message)state.value("qa_result").get(); + boolean isValid = message.getText().length() > 50; // 简单验证 + return Map.of("is_valid", isValid); + } + } + + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("input", new ReplaceStrategy()); + strategies.put("cleaned_input", new ReplaceStrategy()); + strategies.put("qa_result", new ReplaceStrategy()); + strategies.put("is_valid", new ReplaceStrategy()); + return strategies; + }; + + // 构建混合工作流 + StateGraph workflow = new StateGraph(keyStrategyFactory); + + // 添加普通 Node + workflow.addNode("preprocess", node_async(new PreprocessorNode())); + workflow.addNode("validate", node_async(new ValidatorNode())); + + // 添加 Agent Node + workflow.addNode(qaAgent.name(), qaAgent.asNode( + true, + false)); + + // 定义流程:预处理 -> Agent处理 -> 验证 + workflow.addEdge(StateGraph.START, "preprocess"); + workflow.addEdge("preprocess", qaAgent.name()); + workflow.addEdge(qaAgent.name(), "validate"); + + // 条件边:验证通过则结束,否则重新处理 + workflow.addConditionalEdges( + "validate", + edge_async(state -> (Boolean) state.value("is_valid", false) ? "end" : qaAgent.name()), + Map.of("end", StateGraph.END, qaAgent.name(), qaAgent.name()) + ); + + CompiledGraph compiledGraph = workflow.compile(CompileConfig.builder().build()); + NodeOutput lastOutput = compiledGraph.stream(Map.of("input", "请解释量子计算的基本原理")).doOnNext(output -> { + if (output instanceof StreamingOutput streamingOutput) { + if (streamingOutput.message() != null) { + // steaming output from streaming llm node + System.out.println("Streaming output from node " + streamingOutput.node() + ": " + streamingOutput.message().getText()); + } else { + // output from normal node, investigate the state to get the node data + System.out.println("Output from node " + streamingOutput.node() + ": " + streamingOutput.state().data()); + } + } + }).blockLast(); + + System.out.println("\n\n\n最终结果,包含所有节点状态:\n" + lastOutput.state().data()); + } + + /** + * 示例8:执行工作流 + * + * 编译并执行StateGraph工作流 + */ + public void example8_executeWorkflow() throws Exception { + // 创建简单的工作流 + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("input", new ReplaceStrategy()); + strategies.put("output", new ReplaceStrategy()); + return strategies; + }; + + StateGraph workflow = new StateGraph(keyStrategyFactory); + + class SimpleNode implements NodeAction { + @Override + public Map apply(OverAllState state) throws Exception { + String input = state.value("input", "").toString(); + return Map.of("output", "Processed: " + input); + } + } + + workflow.addNode("process", node_async(new SimpleNode())); + workflow.addEdge(StateGraph.START, "process"); + workflow.addEdge("process", StateGraph.END); + + // 编译工作流 + CompileConfig compileConfig = CompileConfig.builder().build(); + CompiledGraph compiledGraph = workflow.compile(compileConfig); + + // 准备输入 + Map input = Map.of( + "input", "请分析2024年AI行业发展趋势" + ); + + // 配置运行参数 + RunnableConfig runnableConfig = RunnableConfig.builder() + .threadId("workflow-001") + .build(); + + // 执行工作流 + Optional result = compiledGraph.invoke(input, runnableConfig); + + // 处理结果 + result.ifPresent(state -> { + System.out.println("输入: " + state.value("input").orElse("无")); + System.out.println("输出: " + state.value("output").orElse("无")); + }); + + System.out.println("工作流执行完成"); + } + + /** + * 示例9:多Agent协作工作流 + * + * 构建完整的研究工作流 + */ + private static final String RESEARCH_RESULT = """ + #### 1. 引言 + AI Agent(人工智能代理)是近年来人工智能领域的重要研究方向之一。它指的是一种能够感知环境、自主决策并采取行动以实现特定目标的智能系统。随着深度学习、强化学习和自然语言处理等技术的发展,AI Agent 在多个领域展现出巨大的潜力。 + + 本报告旨在全面梳理 AI Agent 的技术发展、应用场景、典型案例以及未来趋势,为相关研究和应用提供参考。 + + --- + + #### 2. 技术发展 + + ##### 2.1 核心技术 + - **感知能力**:通过计算机视觉、语音识别和传感器数据处理,AI Agent 能够理解外部环境。 + - **决策能力**:基于强化学习、规则引擎或大模型推理,AI Agent 可以在复杂环境中做出最优决策。 + - **执行能力**:通过与物理设备(如机器人)或软件系统(如自动化工具)集成,AI Agent 实现任务执行。 + - **学习与适应**:利用在线学习和迁移学习技术,AI Agent 能够不断优化自身行为。 + + ##### 2.2 关键进展 + - **大模型驱动的 Agent**:以 LLM(大语言模型)为基础的 AI Agent 成为研究热点,例如 AutoGPT、BabyAGI 等项目展示了自主任务分解与执行的能力。 + - **多模态融合**:结合文本、图像、音频等多种输入形式,提升 Agent 的环境理解能力。 + - **人机协作**:设计更自然的人机交互机制,使 AI Agent 更好地融入人类工作流程。 + + """; + + public void example9_multiAgentResearchWorkflow() throws Exception { + // 创建工具(示例) + ToolCallback searchTool = FunctionToolCallback + .builder("search", (args) -> RESEARCH_RESULT) + .description("搜索工具") + .inputType(String.class) + .build(); + + ToolCallback analysisTool = FunctionToolCallback + .builder("analysis", (args) -> "分析结果") + .description("分析工具") + .inputType(String.class) + .build(); + + ToolCallback summaryTool = FunctionToolCallback + .builder("summary", (args) -> "总结结果") + .description("总结结果。") + .inputType(String.class) + .build(); + + // 1. 创建信息收集 Agent + ReactAgent researchAgent = ReactAgent.builder() + .name("researcher") + .model(chatModel) + .instruction("你是一个研究专家,负责收集和整理相关信息,请研究主题: {input}") + .tools(searchTool) + .outputKey("research_data") + .enableLogging(true) + .build(); + + // 2. 创建数据分析 Agent + ReactAgent analysisAgent = ReactAgent.builder() + .name("analyst") + .model(chatModel) + .instruction("你是一个分析专家,负责深入分析关于主题 “{input}” 的研究数据。数据如下: \n\n {research_data}") + .tools(analysisTool) + .outputKey("analysis_result") + .enableLogging(true) + .build(); + + // 3. 创建总结 Agent + ReactAgent summaryAgent = ReactAgent.builder() + .name("summarizer") + .model(chatModel) + .instruction("你是一个总结专家,负责将分析结果提炼为简洁的结论,结果:\n\n {analysis_result}") + .tools(summaryTool) + .outputKey("final_summary") + .enableLogging(true) + .build(); + + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("input", new ReplaceStrategy()); + return strategies; + }; + + // 4. 构建工作流 + StateGraph workflow = new StateGraph(keyStrategyFactory); + + // 添加 Agent 节点 + workflow.addNode(researchAgent.name(), researchAgent.asNode( + true, // 包含历史消息 + false // 不返回推理过程 + )); + + workflow.addNode(analysisAgent.name(), analysisAgent.asNode( + true, + false)); + + workflow.addNode(summaryAgent.name(), summaryAgent.asNode( + true, + true // 返回完整推理过程 + )); + + // 定义顺序执行流程 + workflow.addEdge(StateGraph.START, researchAgent.name()); + workflow.addEdge(researchAgent.name(), analysisAgent.name()); + workflow.addEdge(analysisAgent.name(), summaryAgent.name()); + workflow.addEdge(summaryAgent.name(), StateGraph.END); + + + CompiledGraph compiledGraph = workflow.compile(CompileConfig.builder().build()); + NodeOutput finaOutput = compiledGraph.stream(Map.of("input", "帮我做一份关于AI Agent的研究报告")).doOnNext(output -> { + if (output instanceof StreamingOutput streamingOutput) { + System.out.println("Output from node " + streamingOutput.node() + ": " + streamingOutput.message().getText()); + } + }).blockLast(); + + System.out.println("多Agent研究工作流构建完成"); + System.out.println("最终输出: " + finaOutput.state().value("final_summary").orElse("无")); + } + + /** + * 运行所有示例 + */ + public void runAllExamples() { + System.out.println("=== 工作流(Workflow)示例 ===\n"); + + try { + System.out.println("示例1: 基础Node定义"); + example1_basicNode(); + System.out.println(); + + System.out.println("示例2: 带配置的AI Node"); + example2_aiNode(); + System.out.println(); + + System.out.println("示例3: 条件评估Node"); + example3_conditionNode(); + System.out.println(); + + System.out.println("示例4: 并行结果聚合Node"); + example4_aggregatorNode(); + System.out.println(); + + System.out.println("示例5: 集成自定义Node到StateGraph"); + example5_buildWorkflowWithCustomNodes(); + System.out.println(); + + System.out.println("示例6: Agent作为SubGraph Node"); + example6_agentAsNode(); + System.out.println(); +// + System.out.println("示例7: 混合使用Agent Node和普通Node"); + example7_hybridWorkflow(); + System.out.println(); + + System.out.println("示例8: 执行工作流"); + example8_executeWorkflow(); + System.out.println(); + + System.out.println("示例9: 多Agent协作工作流"); + example9_multiAgentResearchWorkflow(); + System.out.println(); + + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/A2AAgentConfig.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/A2AAgentConfig.java new file mode 100644 index 00000000..723b8703 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/A2AAgentConfig.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.cloud.ai.examples.documentation.framework.advanced.a2a; + +import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +import org.springframework.ai.chat.model.ChatModel; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 定义并暴露本地 ReactAgent + */ +@Configuration +public class A2AAgentConfig { + + @Bean(name = "dataAnalysisAgent") + public ReactAgent dataAnalysisAgent(@Qualifier("dashscopeChatModel") ChatModel chatModel) { + return ReactAgent.builder() + .name("data_analysis_agent") + .model(chatModel) + .description("专门用于数据分析和统计计算的本地智能体") + .instruction("你是一个专业的数据分析专家,擅长处理各类数据统计和分析任务。" + + "你能够理解用户的数据分析需求,提供准确的统计计算结果和专业的分析建议。") + .outputKey("messages") + .build(); + } +} diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/A2AExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/A2AExample.java new file mode 100644 index 00000000..3396fa75 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/A2AExample.java @@ -0,0 +1,138 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.advanced.a2a; + +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.a2a.A2aRemoteAgent; +import com.alibaba.cloud.ai.graph.agent.a2a.AgentCardProvider; +import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; + +import org.springframework.ai.chat.model.ChatModel; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +/** + * A2A (Agent-to-Agent) 一体化示例:注册 -> 发现 -> 调用 + * + * - 启动本应用后,data_analysis_agent 将作为本地 ReactAgent 自动注册到 A2A(并根据配置注册到 Nacos) + * - 通过 AgentCardProvider 从注册中心发现该 Agent + * - 构造 A2aRemoteAgent 远程代理并完成调用 + */ +@Component +public class A2AExample { + + private final ChatModel chatModel; + private final AgentCardProvider agentCardProvider; + private final ReactAgent localDataAnalysisAgent; + + @Autowired + public A2AExample(@Qualifier("dashscopeChatModel") ChatModel chatModel, + AgentCardProvider agentCardProvider, + @Qualifier("dataAnalysisAgent") ReactAgent localDataAnalysisAgent) { + this.chatModel = chatModel; + this.agentCardProvider = agentCardProvider; + this.localDataAnalysisAgent = localDataAnalysisAgent; + } + + /** + * 运行一体化演示 + * 1) 本地 Agent 已由 Spring 容器创建并通过 A2A Server 自动暴露 + * 2) 使用 AgentCardProvider 从注册中心发现该 Agent + * 3) 构建 A2aRemoteAgent 并完成一次远程调用 + */ + public void runDemo() throws GraphRunnerException { + System.out.println("=== A2A 一体化演示:注册 -> 发现 -> 调用 ===\n"); + + // 阶段说明 + System.out.println("【架构说明】"); + System.out.println("1. Registry(注册):本地 Agent 注册到 Nacos,供其他服务发现"); + System.out.println("2. Discovery(发现):通过 AgentCardProvider 从 Nacos 查询 Agent"); + System.out.println("3. Invocation(调用):构造 A2aRemoteAgent 完成远程调用"); + System.out.println(); + + // 1) 本地直连:验证本地注册的 ReactAgent 可用 + System.out.println("【阶段1:本地直调】验证 ReactAgent Bean 功能"); + System.out.println("- Agent 名称: data_analysis_agent"); + System.out.println("- 调用方式: 直接调用 Bean"); + System.out.println("- 注册状态: 已通过 A2A Server AutoConfiguration 注册到 Nacos"); + System.out.println(); + + System.out.println("执行本地调用..."); + Optional localResult = localDataAnalysisAgent.invoke("请对上月销售数据进行趋势分析,并给出关键结论。"); + localResult.flatMap(s -> s.value("messages")).ifPresent(r -> + System.out.println("✓ 本地调用成功,结果: " + (r.toString().length() > 100 ? r.toString() + .substring(0, 100) + "..." : r))); + System.out.println(); + + // 2) 发现:通过 AgentCardProvider 从注册中心获取该 Agent 的 AgentCard + System.out.println("【阶段2:服务发现】使用 AgentCardProvider 从 Nacos 发现 Agent"); + System.out.println("- 发现机制: Nacos Discovery (spring.ai.alibaba.a2a.nacos.discovery.enabled=true)"); + System.out.println("- AgentCardProvider 类型: " + agentCardProvider.getClass().getSimpleName()); + System.out.println("- 查询 Agent: data_analysis_agent"); + System.out.println(); + + System.out.println("构建 A2aRemoteAgent..."); + A2aRemoteAgent remote = A2aRemoteAgent.builder() + .name("data_analysis_agent") + .agentCardProvider(agentCardProvider) // 从 Nacos 自动获取 AgentCard + .description("数据分析远程代理") + .build(); + System.out.println("✓ A2aRemoteAgent 构建成功,AgentCard 已从 Nacos 获取"); + System.out.println(); + + // 3) 远程调用:通过 A2aRemoteAgent 调用(即便是同进程,也模拟远程化调用路径) + System.out.println("【阶段3:远程调用】通过 A2aRemoteAgent 执行远程调用"); + System.out.println("- 调用路径: A2aRemoteAgent -> REST API (/a2a/message) -> 本地 ReactAgent"); + System.out.println("- 传输协议: JSON-RPC over HTTP"); + System.out.println(); + + System.out.println("执行远程调用..."); + Optional remoteResult = remote.invoke("请根据季度数据给出同比与环比分析概要。"); + remoteResult.flatMap(s -> s.value("output")).ifPresent(r -> + System.out.println("✓ 远程调用成功,结果: " + (r.toString().length() > 100 ? r.toString() + .substring(0, 100) + "..." : r))); + System.out.println(); + + // 验证要点 + System.out.println("【验证要点】"); + System.out.println("1. 本地 AgentCard:"); + System.out.println(" → curl http://localhost:8080/.well-known/agent.json"); + System.out.println(); + System.out.println("2. Nacos 控制台(验证注册):"); + System.out.println(" → http://localhost:8848/nacos"); + System.out.println(" → 登录 (nacos/nacos)"); + System.out.println(" → 查看 A2A 服务注册维度"); + System.out.println(); + System.out.println("3. 配置说明:"); + System.out.println(" → registry.enabled=true : 将本地 Agent 注册到 Nacos(服务提供者)"); + System.out.println(" → discovery.enabled=true : 从 Nacos 发现其他 Agent(服务消费者)"); + System.out.println(); + System.out.println("4. 其他服务调用:"); + System.out.println(" 其他服务可使用相同的方式发现并调用 data_analysis_agent:"); + System.out.println(" ```"); + System.out.println(" A2aRemoteAgent remote = A2aRemoteAgent.builder()"); + System.out.println(" .name(\"data_analysis_agent\")"); + System.out.println(" .agentCardProvider(agentCardProvider)"); + System.out.println(" .build();"); + System.out.println(" remote.invoke(\"分析请求...\");"); + System.out.println(" ```"); + } +} diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/A2AExampleController.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/A2AExampleController.java new file mode 100644 index 00000000..c67981a1 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/A2AExampleController.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.cloud.ai.examples.documentation.framework.advanced.a2a; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * 提供 HTTP 接口来调用 A2A 示例 + */ +@RestController +@RequestMapping("/api/a2a") +public class A2AExampleController { + + private final A2AExample a2aExample; + + @Autowired + public A2AExampleController(A2AExample a2aExample) { + this.a2aExample = a2aExample; + } + + /** + * 运行统一的 A2A 演示 + * + * @return 执行结果 + */ + @GetMapping("/demo") + public Map runDemo() { + Map response = new HashMap<>(); + try { + a2aExample.runDemo(); + response.put("status", "success"); + response.put("message", "A2A 一体化演示执行完成"); + } + catch (Exception e) { + response.put("status", "error"); + response.put("message", "执行演示时出错: " + e.getMessage()); + response.put("error", e.getClass().getSimpleName()); + } + return response; + } +} diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/DocumentationApplication.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/DocumentationApplication.java new file mode 100644 index 00000000..d2296fb8 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/DocumentationApplication.java @@ -0,0 +1,53 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.cloud.ai.examples.documentation.framework.advanced.a2a; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Spring AI Alibaba Documentation Examples Application + * + * 本应用演示了 Spring AI Alibaba 的各种功能,包括: + * - Agent Framework 示例 + * - A2A (Agent-to-Agent) 分布式智能体示例 + * - Graph 工作流示例 + */ +@SpringBootApplication +public class DocumentationApplication { + + private static final Logger logger = LoggerFactory.getLogger(DocumentationApplication.class); + + public static void main(String[] args) { + SpringApplication.run(DocumentationApplication.class, args); + } + + // @Bean + public CommandLineRunner demoRunner(A2AExample a2aExample) { + return args -> { + logger.info("================================================="); + logger.info("Spring AI Alibaba Documentation Examples Started"); + logger.info("================================================="); + // a2aExample.runDemo(); + logger.info("Application is ready. Hit /api/a2a/demo to run the A2A demo."); + }; + } +} diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/README.md b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/README.md new file mode 100644 index 00000000..80ee3f54 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/a2a/README.md @@ -0,0 +1,194 @@ +# Spring AI Alibaba A2A 一体化演示(注册 -> 发现 -> 调用) + +本示例演示如何在一个应用中完成: +- 本地 ReactAgent 的创建与对外暴露(A2A Server 自动导出) +- **使用 Nacos Registry 进行 Agent 注册** +- 使用 Nacos Discovery 进行 Agent 发现 +- 通过 AgentCardProvider 发现并以 A2aRemoteAgent 方式调用 + +## 架构说明 + +### A2A 注册与发现流程 + +1. **本地 Agent 创建**:`A2AAgentConfig` 创建 ReactAgent Bean +2. **A2A Server 自动暴露**:Spring Boot 启动时,A2A Server AutoConfiguration 自动: + - 根据 ReactAgent Bean 生成 AgentCard + - 暴露 REST API 端点(`/.well-known/agent.json` 和 `/a2a/message`) +3. **Nacos Registry 注册**:配置 `registry.enabled: true` 后: + - 自动将 AgentCard 注册到 Nacos A2A 服务注册表 + - 其他服务可通过 Nacos 发现此 Agent +4. **Nacos Discovery 发现**:配置 `discovery.enabled: true` 后: + - AgentCardProvider 可从 Nacos 查询已注册的 Agent + - 构造 A2aRemoteAgent 进行远程调用 + +## 目录 +- `A2AAgentConfig.java`:定义本地 ReactAgent(data_analysis_agent) +- `A2AExample.java`:一体化演示入口,完成本地调用、发现、远程调用 +- `A2AExampleController.java`:提供 `/api/a2a/demo` HTTP 入口 +- `application.yml`:**A2A server、Nacos Registry 和 Nacos Discovery 配置** + +## 配置说明 + +### 关键配置项(application.yml) + +```yaml +spring: + ai: + alibaba: + a2a: + nacos: + server-addr: 127.0.0.1:8848 + username: nacos + password: nacos + discovery: + enabled: true # 启用服务发现(查询其他 Agent) + registry: + enabled: true # 启用服务注册(注册本地 Agent) + server: + version: 1.0.0 + card: + name: data_analysis_agent + description: 专门用于数据分析和统计计算的本地智能体 + provider: + name: Spring AI Alibaba Documentation + organization: Spring AI Alibaba +``` + +**重要**: +- `registry.enabled: true` - 必须启用才能将 Agent 注册到 Nacos +- `discovery.enabled: true` - 启用后才能通过 AgentCardProvider 发现其他 Agent +- `server.card` - 定义注册到 Nacos 的 AgentCard 元数据 + +## 运行 + +### 1) 准备环境 + +```bash +export DASHSCOPE_API_KEY=your-api-key +export NACOS_SERVER_ADDR=127.0.0.1:8848 +export NACOS_USERNAME=nacos +export NACOS_PASSWORD=nacos +``` + +确保本地有可用的 Nacos(可用 Docker 启动): +```bash +docker run --name nacos -d -p 8848:8848 -e MODE=standalone nacos/nacos-server:latest +``` + +### 2) 启动应用 + +```bash +mvn -q -pl examples/documentation -am spring-boot:run +``` + +### 3) 访问演示接口 + +```bash +curl http://localhost:8080/api/a2a/demo +``` + +### 4) 验证注册和发现 + +**本地 AgentCard**: +```bash +curl http://localhost:8080/.well-known/agent.json +``` + +**Nacos 控制台**: +- 打开 http://localhost:8848/nacos +- 登录(nacos/nacos) +- 查看 A2A 服务注册维度,应该能看到 `data_analysis_agent` + +**通过 API 查询 Nacos 中的 Agent**: +```bash +# 需要 Nacos A2A API(如果启用) +curl "http://localhost:8848/nacos/v1/ai/a2a/agent?agentName=data_analysis_agent" +``` + +## 关键代码 + +### 注册逻辑(自动) + +1. **定义 ReactAgent Bean**(`A2AAgentConfig.java`): +```java +@Bean(name = "dataAnalysisAgent") +public ReactAgent dataAnalysisAgent(@Qualifier("dashscopeChatModel") ChatModel chatModel) { + return ReactAgent.builder() + .name("data_analysis_agent") + .model(chatModel) + .description("专门用于数据分析和统计计算的本地智能体") + .instruction("你是一个专业的数据分析专家...") + .outputKey("messages") + .build(); +} +``` + +2. **配置 A2A Server 和 Nacos Registry**(`application.yml`): + - `spring.ai.alibaba.a2a.server.card` - 定义 AgentCard + - `spring.ai.alibaba.a2a.nacos.registry.enabled: true` - 启用注册 + +3. **自动注册流程**: + - Spring Boot 启动 → A2A Server AutoConfiguration 检测到 ReactAgent Bean + - 生成 AgentCard → 注册到 Nacos A2A Registry + - 暴露 REST API 端点 + +### 发现与调用逻辑(`A2AExample.java`) + +```java +// 1. 通过 AgentCardProvider 发现 Agent +A2aRemoteAgent remote = A2aRemoteAgent.builder() + .name("data_analysis_agent") + .agentCardProvider(agentCardProvider) // 自动从 Nacos 获取 AgentCard + .description("数据分析远程代理") + .build(); + +// 2. 远程调用 +Optional result = remote.invoke("请根据季度数据给出同比与环比分析概要。"); +``` + +## 注册与发现的区别 + +| 功能 | 配置项 | 作用 | 本示例中的角色 | +|------|--------|------|----------------| +| **Registry(注册)** | `registry.enabled: true` | 将本地 Agent 注册到 Nacos | 本应用作为 **服务提供者** | +| **Discovery(发现)** | `discovery.enabled: true` | 从 Nacos 查询其他 Agent | 本应用作为 **服务消费者** | + +本示例同时启用了两者,因此: +- 作为**提供者**:注册 `data_analysis_agent` 到 Nacos +- 作为**消费者**:可发现并调用其他已注册的 Agent(包括自己) + +## 注意事项 + +1. **依赖要求**: + - 需要添加 `spring-ai-alibaba-starter-a2a-nacos` 依赖 + - 确保 Nacos 服务正常运行 + +2. **Registry vs Discovery**: + - `registry.enabled: true` - 注册本地 Agent + - `discovery.enabled: true` - 发现远程 Agent + - 两者可独立配置,也可同时启用 + +3. **多 Agent 注册**: + - 默认情况下,只有一个 Agent Bean 会被注册 + - 如需注册多个 Agent,需运行多个应用实例,每个实例配置不同的 Agent + +4. **AgentCard 元数据**: + - `server.card.name` 必须与 ReactAgent Bean 的 `name` 一致 + - `server.card.provider` 可选,用于标识 Agent 提供者信息 + +## 故障排查 + +### Agent 没有注册到 Nacos +- 检查 `registry.enabled: true` 是否配置 +- 查看应用日志,确认 Nacos Registry AutoConfiguration 是否生效 +- 验证 Nacos 连接配置(server-addr、username、password) + +### AgentCardProvider 无法发现 Agent +- 检查 `discovery.enabled: true` 是否配置 +- 确认 Agent 已成功注册到 Nacos +- 验证 agent name 是否匹配 + +### 远程调用失败 +- 确认目标 Agent 的 REST API 端点可访问 +- 检查网络连接和防火墙配置 +- 查看 A2A 消息传输日志 diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/toolselection/README.md b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/toolselection/README.md new file mode 100644 index 00000000..b8a4e024 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/toolselection/README.md @@ -0,0 +1,275 @@ +# ToolSelectionInterceptor 工具选择拦截器使用指南 + +## 概述 + +`ToolSelectionInterceptor` 是 Spring AI Alibaba Agent Framework 中的一个模型拦截器,用于在 Agent 拥有大量工具时,通过 LLM 智能选择最相关的工具。这可以减少 token 使用量,并帮助主模型专注于正确的工具。 + +### 增强功能:工具描述支持 + +在最新版本中,`ToolSelectionInterceptor` 增加了对**工具描述**的支持。当进行工具选择时,不仅会传递工具名称,还会传递工具描述给选择模型,从而大幅提高工具选择的准确性。 + +**改进前(仅工具名称):** +``` +- searchProducts +- getOrderDetails +- updateInventory +``` + +**改进后(包含工具描述):** +``` +- searchProducts: 按名称、类别或价格范围搜索产品 +- getOrderDetails: 根据订单ID获取订单详情 +- updateInventory: 更新产品库存数量 +``` + +--- + +## 快速开始 + +### 1. 基本用法 + +```java +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.interceptor.toolselection.ToolSelectionInterceptor; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.tool.ToolCallback; + +// 创建工具选择拦截器 +ToolSelectionInterceptor toolSelectionInterceptor = ToolSelectionInterceptor.builder() + .selectionModel(chatModel) // 用于选择工具的模型 + .maxTools(3) // 每次最多选择3个工具 + .build(); + +// 创建 ReactAgent 并配置拦截器 +ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .tools(weatherTool, ticketTool, hotelTool, mapTool, translatorTool) + .interceptors(toolSelectionInterceptor) + .build(); + +// 调用 Agent +Optional result = agent.invoke("帮我查询北京的天气"); +``` + +### 2. 使用 alwaysInclude 确保关键工具始终可用 + +```java +ToolSelectionInterceptor interceptor = ToolSelectionInterceptor.builder() + .selectionModel(chatModel) + .maxTools(3) + .alwaysInclude("error_handler", "logging_tool") // 这些工具始终包含 + .build(); +``` + +### 3. 自定义系统提示词 + +```java +ToolSelectionInterceptor interceptor = ToolSelectionInterceptor.builder() + .selectionModel(chatModel) + .maxTools(3) + .systemPrompt("你是一个工具选择专家,请根据用户查询选择最相关的工具。优先选择能直接解决用户问题的工具。") + .build(); +``` + +--- + +## 工具定义最佳实践 + +为了让 `ToolSelectionInterceptor` 更准确地选择工具,请遵循以下最佳实践: + +### 1. 清晰的工具名称 + +```java +// 好的命名 +@Tool(name = "searchProducts") +@Tool(name = "getUserProfile") +@Tool(name = "sendEmailNotification") + +// 不好的命名 +@Tool(name = "search") // 太模糊 +@Tool(name = "tool1") // 无意义 +@Tool(name = "doSomething") // 不明确 +``` + +### 2. 详细的工具描述 + +```java +// 好的描述 +@Tool(name = "searchProducts", + description = "按名称、类别或价格范围搜索产品。" + + "返回匹配产品的列表,包含名称、价格和库存信息。" + + "适用于用户想要查找或浏览产品的场景。") + +// 不好的描述 +@Tool(name = "search", description = "搜索") // 太简短,缺乏上下文 +``` + +### 3. 参数描述 + +```java +@Tool(name = "searchProducts", + description = "按条件搜索产品") +public List searchProducts( + @ToolParam(description = "产品名称关键词,支持模糊匹配") String keyword, + @ToolParam(description = "产品类别,如'电子产品'、'服装'") String category, + @ToolParam(description = "价格范围上限") Double maxPrice) { + // implementation +} +``` + +--- + +## 配置参数说明 + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `selectionModel` | ChatModel | 是 | 用于选择工具的 LLM 模型 | +| `maxTools` | Integer | 否 | 每次最多选择的工具数量。如果工具总数 <= maxTools,则跳过选择 | +| `systemPrompt` | String | 否 | 自定义系统提示词,默认为 "Your goal is to select the most relevant tools for answering the user's query." | +| `alwaysInclude` | Set | 否 | 始终包含的工具名称列表,不受 maxTools 限制 | + +--- + +## 工作原理 + +### 数据流程 + +``` +1. Agent 接收用户查询 + ↓ +2. AgentLlmNode 提取所有工具的名称和描述 + ↓ +3. 构建 ModelRequest,包含: + - tools: List (工具名称列表) + - toolDescriptions: Map (工具名称→描述映射) + ↓ +4. ToolSelectionInterceptor 检查工具数量 + - 如果 tools.size() <= maxTools,跳过选择,直接传递 + - 否则,调用选择模型 + ↓ +5. 构建选择提示词,格式为: + "- toolName1: description1 + - toolName2: description2 + ..." + ↓ +6. 选择模型返回 JSON: {"tools": ["tool1", "tool2"]} + ↓ +7. 过滤工具列表,只保留选中的工具 + ↓ +8. 将过滤后的工具传递给主模型 +``` + +### 选择逻辑 + +```java +// 伪代码 +if (availableTools.size() <= maxTools) { + // 工具数量不超过限制,跳过选择 + return handler.call(request); +} + +// 构建工具列表(包含描述) +StringBuilder toolList = new StringBuilder(); +for (String toolName : toolNames) { + toolList.append("- ").append(toolName); + String description = toolDescriptions.get(toolName); + if (description != null && !description.isEmpty()) { + toolList.append(": ").append(description); + } + toolList.append("\n"); +} + +// 调用选择模型 +Set selectedTools = selectTools(toolList, userQuery); + +// 过滤并传递 +ModelRequest filteredRequest = ModelRequest.builder(request) + .tools(selectedTools) + .build(); +return handler.call(filteredRequest); +``` + +--- + +## 常见问题 + +### Q1: 什么时候应该使用 ToolSelectionInterceptor? + +当你的 Agent 有超过 5-10 个工具时,建议使用此拦截器。过多的工具会: +- 增加 token 消耗 +- 降低主模型选择正确工具的准确性 +- 增加响应延迟 + +### Q2: selectionModel 应该用什么模型? + +建议使用轻量级、快速的模型(如 qwen-turbo),因为工具选择任务相对简单,不需要最强大的模型。 + +### Q3: maxTools 应该设置多少? + +- 简单任务:2-3 个 +- 中等复杂任务:3-5 个 +- 复杂任务:5-7 个 + +### Q4: 工具描述为空会怎样? + +如果某个工具没有描述,只会显示工具名称,不会导致错误。但建议为所有工具添加描述以提高选择准确性。 + +### Q5: 如何调试工具选择? + +启用 DEBUG 日志级别,可以看到选择了哪些工具: + +```properties +logging.level.com.alibaba.cloud.ai.graph.agent.interceptor.toolselection=DEBUG +``` + +日志示例: +``` +INFO ToolSelectionInterceptor - Selected 2 tools from 5 available: [weather_tool, map_tool] +``` + +--- + +## 目录 + +- `ToolSelectionExample.java`:完整示例代码,包含基础用法、alwaysInclude、自定义提示词、多工具场景 + +--- + +## 运行示例 + +### 1) 准备环境 + +```bash +export AI_DASHSCOPE_API_KEY=your-api-key +``` + +### 2) 运行示例 + +```bash +mvn -q -pl examples/documentation -am exec:java \ + -Dexec.mainClass="com.alibaba.cloud.ai.examples.documentation.framework.advanced.toolselection.ToolSelectionExample" +``` + +--- + +## 相关类 + +| 类名 | 路径 | 说明 | +|------|------|------| +| `ToolSelectionInterceptor` | `agent-framework/.../interceptor/toolselection/ToolSelectionInterceptor.java` | 工具选择拦截器 | +| `ModelRequest` | `agent-framework/.../interceptor/ModelRequest.java` | 模型请求对象,包含工具名称和描述 | +| `ModelInterceptor` | `agent-framework/.../interceptor/ModelInterceptor.java` | 拦截器基类 | +| `AgentLlmNode` | `agent-framework/.../node/AgentLlmNode.java` | LLM 节点,负责提取工具信息 | + +--- + +## 版本历史 + +| 版本 | 变更 | +|------|------| +| 1.1.0.0-RC2 | 新增 `toolDescriptions` 支持,提高工具选择准确性 | + +--- + +*文档更新日期: 2025-12-17* diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/toolselection/ToolSelectionExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/toolselection/ToolSelectionExample.java new file mode 100644 index 00000000..3c1fc926 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/advanced/toolselection/ToolSelectionExample.java @@ -0,0 +1,366 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.advanced.toolselection; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.interceptor.toolselection.ToolSelectionInterceptor; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; + +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.ai.tool.annotation.ToolParam; + +import java.util.List; +import java.util.Optional; + +/** + * ToolSelectionInterceptor 示例 + * + * 本示例演示如何使用 ToolSelectionInterceptor 进行智能工具选择。 + * + * 核心功能: + * 1. 当 Agent 有多个工具时,使用 LLM 智能选择最相关的工具 + * 2. 工具描述会自动传递给选择模型,提高选择准确性 + * 3. 可配置 maxTools 限制每次选择的工具数量 + * 4. 支持 alwaysInclude 确保关键工具始终可用 + * + * 使用场景: + * - Agent 拥有大量工具(>5个),需要减少 token 消耗 + * - 需要提高工具选择的准确性 + * - 不同查询需要不同的工具子集 + */ +public class ToolSelectionExample { + + // ==================== 示例1:基础用法 ==================== + + /** + * 基础用法:创建带有工具选择的 Agent + */ + public static void basicToolSelection() throws Exception { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建工具类实例 + TravelTools travelTools = new TravelTools(); + + // 创建 ToolSelectionInterceptor + // 当工具数量超过 maxTools 时,会使用 LLM 选择最相关的工具 + ToolSelectionInterceptor interceptor = ToolSelectionInterceptor.builder() + .selectionModel(chatModel) // 用于选择工具的模型 + .maxTools(3) // 最多选择3个工具 + .build(); + + // 创建 Agent + ReactAgent agent = ReactAgent.builder() + .name("travel_assistant") + .model(chatModel) + .methodTools(travelTools) // 自动扫描 @Tool 注解的方法 + .interceptors(interceptor) + .saver(new MemorySaver()) + .build(); + + // 调用 Agent - 会自动选择最相关的工具 + Optional result = agent.invoke("北京今天天气怎么样?"); + printResult(result, "基础用法"); + } + + // ==================== 示例2:使用 alwaysInclude ==================== + + /** + * 高级用法:使用 alwaysInclude 确保关键工具始终可用 + */ + public static void toolSelectionWithAlwaysInclude() throws Exception { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + TravelTools travelTools = new TravelTools(); + + // 使用 alwaysInclude 确保某些工具始终可用 + ToolSelectionInterceptor interceptor = ToolSelectionInterceptor.builder() + .selectionModel(chatModel) + .maxTools(2) + .alwaysInclude("get_weather") // 天气工具始终包含 + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("travel_assistant") + .model(chatModel) + .methodTools(travelTools) + .interceptors(interceptor) + .saver(new MemorySaver()) + .build(); + + // 即使查询与天气无关,weather 工具也会被包含 + Optional result = agent.invoke("帮我预订一张去上海的机票"); + printResult(result, "alwaysInclude 示例"); + } + + // ==================== 示例3:自定义系统提示词 ==================== + + /** + * 高级用法:自定义工具选择的系统提示词 + */ + public static void toolSelectionWithCustomPrompt() throws Exception { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + TravelTools travelTools = new TravelTools(); + + // 自定义选择逻辑的系统提示词 + String customPrompt = """ + 你是一个旅行助手的工具选择器。 + 根据用户的查询,选择最相关的工具来帮助回答问题。 + + 选择原则: + 1. 优先选择能直接解决用户问题的工具 + 2. 如果用户询问多个方面,选择覆盖所有方面的工具 + 3. 避免选择明显不相关的工具 + """; + + ToolSelectionInterceptor interceptor = ToolSelectionInterceptor.builder() + .selectionModel(chatModel) + .maxTools(3) + .systemPrompt(customPrompt) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("travel_assistant") + .model(chatModel) + .methodTools(travelTools) + .interceptors(interceptor) + .saver(new MemorySaver()) + .build(); + + Optional result = agent.invoke("我下周要去杭州旅游,帮我看看天气和景点"); + printResult(result, "自定义提示词示例"); + } + + // ==================== 示例4:多工具场景 ==================== + + /** + * 复杂场景:拥有多个工具的 Agent + */ + public static void multiToolScenario() throws Exception { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建多个工具类 + TravelTools travelTools = new TravelTools(); + UtilityTools utilityTools = new UtilityTools(); + + // 配置工具选择 + ToolSelectionInterceptor interceptor = ToolSelectionInterceptor.builder() + .selectionModel(chatModel) + .maxTools(3) // 从8+个工具中选择3个 + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("smart_assistant") + .model(chatModel) + .methodTools(travelTools, utilityTools) // 注册多个工具类 + .interceptors(interceptor) + .saver(new MemorySaver()) + .build(); + + // 测试不同的查询 + System.out.println("\n--- 测试1:天气查询 ---"); + Optional result1 = agent.invoke("北京今天天气如何?"); + printResult(result1, "天气查询"); + + System.out.println("\n--- 测试2:机票查询 ---"); + Optional result2 = agent.invoke("查一下明天从上海到北京的机票"); + printResult(result2, "机票查询"); + + System.out.println("\n--- 测试3:货币转换 ---"); + Optional result3 = agent.invoke("100美元能换多少人民币?"); + printResult(result3, "货币转换"); + + System.out.println("\n--- 测试4:复合查询 ---"); + Optional result4 = agent.invoke("我要去杭州旅游,帮我看看天气、推荐景点、再找个酒店"); + printResult(result4, "复合查询"); + } + + // ==================== 工具类定义 ==================== + + /** + * 旅行相关工具 + * + * 注意:工具描述要详细、准确,这样 ToolSelectionInterceptor 才能做出正确选择 + */ + public static class TravelTools { + + @Tool(name = "get_weather", + description = "获取指定城市的实时天气信息,包括温度、湿度、天气状况和空气质量。" + + "当用户询问某个城市的天气时使用此工具。") + public String getWeather( + @ToolParam(description = "城市名称,如:北京、上海、广州") String city) { + return String.format("%s今日天气:晴,温度 18-25°C,湿度 45%%,空气质量良好。", city); + } + + @Tool(name = "search_flights", + description = "搜索两个城市之间的航班信息,返回航班号、出发时间、到达时间和票价。" + + "当用户想要查询或预订机票时使用此工具。") + public String searchFlights( + @ToolParam(description = "出发城市") String from, + @ToolParam(description = "到达城市") String to, + @ToolParam(description = "出发日期,格式:YYYY-MM-DD") String date) { + return String.format("找到 %s 到 %s 的航班(%s):\n" + + "1. CA1234 08:00-10:30 ¥680\n" + + "2. MU5678 12:00-14:30 ¥720\n" + + "3. CZ9012 18:00-20:30 ¥650", from, to, date); + } + + @Tool(name = "search_hotels", + description = "搜索指定城市的酒店,可按入住日期和价格范围筛选。" + + "当用户想要预订住宿时使用此工具。") + public String searchHotels( + @ToolParam(description = "城市名称") String city, + @ToolParam(description = "入住日期,格式:YYYY-MM-DD") String arrivalDate) { + return String.format("%s 酒店推荐(%s 入住):\n" + + "1. 希尔顿酒店 ★★★★★ ¥800/晚\n" + + "2. 如家酒店 ★★★ ¥280/晚\n" + + "3. 民宿小院 ★★★★ ¥450/晚", city, arrivalDate); + } + + @Tool(name = "get_attractions", + description = "获取指定城市的热门旅游景点列表,包括景点介绍、门票价格和推荐游览时间。" + + "当用户想要了解旅游目的地的景点时使用此工具。") + public String getAttractions( + @ToolParam(description = "城市名称") String city) { + return String.format("%s 热门景点:\n" + + "1. 西湖 - 免费,建议游览半天\n" + + "2. 灵隐寺 - 门票¥45,上香另付\n" + + "3. 宋城 - 门票¥300,含演出", city); + } + + @Tool(name = "search_restaurants", + description = "搜索指定城市的餐厅,可按菜系和价格范围筛选。" + + "当用户想要找地方吃饭或了解当地美食时使用此工具。") + public String searchRestaurants( + @ToolParam(description = "城市名称") String city, + @ToolParam(description = "菜系类型,如:火锅、川菜、粤菜等") String cuisine) { + return String.format("%s %s 餐厅推荐:\n" + + "1. 老字号餐厅 - 人均¥80 评分4.8\n" + + "2. 网红打卡店 - 人均¥120 评分4.5\n" + + "3. 本地特色馆 - 人均¥60 评分4.7", city, cuisine); + } + } + + /** + * 实用工具类 + */ + public static class UtilityTools { + + @Tool(name = "convert_currency", + description = "货币汇率转换,支持多种货币之间的转换(如 USD、EUR、CNY、JPY)。" + + "当用户需要了解汇率或进行货币换算时使用此工具。") + public String convertCurrency( + @ToolParam(description = "金额") double amount, + @ToolParam(description = "源货币代码,如 USD, EUR, CNY") String from, + @ToolParam(description = "目标货币代码") String to) { + double rate = 7.2; // 简化的汇率 + if ("USD".equals(from) && "CNY".equals(to)) { + return String.format("%.2f 美元 = %.2f 人民币(汇率: 1 USD = %.2f CNY)", + amount, amount * rate, rate); + } + return String.format("%.2f %s = %.2f %s", amount, from, amount, to); + } + + @Tool(name = "translate_text", + description = "文本翻译服务,支持中英日韩等多种语言互译。" + + "当用户需要翻译文字或了解外语含义时使用此工具。") + public String translateText( + @ToolParam(description = "要翻译的文本") String text, + @ToolParam(description = "目标语言:中文、英文、日文、韩文") String targetLang) { + return String.format("翻译结果(%s):[翻译后的内容]", targetLang); + } + + @Tool(name = "calculate", + description = "数学计算器,支持加减乘除、幂运算、百分比等计算。" + + "当用户需要进行数学计算时使用此工具。") + public String calculate( + @ToolParam(description = "数学表达式,如:100*1.1、50+30") String expression) { + return "计算结果:" + expression + " = [结果]"; + } + } + + // ==================== 辅助方法 ==================== + + private static void printResult(Optional result, String testName) { + System.out.println("[" + testName + "] 执行结果:"); + result.ifPresent(state -> { + List messages = state.value("messages", List.of()); + for (Message msg : messages) { + if (msg instanceof AssistantMessage) { + System.out.println("助手: " + msg.getText()); + } + } + }); + } + + // ==================== Main 方法 ==================== + + public static void main(String[] args) { + System.out.println("=== ToolSelectionInterceptor 示例 ==="); + System.out.println("注意:需要设置 AI_DASHSCOPE_API_KEY 环境变量\n"); + + try { + System.out.println("\n--- 示例1:基础用法 ---"); + basicToolSelection(); + + System.out.println("\n--- 示例2:使用 alwaysInclude ---"); + toolSelectionWithAlwaysInclude(); + + System.out.println("\n--- 示例3:自定义系统提示词 ---"); + toolSelectionWithCustomPrompt(); + + System.out.println("\n--- 示例4:多工具场景 ---"); + multiToolScenario(); + + System.out.println("\n=== 所有示例执行完成 ==="); + } + catch (Exception e) { + System.err.println("执行示例时发生错误: " + e.getMessage()); + e.printStackTrace(); + } + } + +} diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/AgentsExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/AgentsExample.java new file mode 100644 index 00000000..4e029a5b --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/AgentsExample.java @@ -0,0 +1,840 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.tutorials; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; +import com.alibaba.cloud.ai.graph.NodeOutput; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.hook.AgentHook; +import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; +import com.alibaba.cloud.ai.graph.agent.hook.HookPositions; +import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand; +import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesModelHook; +import com.alibaba.cloud.ai.graph.agent.hook.messages.UpdatePolicy; +import com.alibaba.cloud.ai.graph.agent.interceptor.*; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.converter.BeanOutputConverter; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.annotation.ToolParam; +import org.springframework.ai.tool.function.FunctionToolCallback; +import reactor.core.publisher.Flux; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; + +/** + * Agents Tutorial - agents.md + */ +public class AgentsExample { + + // ==================== 基础模型配置 ==================== + + /** + * 示例1:基础模型配置 + */ + public static void basicModelConfiguration() { + // 创建 DashScope API 实例 + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + // 创建 ChatModel + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建 Agent + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .build(); + } + + /** + * 示例2:高级模型配置 + */ + public static void advancedModelConfiguration() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .defaultOptions(DashScopeChatOptions.builder() + .temperature(0.7) // 控制随机性 + .maxToken(2000) // 最大输出长度 + .topP(0.9) // 核采样参数 + .enableThinking(true) + .build()) + .build(); + } + + // ==================== 工具定义 ==================== + + public static void toolUsage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建工具回调 + ToolCallback searchTool = FunctionToolCallback + .builder("search", new SearchTool()) + .description("搜索信息的工具") + .inputType(String.class) + .build(); + + // 使用多个工具 + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .tools(searchTool) + .build(); + } + + /** + * 示例5:基础 System Prompt + */ + public static void basicSystemPrompt() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .systemPrompt("你是一个专业的技术助手。请准确、简洁地回答问题。") + .build(); + } + + /** + * 示例6:使用 instruction + */ + public static void instructionUsage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + String instruction = """ + 你是一个经验丰富的软件架构师。 + + 在回答问题时,请: + 1. 首先理解用户的核心需求 + 2. 分析可能的技术方案 + 3. 提供清晰的建议和理由 + 4. 如果需要更多信息,主动询问 + + 保持专业、友好的语气。 + """; + + ReactAgent agent = ReactAgent.builder() + .name("architect_agent") + .model(chatModel) + .instruction(instruction) + .build(); + } + + // ==================== System Prompt ==================== + + public static void dynamicSystemPrompt() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("adaptive_agent") + .model(chatModel) + .interceptors(new DynamicPromptInterceptor()) + .build(); + } + + /** + * 示例8:基础调用 + */ + public static void basicInvocation() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .build(); + + // 字符串输入 + AssistantMessage response = agent.call("杭州的天气怎么样?"); + System.out.println(response.getText()); + + // UserMessage 输入 + UserMessage userMessage = new UserMessage("帮我分析这个问题"); + AssistantMessage response2 = agent.call(userMessage); + + // 多个消息 + List messages = List.of( + new UserMessage("我想了解 Java 多线程"), + new UserMessage("特别是线程池的使用") + ); + AssistantMessage response3 = agent.call(messages); + } + + /** + * 示例9:获取完整状态 + */ + public static void getFullState() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .build(); + + Optional result = agent.invoke("帮我写一首诗"); + + if (result.isPresent()) { + OverAllState state = result.get(); + + // 访问消息历史 + Optional messages = state.value("messages"); + List messageList = (List) messages.get(); + + // 访问自定义状态 + Optional customData = state.value("custom_key"); + + System.out.println("完整状态:" + state); + } + } + + /** + * 示例10:使用配置 + */ + public static void useConfiguration() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .build(); + + String threadId = "thread_123"; + RunnableConfig runnableConfig = RunnableConfig.builder() + .threadId(threadId) + .addMetadata("key", "value") + .build(); + + AssistantMessage response = agent.call("你的问题", runnableConfig); + } + + // ==================== 调用 Agent ==================== + + /** + * 示例10.1:流式调用 - 基础用法 + */ + public static void basicStreamInvocation() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("streaming_agent") + .model(chatModel) + .build(); + + // 流式输出 + Flux stream = agent.stream("帮我写一首关于春天的诗"); + + stream.subscribe( + output -> { + // 处理每个节点输出 + System.out.println("节点: " + output.node()); + System.out.println("Agent: " + output.agent()); + if (output.tokenUsage() != null) { + System.out.println("Token使用: " + output.tokenUsage()); + } + }, + error -> System.err.println("错误: " + error.getMessage()), + () -> System.out.println("流式输出完成") + ); + } + + /** + * 示例10.2:流式调用 - 高级用法 + */ + public static void advancedStreamInvocation() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("streaming_agent") + .model(chatModel) + .saver(new MemorySaver()) + .build(); + + RunnableConfig config = RunnableConfig.builder() + .threadId("stream_thread_1") + .build(); + + // 使用配置的流式调用 + Flux stream = agent.stream(new UserMessage("解释一下量子计算"), config); + + // 使用 doOnNext 处理中间输出 + stream.doOnNext(output -> { + if (!output.isSTART() && !output.isEND()) { + System.out.println("处理中..."); + System.out.println("当前节点: " + output.node()); + } + }) + .doOnComplete(() -> System.out.println("所有节点处理完成")) + .doOnError(e -> System.err.println("流处理错误: " + e.getMessage())) + .blockLast(); // 阻塞等待完成 + } + + /** + * 示例10.3:流式调用 - 收集所有输出 + */ + public static void collectStreamOutputs() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("streaming_agent") + .model(chatModel) + .build(); + + Flux stream = agent.stream("分析机器学习的应用场景"); + + // 收集所有输出 + List outputs = stream.collectList().block(); + + if (outputs != null) { + System.out.println("总共收到 " + outputs.size() + " 个节点输出"); + + // 获取最终输出 + NodeOutput lastOutput = outputs.get(outputs.size() - 1); + System.out.println("最终状态: " + lastOutput.state()); + + // 获取消息 + Optional messages = lastOutput.state().value("messages"); + if (messages.isPresent()) { + List messageList = (List) messages.get(); + Message lastMessage = messageList.get(messageList.size() - 1); + if (lastMessage instanceof AssistantMessage assistantMsg) { + System.out.println("最终回复: " + assistantMsg.getText()); + } + } + } + } + + public static void structuredOutputWithType() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("poem_agent") + .model(chatModel) + .outputType(PoemOutput.class) + .saver(new MemorySaver()) + .build(); + + AssistantMessage response = agent.call("写一首关于春天的诗"); + // 输出会遵循 PoemOutput 的结构 + System.out.println(response.getText()); + } + + /** + * 示例12:使用 outputSchema + */ + public static void structuredOutputWithSchema() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // Use BeanOutputConverter to generate outputSchema + BeanOutputConverter outputConverter = new BeanOutputConverter<>(TextAnalysisResult.class); + String format = outputConverter.getFormat(); + + ReactAgent agent = ReactAgent.builder() + .name("analysis_agent") + .model(chatModel) + .outputSchema(format) + .saver(new MemorySaver()) + .build(); + + AssistantMessage response = agent.call("分析这段文本:春天来了,万物复苏。"); + } + + /** + * 示例13:配置记忆 + */ + public static void configureMemory() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 配置内存存储 + ReactAgent agent = ReactAgent.builder() + .name("chat_agent") + .model(chatModel) + .saver(new MemorySaver()) + .build(); + + // 使用 thread_id 维护对话上下文 + RunnableConfig config = RunnableConfig.builder() + .threadId("user_123") + .build(); + + agent.call("我叫张三", config); + agent.call("我叫什么名字?", config); // 输出: "你叫张三" + } + + // ==================== 结构化输出 ==================== + + public static void main(String[] args) { + System.out.println("=== Agents Tutorial Examples ==="); + System.out.println("注意:需要设置 AI_DASHSCOPE_API_KEY 环境变量\n"); + + try { + // System.out.println("\n--- 示例1:基础模型配置 ---"); + // basicModelConfiguration(); + // + // System.out.println("\n--- 示例2:高级模型配置 ---"); + // advancedModelConfiguration(); + // + // System.out.println("\n--- 示例3:工具使用 ---"); + // toolUsage(); + // + // System.out.println("\n--- 示例5:基础 System Prompt ---"); + // basicSystemPrompt(); + // + // System.out.println("\n--- 示例6:使用 instruction ---"); + // instructionUsage(); + // + // System.out.println("\n--- 示例7:动态 System Prompt ---"); + // dynamicSystemPrompt(); + // + // System.out.println("\n--- 示例8:基础调用 ---"); + // basicInvocation(); + // + // System.out.println("\n--- 示例9:获取完整状态 ---"); + // getFullState(); + // + // System.out.println("\n--- 示例10:使用配置 ---"); + useConfiguration(); + // + // System.out.println("\n--- 示例10.1:流式调用 - 基础用法 ---"); + // basicStreamInvocation(); + // + // System.out.println("\n--- 示例10.2:流式调用 - 高级用法 ---"); + // advancedStreamInvocation(); + // + // System.out.println("\n--- 示例10.3:流式调用 - 收集所有输出 ---"); + // collectStreamOutputs(); + // + // System.out.println("\n--- 示例11:使用 outputType ---"); + // structuredOutputWithType(); + // + // System.out.println("\n--- 示例12:使用 outputSchema ---"); + // structuredOutputWithSchema(); + // + // System.out.println("\n--- 示例13:配置记忆 ---"); + // configureMemory(); + + System.out.println("\n=== 所有示例执行完成 ==="); + } + catch (Exception e) { + System.err.println("发生未预期的错误: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 示例3:定义和使用工具 + */ + public static class SearchTool implements BiFunction { + @Override + public String apply( + @ToolParam(description = "搜索关键词") String query, + ToolContext toolContext) { + return "搜索结果:" + query; + } + } + + /** + * 示例4:工具错误处理 + */ + public static class ToolErrorInterceptor extends ToolInterceptor { + @Override + public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) { + try { + return handler.call(request); + } + catch (Exception e) { + return ToolCallResponse.of(request.getToolCallId(), request.getToolName(), + "Tool failed: " + e.getMessage()); + } + } + + @Override + public String getName() { + return "ToolErrorInterceptor"; + } + } + + // ==================== Memory ==================== + + /** + * 示例7:动态 System Prompt + */ + public static class DynamicPromptInterceptor extends ModelInterceptor { + @Override + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { + // 基于上下文动态调整 system prompt + Map context = request.getContext(); + + // 根据上下文构建动态提示词 + String dynamicPrompt = buildDynamicPrompt(context); + + // 增强 system message + SystemMessage enhancedSystemMessage; + if (request.getSystemMessage() == null) { + enhancedSystemMessage = new SystemMessage(dynamicPrompt); + } + else { + enhancedSystemMessage = new SystemMessage( + request.getSystemMessage().getText() + "\n\n" + dynamicPrompt + ); + } + + // 创建增强的请求 + ModelRequest modifiedRequest = ModelRequest.builder(request) + .systemMessage(enhancedSystemMessage) + .build(); + + return handler.call(modifiedRequest); + } + + private String buildDynamicPrompt(Map context) { + // 示例:根据用户角色动态生成提示词 + String userRole = (String) context.getOrDefault("user_role", "default"); + + return switch (userRole) { + case "expert" -> """ + 你正在与技术专家对话。 + - 使用专业术语 + - 深入技术细节 + - 提供高级建议 + """; + case "beginner" -> """ + 你正在与初学者对话。 + - 使用简单易懂的语言 + - 详细解释概念 + - 提供入门级建议 + """; + default -> """ + 你是一个专业的助手。 + - 根据问题复杂度调整回答 + - 保持友好和专业 + """; + }; + } + + @Override + public String getName() { + return "DynamicPromptInterceptor"; + } + } + + // ==================== Hooks ==================== + + /** + * 示例11:使用 outputType + */ + public static class PoemOutput { + private String title; + private String content; + private String style; + + // Getters and Setters + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getStyle() { + return style; + } + + public void setStyle(String style) { + this.style = style; + } + } + + /** + * 示例12:文本分析结果输出类 + */ + public static class TextAnalysisResult { + private String summary; + private List keywords; + private String sentiment; + private Double confidence; + + // Getters and Setters + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public List getKeywords() { + return keywords; + } + + public void setKeywords(List keywords) { + this.keywords = keywords; + } + + public String getSentiment() { + return sentiment; + } + + public void setSentiment(String sentiment) { + this.sentiment = sentiment; + } + + public Double getConfidence() { + return confidence; + } + + public void setConfidence(Double confidence) { + this.confidence = confidence; + } + } + + /** + * 示例14:AgentHook - 在 Agent 开始/结束时执行 + */ + public static class LoggingHook extends AgentHook { + @Override + public String getName() { + return "logging"; + } + + @Override + public HookPosition[] getHookPositions() { + return new HookPosition[] { + HookPosition.BEFORE_AGENT, + HookPosition.AFTER_AGENT + }; + } + + @Override + public CompletableFuture> beforeAgent(OverAllState state, RunnableConfig config) { + System.out.println("Agent 开始执行"); + return CompletableFuture.completedFuture(Map.of()); + } + + @Override + public CompletableFuture> afterAgent(OverAllState state, RunnableConfig config) { + System.out.println("Agent 执行完成"); + return CompletableFuture.completedFuture(Map.of()); + } + } + + // ==================== Interceptors ==================== + + /** + * 示例15:MessagesModelHook - 在模型调用前修剪消息 + * 使用 MessagesModelHook 实现,在模型调用前修剪消息列表,只保留最后 MAX_MESSAGES 条消息 + */ + @HookPositions({HookPosition.BEFORE_MODEL}) + public static class MessageTrimmingHook extends MessagesModelHook { + private static final int MAX_MESSAGES = 10; + + @Override + public String getName() { + return "message_trimming"; + } + + @Override + public AgentCommand beforeModel(List previousMessages, RunnableConfig config) { + // 如果消息数量超过限制,只保留最后 MAX_MESSAGES 条消息 + if (previousMessages.size() > MAX_MESSAGES) { + List trimmedMessages = previousMessages.subList( + previousMessages.size() - MAX_MESSAGES, + previousMessages.size() + ); + // 使用 REPLACE 策略替换所有消息 + return new AgentCommand(trimmedMessages, UpdatePolicy.REPLACE); + } + // 如果消息数量未超过限制,返回原始消息(不进行修改) + return new AgentCommand(previousMessages); + } + } + + /** + * 示例16:ModelInterceptor - 内容安全检查 + */ + public static class GuardrailInterceptor extends ModelInterceptor { + @Override + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { + // 前置:检查输入 + if (containsSensitiveContent(request.getMessages())) { + return ModelResponse.of(new AssistantMessage("检测到不适当的内容")); + } + + // 执行调用 + ModelResponse response = handler.call(request); + + // 后置:检查输出 + return sanitizeIfNeeded(response); + } + + private boolean containsSensitiveContent(List messages) { + // 实现敏感内容检测逻辑 + return false; + } + + private ModelResponse sanitizeIfNeeded(ModelResponse response) { + // 实现响应清理逻辑 + return response; + } + + @Override + public String getName() { + return "GuardrailInterceptor"; + } + } + + // ==================== Main 方法 ==================== + + /** + * 示例17:ToolInterceptor - 监控和错误处理 + */ + public static class ToolMonitoringInterceptor extends ToolInterceptor { + @Override + public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) { + long startTime = System.currentTimeMillis(); + try { + ToolCallResponse response = handler.call(request); + logSuccess(request, System.currentTimeMillis() - startTime); + return response; + } + catch (Exception e) { + logError(request, e, System.currentTimeMillis() - startTime); + return ToolCallResponse.of(request.getToolCallId(), request.getToolName(), + "工具执行遇到问题,请稍后重试"); + } + } + + private void logSuccess(ToolCallRequest request, long duration) { + System.out.println("Tool " + request.getToolName() + " succeeded in " + duration + "ms"); + } + + private void logError(ToolCallRequest request, Exception e, long duration) { + System.err.println("Tool " + request.getToolName() + " failed in " + duration + "ms: " + e.getMessage()); + } + + @Override + public String getName() { + return "ToolMonitoringInterceptor"; + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/HooksExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/HooksExample.java new file mode 100644 index 00000000..2c511836 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/HooksExample.java @@ -0,0 +1,650 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.tutorials; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.hook.AgentHook; +import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; +import com.alibaba.cloud.ai.graph.agent.hook.HookPositions; +import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; +import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesModelHook; +import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand; +import com.alibaba.cloud.ai.graph.agent.hook.messages.UpdatePolicy; +import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook; +import com.alibaba.cloud.ai.graph.agent.hook.hip.ToolConfig; +import com.alibaba.cloud.ai.graph.agent.hook.modelcalllimit.ModelCallLimitHook; +import com.alibaba.cloud.ai.graph.agent.hook.pii.PIIDetectionHook; +import com.alibaba.cloud.ai.graph.agent.hook.pii.PIIType; +import com.alibaba.cloud.ai.graph.agent.hook.pii.RedactionStrategy; +import com.alibaba.cloud.ai.graph.agent.hook.summarization.SummarizationHook; +import com.alibaba.cloud.ai.graph.agent.interceptor.ModelCallHandler; +import com.alibaba.cloud.ai.graph.agent.interceptor.ModelInterceptor; +import com.alibaba.cloud.ai.graph.agent.interceptor.ModelRequest; +import com.alibaba.cloud.ai.graph.agent.interceptor.ModelResponse; +import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallHandler; +import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallRequest; +import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallResponse; +import com.alibaba.cloud.ai.graph.agent.interceptor.ToolInterceptor; +import com.alibaba.cloud.ai.graph.agent.interceptor.contextediting.ContextEditingInterceptor; +import com.alibaba.cloud.ai.graph.agent.interceptor.todolist.TodoListInterceptor; +import com.alibaba.cloud.ai.graph.agent.interceptor.toolemulator.ToolEmulatorInterceptor; +import com.alibaba.cloud.ai.graph.agent.interceptor.toolretry.ToolRetryInterceptor; +import com.alibaba.cloud.ai.graph.agent.interceptor.toolselection.ToolSelectionInterceptor; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; + +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * Hooks & Interceptors Tutorial - hooks.md + */ +public class HooksExample { + + // ==================== 基础 Hook 和 Interceptor 配置 ==================== + + /** + * 示例1:添加 Hooks 和 Interceptors + */ + public static void basicHooksAndInterceptors() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建工具(示例) + ToolCallback[] tools = new ToolCallback[0]; + + // 创建 Hooks 和 Interceptors + ModelHook loggingHook = new LoggingModelHook(); + MessagesModelHook messageTrimmingHook = new MessageTrimmingHook(); + ModelInterceptor guardrailInterceptor = new GuardrailInterceptor(); + ToolInterceptor retryInterceptor = new RetryToolInterceptor(); + + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .tools(tools) + .hooks(loggingHook, messageTrimmingHook) + .interceptors(guardrailInterceptor) + .interceptors(retryInterceptor) + .build(); + } + + // ==================== 消息压缩(Summarization) ==================== + + /** + * 示例2:消息压缩 Hook + */ + public static void messageSummarization() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建消息压缩 Hook + SummarizationHook summarizationHook = SummarizationHook.builder() + .model(chatModel) + .maxTokensBeforeSummary(4000) + .messagesToKeep(20) + .build(); + + // 使用 + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .hooks(summarizationHook) + .build(); + + } + + // ==================== Human-in-the-Loop ==================== + + /** + * 示例3:Human-in-the-Loop Hook + */ + public static void humanInTheLoop() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建工具(示例) + ToolCallback sendEmailTool = createSendEmailTool(); + ToolCallback deleteDataTool = createDeleteDataTool(); + + // 创建 Human-in-the-Loop Hook + HumanInTheLoopHook humanReviewHook = HumanInTheLoopHook.builder() + .approvalOn("sendEmailTool", ToolConfig.builder() + .description("Please confirm sending the email.") + .build()) + .approvalOn("deleteDataTool", ToolConfig.builder() + .description("Please confirm deleting the data.") + .build()) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("supervised_agent") + .model(chatModel) + .tools(sendEmailTool, deleteDataTool) + .hooks(humanReviewHook) + .saver(new MemorySaver()) + .build(); + } + + // ==================== 模型调用限制 ==================== + + /** + * 示例4:模型调用限制 + */ + public static void modelCallLimit() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .hooks(ModelCallLimitHook.builder().runLimit(5).build()) // 限制模型调用次数为5次 + .saver(new MemorySaver()) + .build(); + } + + + // ==================== PII 检测 ==================== + + /** + * 示例6:PII 检测 + */ + public static void piiDetection() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + PIIDetectionHook pii = PIIDetectionHook.builder() + .piiType(PIIType.EMAIL) + .strategy(RedactionStrategy.REDACT) + .applyToInput(true) + .build(); + + // 使用 + ReactAgent agent = ReactAgent.builder() + .name("secure_agent") + .model(chatModel) + .hooks(pii) + .build(); + } + + // ==================== 工具重试 ==================== + + /** + * 示例7:工具重试 + */ + public static void toolRetry() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建工具(示例) + ToolCallback searchTool = createSearchTool(); + ToolCallback databaseTool = createDatabaseTool(); + + // 使用 + ReactAgent agent = ReactAgent.builder() + .name("resilient_agent") + .model(chatModel) + .tools(searchTool, databaseTool) + .interceptors(ToolRetryInterceptor.builder().maxRetries(2) + .onFailure(ToolRetryInterceptor.OnFailureBehavior.RETURN_MESSAGE).build()) + .build(); + } + + // ==================== Planning ==================== + + /** + * 示例8:Planning Hook + */ + public static void planning() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ToolCallback myTool = createSampleTool(); + + // 使用 + ReactAgent agent = ReactAgent.builder() + .name("planning_agent") + .model(chatModel) + .tools(myTool) + .interceptors(TodoListInterceptor.builder().build()) + .build(); + } + + // ==================== LLM Tool Selector ==================== + + /** + * 示例9:LLM 工具选择器 + */ + public static void llmToolSelector() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ChatModel selectorModel = chatModel; // 用于选择的另一个ChatModel + + ToolCallback tool1 = createSampleTool(); + ToolCallback tool2 = createSampleTool(); + + // 使用 + ReactAgent agent = ReactAgent.builder() + .name("smart_selector_agent") + .model(chatModel) + .tools(tool1, tool2) + .interceptors(ToolSelectionInterceptor.builder().build()) + .build(); + } + + // ==================== LLM Tool Emulator ==================== + + /** + * 示例10:LLM 工具模拟器 + */ + public static void llmToolEmulator() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ToolCallback simulatedTool = createSampleTool(); + + // 使用 + ReactAgent agent = ReactAgent.builder() + .name("emulator_agent") + .model(chatModel) + .tools(simulatedTool) + .interceptors(ToolEmulatorInterceptor.builder().model(chatModel).build()) + .build(); + } + + // ==================== Context Editing ==================== + + /** + * 示例11:上下文编辑 + */ + public static void contextEditing() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 使用 + ReactAgent agent = ReactAgent.builder() + .name("context_aware_agent") + .model(chatModel) + .interceptors(ContextEditingInterceptor.builder().trigger(120000).clearAtLeast(60000).build()) + .build(); + } + + // ==================== 自定义 Hooks ==================== + + // 创建示例工具的辅助方法 + private static ToolCallback createSendEmailTool() { + return FunctionToolCallback.builder("sendEmailTool", (String input) -> "Email sent") + .description("Send an email") + .inputType(String.class) + .build(); + } + + private static ToolCallback createDeleteDataTool() { + return FunctionToolCallback.builder("deleteDataTool", (String input) -> "Data deleted") + .description("Delete data") + .inputType(String.class) + .build(); + } + + // ==================== 自定义 Interceptors ==================== + + private static ToolCallback createSearchTool() { + return FunctionToolCallback.builder("searchTool", (String input) -> "Search results") + .description("Search the web") + .inputType(String.class) + .build(); + } + + private static ToolCallback createDatabaseTool() { + return FunctionToolCallback.builder("databaseTool", (String input) -> "Database query results") + .description("Query database") + .inputType(String.class) + .build(); + } + + // ==================== 辅助类和方法 ==================== + + private static ToolCallback createSampleTool() { + return FunctionToolCallback.builder("sampleTool", (String input) -> "Sample result") + .description("A sample tool") + .inputType(String.class) + .build(); + } + + public static void main(String[] args) { + System.out.println("=== Hooks and Interceptors Tutorial Examples ==="); + System.out.println("注意:需要设置 AI_DASHSCOPE_API_KEY 环境变量\n"); + + try { + System.out.println("\n--- 示例1:基础 Hooks 和 Interceptors ---"); + basicHooksAndInterceptors(); + + System.out.println("\n--- 示例2:消息压缩 Hook ---"); + messageSummarization(); + + System.out.println("\n--- 示例3:人工介入循环 ---"); + humanInTheLoop(); + + System.out.println("\n--- 示例4:模型调用限制 ---"); + modelCallLimit(); + + System.out.println("\n--- 示例5:PII 检测 ---"); + piiDetection(); + + System.out.println("\n--- 示例6:工具重试 ---"); + toolRetry(); + + System.out.println("\n--- 示例7:规划(Planning) ---"); + planning(); + + System.out.println("\n--- 示例8:LLM 工具选择器 ---"); + llmToolSelector(); + + System.out.println("\n--- 示例9:LLM 工具模拟器 ---"); + llmToolEmulator(); + + System.out.println("\n--- 示例10:上下文编辑 ---"); + contextEditing(); + + System.out.println("\n=== 所有示例执行完成 ==="); + } + catch (Exception e) { + System.err.println("执行示例时发生错误: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 示例12:自定义 ModelHook + */ + @HookPositions({HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL}) + public static class CustomModelHook extends ModelHook { + + @Override + public String getName() { + return "custom_model_hook"; + } + + @Override + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { + // 在模型调用前执行 + System.out.println("准备调用模型..."); + + // 可以修改状态 + // 例如:添加额外的上下文 + return CompletableFuture.completedFuture(Map.of("extra_context", "某些额外信息")); + } + + @Override + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { + // 在模型调用后执行 + System.out.println("模型调用完成"); + + // 可以记录响应信息 + return CompletableFuture.completedFuture(Map.of()); + } + } + + /** + * 示例13:自定义 AgentHook + */ + @HookPositions({HookPosition.BEFORE_AGENT, HookPosition.AFTER_AGENT}) + public static class CustomAgentHook extends AgentHook { + + @Override + public String getName() { + return "custom_agent_hook"; + } + + @Override + public CompletableFuture> beforeAgent(OverAllState state, RunnableConfig config) { + System.out.println("Agent 开始执行"); + // 可以初始化资源、记录开始时间等 + return CompletableFuture.completedFuture(Map.of("start_time", System.currentTimeMillis())); + } + + @Override + public CompletableFuture> afterAgent(OverAllState state, RunnableConfig config) { + System.out.println("Agent 执行完成"); + // 可以清理资源、计算执行时间等 + Optional startTime = state.value("start_time"); + if (startTime.isPresent()) { + long duration = System.currentTimeMillis() - (Long) startTime.get(); + System.out.println("执行耗时: " + duration + "ms"); + } + return CompletableFuture.completedFuture(Map.of()); + } + } + + /** + * 示例14:自定义 ModelInterceptor + */ + public static class LoggingInterceptor extends ModelInterceptor { + + @Override + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { + // 请求前记录 + System.out.println("发送请求到模型: " + request.getMessages().size() + " 条消息"); + + long startTime = System.currentTimeMillis(); + + // 执行实际调用 + ModelResponse response = handler.call(request); + + // 响应后记录 + long duration = System.currentTimeMillis() - startTime; + System.out.println("模型响应耗时: " + duration + "ms"); + + return response; + } + + @Override + public String getName() { + return "LoggingInterceptor"; + } + } + + /** + * 示例15:自定义 ToolInterceptor + */ + public static class ToolMonitoringInterceptor extends ToolInterceptor { + + @Override + public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) { + String toolName = request.getToolName(); + long startTime = System.currentTimeMillis(); + + System.out.println("执行工具: " + toolName); + + try { + ToolCallResponse response = handler.call(request); + + long duration = System.currentTimeMillis() - startTime; + System.out.println("工具 " + toolName + " 执行成功 (耗时: " + duration + "ms)"); + + return response; + } + catch (Exception e) { + long duration = System.currentTimeMillis() - startTime; + System.err.println("工具 " + toolName + " 执行失败 (耗时: " + duration + "ms): " + e.getMessage()); + + return ToolCallResponse.of( + request.getToolCallId(), + request.getToolName(), + "工具执行失败: " + e.getMessage() + ); + } + } + + @Override + public String getName() { + return "ToolMonitoringInterceptor"; + } + } + + /** + * 日志记录 ModelHook + */ + @HookPositions({HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL}) + private static class LoggingModelHook extends ModelHook { + @Override + public String getName() { + return "logging_model_hook"; + } + + @Override + public HookPosition[] getHookPositions() { + return new HookPosition[] {HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL}; + } + + @Override + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { + System.out.println("Before model call"); + return CompletableFuture.completedFuture(Map.of()); + } + + @Override + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { + System.out.println("After model call"); + return CompletableFuture.completedFuture(Map.of()); + } + } + + /** + * 消息修剪 Hook + * 使用 MessagesModelHook 实现,在模型调用前修剪消息列表,只保留最后 10 条消息 + */ + @HookPositions({HookPosition.BEFORE_MODEL}) + private static class MessageTrimmingHook extends MessagesModelHook { + private static final int MAX_MESSAGES = 10; + + @Override + public String getName() { + return "message_trimming"; + } + + @Override + public AgentCommand beforeModel(List previousMessages, RunnableConfig config) { + // 如果消息数量超过限制,只保留最后 MAX_MESSAGES 条消息 + if (previousMessages.size() > MAX_MESSAGES) { + List trimmedMessages = previousMessages.subList( + previousMessages.size() - MAX_MESSAGES, + previousMessages.size() + ); + // 使用 REPLACE 策略替换所有消息 + return new AgentCommand(trimmedMessages, UpdatePolicy.REPLACE); + } + // 如果消息数量未超过限制,返回原始消息(不进行修改) + return new AgentCommand(previousMessages); + } + } + + /** + * 护栏拦截器 + */ + private static class GuardrailInterceptor extends ModelInterceptor { + @Override + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { + // 简化的实现 + return handler.call(request); + } + + @Override + public String getName() { + return "GuardrailInterceptor"; + } + } + + // ==================== Main 方法 ==================== + + /** + * 重试工具拦截器 + */ + private static class RetryToolInterceptor extends ToolInterceptor { + @Override + public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) { + // 简化的实现 + return handler.call(request); + } + + @Override + public String getName() { + return "RetryToolInterceptor"; + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/MemoryExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/MemoryExample.java new file mode 100644 index 00000000..7c70b9fd --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/MemoryExample.java @@ -0,0 +1,557 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.tutorials; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; +import com.alibaba.cloud.ai.graph.agent.hook.HookPositions; +import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand; +import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesModelHook; +import com.alibaba.cloud.ai.graph.agent.hook.messages.UpdatePolicy; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.checkpoint.savers.redis.RedisSaver; +import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; +import org.redisson.api.RedissonClient; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +/** + * Memory Tutorial - 完整代码示例 + * 展示如何使用短期记忆让Agent记住先前交互 + * + * 来源:memory.md + */ +public class MemoryExample { + + // ==================== 基础使用 ==================== + + /** + * 示例1:基础记忆配置 + */ + public static void basicMemoryConfiguration() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建示例工具 + ToolCallback getUserInfoTool = createGetUserInfoTool(); + + // 配置 checkpointer + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .tools(getUserInfoTool) + .saver(new MemorySaver()) + .build(); + + // 使用 thread_id 维护对话上下文 + RunnableConfig config = RunnableConfig.builder() + .threadId("1") // threadId 指定会话 ID + .build(); + + AssistantMessage message = agent.call("你好!我叫 Bob。", config); + System.out.println(message.getText()); + } + + /** + * 示例2:生产环境使用 Redis Checkpointer + */ + public static void productionMemoryConfiguration(RedissonClient redissonClient) { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ToolCallback getUserInfoTool = createGetUserInfoTool(); + + // 配置 Redis checkpointer + RedisSaver redisSaver = RedisSaver.builder().redisson(redissonClient).build(); + + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .tools(getUserInfoTool) + .saver(redisSaver) + .build(); + } + + // ==================== 自定义 Agent 记忆 ==================== + + /** + * 示例5:使用消息修剪 + */ + public static void useMessageTrimming() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ToolCallback[] tools = new ToolCallback[0]; + + // 使用 + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .tools(tools) + .hooks(new MessageTrimmingHook()) + .saver(new MemorySaver()) + .build(); + + RunnableConfig config = RunnableConfig.builder() + .threadId("1") + .build(); + + agent.call("你好,我叫 bob", config); + agent.call("写一首关于猫的短诗", config); + agent.call("现在对狗做同样的事情", config); + agent.call("写一首关于狗的短诗", config); + agent.call("写一首关于牛的短诗", config); + AssistantMessage finalResponse = agent.call("我叫什么名字?", config); + + System.out.println(finalResponse.getText()); + // 输出:你的名字是 Bob。你之前告诉我的。 + } + + // ==================== 修剪消息 ==================== + + /** + * 示例8:使用消息删除 + */ + public static void useMessageDeletion() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .systemPrompt("请简洁明了。") + .hooks(new MessageDeletionHook()) + .saver(new MemorySaver()) + .build(); + + RunnableConfig config = RunnableConfig.builder() + .threadId("1") + .build(); + + // 第一次调用 + agent.call("你好!我是 bob", config); + // 输出:[('human', "你好!我是 bob"), ('assistant', '你好 Bob!很高兴见到你...')] + + // 第二次调用 + agent.call("我叫什么名字?", config); + // 输出:[('human', "我叫什么名字?"), ('assistant', '你的名字是 Bob...')] + } + + /** + * 示例10:使用消息总结 + */ + public static void useMessageSummarization() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 用于总结的模型(可以是更便宜的模型) + ChatModel summaryModel = chatModel; + + MessageSummarizationHook summarizationHook = new MessageSummarizationHook( + summaryModel, + 100, // 在 4000 tokens 时触发总结 + 3 // 总结后保留最后 20 条消息 + ); + + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .hooks(summarizationHook) + .saver(new MemorySaver()) + .build(); + + RunnableConfig config = RunnableConfig.builder() + .threadId("1") + .build(); + + agent.call("你好,我叫 bob", config); + agent.call("陈晓红生了一个儿子,请给她儿子取一个名字", config); + agent.call("写一首关于猫的短诗", config); + agent.call("写一首关于狗的短诗", config); + agent.call("现在对狗做同样的事情", config); + agent.call("介绍红烧肉的做法", config); + agent.call("她又生了一个女儿,请给她女儿取一个名字", config); + AssistantMessage finalResponse = agent.call("我叫什么名字?", config); + + System.out.println(finalResponse.getText()); + // 输出:你的名字是 Bob! + } + + // ==================== 删除消息 ==================== + + /** + * 示例12:使用工具访问记忆 + */ + public static void accessMemoryInTool() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建工具 + ToolCallback getUserInfoTool = FunctionToolCallback + .builder("get_user_info", new UserInfoTool()) + .description("查找用户信息") + .inputType(String.class) + .build(); + + // 使用 + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .tools(getUserInfoTool) + .saver(new MemorySaver()) + .build(); + + RunnableConfig config = RunnableConfig.builder() + .threadId("1") + .addMetadata("user_id", "user_123") + .build(); + + AssistantMessage response = agent.call("获取用户信息", config); + System.out.println(response.getText()); + } + + /** + * 创建示例工具 + */ + private static ToolCallback createGetUserInfoTool() { + return FunctionToolCallback.builder("get_user_info", (String query) -> { + return "User info: " + query; + }) + .description("Get user information") + .inputType(String.class) + .build(); + } + + public static void main(String[] args) { + System.out.println("=== Memory Tutorial Examples ==="); + System.out.println("注意:需要设置 AI_DASHSCOPE_API_KEY 环境变量\n"); + + try { + // 示例1:基础记忆配置 + // System.out.println("\n--- 示例1:基础记忆配置 ---"); + // basicMemoryConfiguration(); + + // 示例2:生产环境使用 Redis Checkpointer (需要 RedissonClient 实例,此处跳过) + // System.out.println("\n--- 示例2:生产环境使用 Redis Checkpointer (跳过,需要 RedissonClient) ---"); + // productionMemoryConfiguration(redissonClient); + + // // 示例5:使用消息修剪 + // System.out.println("\n--- 示例5:使用消息修剪 ---"); +// useMessageTrimming(); + // + // // 示例8:使用消息删除 + // System.out.println("\n--- 示例8:使用消息删除 ---"); + // useMessageDeletion(); + // + // // 示例10:使用消息总结 + // System.out.println("\n--- 示例10:使用消息总结 ---"); +// useMessageSummarization(); + // + // // 示例12:使用工具访问记忆 + // System.out.println("\n--- 示例12:使用工具访问记忆 ---"); + accessMemoryInTool(); + + // System.out.println("\n=== 所有示例执行完成 ==="); + } + // catch (GraphRunnerException e) { + // System.err.println("执行示例时发生错误: " + e.getMessage()); + // e.printStackTrace(); + // } + catch (Exception e) { + System.err.println("发生未预期的错误: " + e.getMessage()); + e.printStackTrace(); + } + } + + // ==================== 总结消息 ==================== + + /** + * 示例3:在 Hook 中访问和修改状态 + * 注意:这个 Hook 主要用于访问消息历史,不修改消息,所以可以继续使用 ModelHook + * 但如果需要修改消息,应该使用 MessagesModelHook + */ + @HookPositions({HookPosition.BEFORE_MODEL}) + public static class CustomMemoryHook extends MessagesModelHook { + + @Override + public String getName() { + return "custom_memory"; + } + + @Override + public AgentCommand beforeModel(List previousMessages, RunnableConfig config) { + // 访问消息历史(previousMessages 已经提供了消息列表) + // 处理消息... + // 如果需要修改消息,可以返回新的 AgentCommand + // 这里只是访问,不修改消息,所以返回原始消息 + return new AgentCommand(previousMessages); + } + } + + /** + * 示例4:消息修剪 Hook + * 使用 MessagesModelHook 实现,在模型调用前修剪消息列表 + * 保留第一条消息和最后 keepCount 条消息,删除中间的消息 + */ + @HookPositions({HookPosition.BEFORE_MODEL}) + public static class MessageTrimmingHook extends MessagesModelHook { + + private static final int MAX_MESSAGES = 3; + + @Override + public String getName() { + return "message_trimming"; + } + + @Override + public AgentCommand beforeModel(List previousMessages, RunnableConfig config) { + if (previousMessages.size() <= MAX_MESSAGES) { + // 如果消息数量未超过限制,无需更改 + return new AgentCommand(previousMessages); + } + + int keepCount = previousMessages.size() % 2 == 0 ? 3 : 4; + + // 构建要保留的消息列表:第一条消息 + 最后 keepCount 条消息 + List trimmedMessages = new ArrayList<>(); + // 保留第一条消息 + if (!previousMessages.isEmpty()) { + trimmedMessages.add(previousMessages.get(0)); + } + // 保留最后 keepCount 条消息 + if (previousMessages.size() - keepCount > 0) { + trimmedMessages.addAll(previousMessages.subList( + previousMessages.size() - keepCount, + previousMessages.size() + )); + } + + // 使用 REPLACE 策略替换所有消息 + return new AgentCommand(trimmedMessages, UpdatePolicy.REPLACE); + } + } + + // ==================== 访问记忆 ==================== + + /** + * 示例6:消息删除 Hook + * 使用 MessagesModelHook 实现,在模型调用后删除最早的两条消息 + */ + @HookPositions({HookPosition.AFTER_MODEL}) + public static class MessageDeletionHook extends MessagesModelHook { + + @Override + public String getName() { + return "message_deletion"; + } + + @Override + public AgentCommand afterModel(List previousMessages, RunnableConfig config) { + if (previousMessages.size() <= 2) { + // 如果消息数量不超过2条,无需删除 + return new AgentCommand(previousMessages); + } + + // 删除最早的两条消息,保留其余消息 + List remainingMessages = previousMessages.subList(2, previousMessages.size()); + + // 使用 REPLACE 策略替换所有消息 + return new AgentCommand(remainingMessages, UpdatePolicy.REPLACE); + } + } + + /** + * 示例7:删除所有消息 + * 使用 MessagesModelHook 实现,在模型调用后删除所有消息 + */ + @HookPositions({HookPosition.AFTER_MODEL}) + public static class ClearAllMessagesHook extends MessagesModelHook { + + @Override + public String getName() { + return "clear_all_messages"; + } + + @Override + public AgentCommand afterModel(List previousMessages, RunnableConfig config) { + // 删除所有消息,返回空列表 + List emptyMessages = new ArrayList<>(); + // 使用 REPLACE 策略替换所有消息为空列表 + return new AgentCommand(emptyMessages, UpdatePolicy.REPLACE); + } + } + + // ==================== 辅助方法 ==================== + + /** + * 示例9:消息总结 Hook + * 使用 MessagesModelHook 实现,在模型调用前检查消息数量,如果超过阈值则生成摘要 + * 删除旧消息,保留摘要消息和最近的消息 + */ + @HookPositions({HookPosition.BEFORE_MODEL}) + public static class MessageSummarizationHook extends MessagesModelHook { + + private final ChatModel summaryModel; + private final int maxTokensBeforeSummary; + private final int messagesToKeep; + + public MessageSummarizationHook( + ChatModel summaryModel, + int maxTokensBeforeSummary, + int messagesToKeep + ) { + this.summaryModel = summaryModel; + this.maxTokensBeforeSummary = maxTokensBeforeSummary; + this.messagesToKeep = messagesToKeep; + } + + @Override + public String getName() { + return "message_summarization"; + } + + @Override + public AgentCommand beforeModel(List previousMessages, RunnableConfig config) { + // 估算 token 数量(简化版) + int estimatedTokens = previousMessages.stream() + .mapToInt(m -> m.getText().length() / 4) + .sum(); + + if (estimatedTokens < maxTokensBeforeSummary) { + // 如果 token 数量未超过阈值,无需总结 + return new AgentCommand(previousMessages); + } + + // 需要总结 + int messagesToSummarize = previousMessages.size() - messagesToKeep; + if (messagesToSummarize <= 0) { + // 如果消息数量不足以总结,无需更改 + return new AgentCommand(previousMessages); + } + + List oldMessages = previousMessages.subList(0, messagesToSummarize); + List recentMessages = previousMessages.subList( + messagesToSummarize, + previousMessages.size() + ); + + // 生成摘要 + String summary = generateSummary(oldMessages); + + // 创建摘要消息 + SystemMessage summaryMessage = new SystemMessage( + "## 之前对话摘要:\n" + summary + ); + + // 构建新的消息列表:摘要消息 + 最近的消息 + List newMessages = new ArrayList<>(); + newMessages.add(summaryMessage); + newMessages.addAll(recentMessages); + + // 使用 REPLACE 策略替换所有消息 + return new AgentCommand(newMessages, UpdatePolicy.REPLACE); + } + + private String generateSummary(List messages) { + StringBuilder conversation = new StringBuilder(); + for (Message msg : messages) { + conversation.append(msg.getMessageType()) + .append(": ") + .append(msg.getText()) + .append("\n"); + } + + String summaryPrompt = "请简要总结以下对话:\n\n" + conversation; + + ChatResponse response = summaryModel.call( + new Prompt(new UserMessage(summaryPrompt)) + ); + + return response.getResult().getOutput().getText(); + } + } + + // ==================== Main 方法 ==================== + + /** + * 示例11:在工具中读取短期记忆 + */ + public static class UserInfoTool implements BiFunction { + + @Override + public String apply(String query, ToolContext toolContext) { + // 从上下文中获取用户信息 + RunnableConfig config = (RunnableConfig) toolContext.getContext().get("config"); + if (config == null) { + return "未知用户"; + } + String userId = (String) config.metadata("user_id").orElse(""); + + if ("user_123".equals(userId)) { + return "用户是 John Smith"; + } + else { + return "未知用户"; + } + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/MessagesExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/MessagesExample.java new file mode 100644 index 00000000..50a96f98 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/MessagesExample.java @@ -0,0 +1,606 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.tutorials; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; + +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.ToolResponseMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.metadata.ChatResponseMetadata; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.content.Media; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.MimeTypeUtils; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import reactor.core.publisher.Flux; + +/** + * Messages Tutorial - 完整代码示例 + * 展示Messages作为模型交互的基本单元的使用方法 + * + * 来源:messages.md + */ +public class MessagesExample { + + // ==================== 基础使用 ==================== + + /** + * 示例1:基础消息使用 + */ + public static void basicMessageUsage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + // 使用 DashScope ChatModel + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + SystemMessage systemMsg = new SystemMessage("你是一个有帮助的助手。"); + UserMessage userMsg = new UserMessage("你好,你好吗?"); + + // 与聊天模型一起使用 + List messages = List.of(systemMsg, userMsg); + Prompt prompt = new Prompt(messages); + ChatResponse response = chatModel.call(prompt); // 返回 ChatResponse,包含 AssistantMessage + } + + // ==================== 文本提示 vs 消息提示 ==================== + + /** + * 示例2:文本提示 + */ + public static void textPromptUsage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 使用字符串直接调用 + String response = chatModel.call("写一首关于春天的俳句"); + } + + /** + * 示例3:消息提示 + */ + public static void messagePromptUsage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + List messages = List.of( + new SystemMessage("你是一个诗歌专家"), + new UserMessage("写一首关于春天的俳句"), + new AssistantMessage("樱花盛开时...") + ); + Prompt prompt = new Prompt(messages); + ChatResponse response = chatModel.call(prompt); + } + + // ==================== System Message ==================== + + /** + * 示例4:基础指令 + */ + public static void basicSystemMessage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 基础指令 + SystemMessage systemMsg = new SystemMessage("你是一个有帮助的编程助手。"); + + List messages = List.of( + systemMsg, + new UserMessage("如何创建 REST API?") + ); + ChatResponse response = chatModel.call(new Prompt(messages)); + } + + /** + * 示例5:详细的角色设定 + */ + public static void detailedSystemMessage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 详细的角色设定 + SystemMessage systemMsg = new SystemMessage(""" + 你是一位资深的 Java 开发者,擅长 Web 框架。 + 始终提供代码示例并解释你的推理。 + 在解释中要简洁但透彻。 + """); + + List messages = List.of( + systemMsg, + new UserMessage("如何创建 REST API?") + ); + ChatResponse response = chatModel.call(new Prompt(messages)); + } + + // ==================== User Message ==================== + + /** + * 示例6:文本内容 + */ + public static void textUserMessage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 使用消息对象 + ChatResponse response = chatModel.call( + new Prompt(List.of(new UserMessage("什么是机器学习?"))) + ); + + // 使用字符串快捷方式 + // 使用字符串是单个 UserMessage 的快捷方式 + String response2 = chatModel.call("什么是机器学习?"); + } + + /** + * 示例7:消息元数据 + */ + public static void userMessageMetadata() { + UserMessage userMsg = UserMessage.builder() + .text("你好!") + .metadata(Map.of( + "user_id", "alice", // 可选:识别不同用户 + "session_id", "sess_123" // 可选:会话标识符 + )) + .build(); + } + + /** + * 示例8:多模态内容 - 图像 + */ + public static void multimodalImageMessage() throws Exception { + // 从 URL 创建图像 + UserMessage userMsg = UserMessage.builder() + .text("描述这张图片的内容。") + .media(Media.builder().mimeType(MimeTypeUtils.IMAGE_JPEG).data(new URL("https://example.com/image.jpg")) + .build()).build(); + } + + // ==================== Assistant Message ==================== + + /** + * 示例9:Assistant Message 基础使用 + */ + public static void basicAssistantMessage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ChatResponse response = chatModel.call(new Prompt("解释 AI")); + AssistantMessage aiMessage = response.getResult().getOutput(); + System.out.println(aiMessage.getText()); + } + + /** + * 示例10:手动创建 AI 消息 + */ + public static void manualAssistantMessage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 手动创建 AI 消息(例如,用于对话历史) + AssistantMessage aiMsg = new AssistantMessage("我很乐意帮助你回答这个问题!"); + + // 添加到对话历史 + List messages = List.of( + new SystemMessage("你是一个有帮助的助手"), + new UserMessage("你能帮我吗?"), + aiMsg, // 插入,就像它来自模型一样 + new UserMessage("太好了!2+2 等于多少?") + ); + + ChatResponse response = chatModel.call(new Prompt(messages)); + } + + /** + * 示例11:工具调用 + */ + public static void toolCallsInAssistantMessage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + Prompt prompt = new Prompt("北京的天气怎么样?"); + ChatResponse response = chatModel.call(prompt); + AssistantMessage aiMessage = response.getResult().getOutput(); + + if (aiMessage.hasToolCalls()) { + for (AssistantMessage.ToolCall toolCall : aiMessage.getToolCalls()) { + System.out.println("Tool: " + toolCall.name()); + System.out.println("Args: " + toolCall.arguments()); + System.out.println("ID: " + toolCall.id()); + } + } + } + + /** + * 示例12:Token 使用 + */ + public static void tokenUsage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ChatResponse response = chatModel.call(new Prompt("你好!")); + ChatResponseMetadata metadata = response.getMetadata(); + + // 访问使用信息 + if (metadata != null && metadata.getUsage() != null) { + System.out.println("Input tokens: " + metadata.getUsage().getPromptTokens()); + System.out.println("Output tokens: " + metadata.getUsage().getCompletionTokens()); + System.out.println("Total tokens: " + metadata.getUsage().getTotalTokens()); + } + } + + /** + * 示例13:流式和块 + */ + public static void streamingMessages() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + Flux responseStream = chatModel.stream(new Prompt("你好")); + + StringBuilder fullResponse = new StringBuilder(); + responseStream.subscribe( + chunk -> { + String content = chunk.getResult().getOutput().getText(); + fullResponse.append(content); + System.out.print(content); + } + ); + } + + // ==================== Tool Response Message ==================== + + /** + * 示例14:Tool Response Message + */ + public static void toolResponseMessage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 在模型进行工具调用后 + AssistantMessage aiMessage = AssistantMessage.builder() + .content("") + .toolCalls(List.of( + new AssistantMessage.ToolCall( + "call_123", + "tool", + "get_weather", + "{\"location\": \"San Francisco\"}" + ) + )) + .build(); + + // 执行工具并创建结果消息 + String weatherResult = "晴朗,22°C"; + ToolResponseMessage toolMessage = ToolResponseMessage.builder() + .responses(List.of( + new ToolResponseMessage.ToolResponse("call_123", "get_weather", weatherResult) + )) + .build(); + + // 继续对话 + List messages = List.of( + new UserMessage("旧金山的天气怎么样?"), + aiMessage, // 模型的工具调用 + toolMessage // 工具执行结果 + ); + ChatResponse response = chatModel.call(new Prompt(messages)); + } + + // ==================== 多模态内容 ==================== + + /** + * 示例15:图像输入 - 从 URL + */ + public static void imageInputFromURL() throws Exception { + // 从 URL + UserMessage message = UserMessage.builder() + .text("描述这张图片的内容。") + .media(Media.builder().mimeType(MimeTypeUtils.IMAGE_JPEG).data(new URL("https://example.com/image.jpg")) + .build()) + .build(); + } + + /** + * 示例16:图像输入 - 从本地文件 + */ + public static void imageInputFromFile() { + // 从本地文件 + ClassPathResource resource = new ClassPathResource("images/photo.jpg"); + UserMessage message = UserMessage.builder() + .text("描述这张图片的内容。") + .media(new Media( + MimeTypeUtils.IMAGE_JPEG, + resource + )) + .build(); + } + + /** + * 示例17:音频输入 + */ + public static void audioInput() { + UserMessage message = UserMessage.builder() + .text("描述这段音频的内容。") + .media(new Media( + MimeTypeUtils.parseMimeType("audio/wav"), + new ClassPathResource("audio/recording.wav") + )) + .build(); + } + + /** + * 示例18:视频输入 + */ + public static void videoInput() throws Exception { + UserMessage message = UserMessage.builder() + .text("描述这段视频的内容。") + .media(Media.builder().mimeType(MimeTypeUtils.parseMimeType("video/mp4")) + .data(new URL("\"https://example.com/path/to/video.mp4")) + .build()) + .build(); + } + + // ==================== 与 Chat Models 一起使用 ==================== + + /** + * 示例19:基础对话示例 + */ + public static void basicConversationExample() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + List conversationHistory = new ArrayList<>(); + + // 第一轮对话 + conversationHistory.add(new UserMessage("你好!")); + ChatResponse response1 = chatModel.call(new Prompt(conversationHistory)); + conversationHistory.add(response1.getResult().getOutput()); + + // 第二轮对话 + conversationHistory.add(new UserMessage("你能帮我学习 Java 吗?")); + ChatResponse response2 = chatModel.call(new Prompt(conversationHistory)); + conversationHistory.add(response2.getResult().getOutput()); + + // 第三轮对话 + conversationHistory.add(new UserMessage("从哪里开始?")); + ChatResponse response3 = chatModel.call(new Prompt(conversationHistory)); + } + + /** + * 示例20:使用 Builder 模式 + */ + public static void builderPattern() { + // UserMessage with builder + UserMessage userMsg = UserMessage.builder() + .text("你好,我想学习 Spring AI Alibaba") + .metadata(Map.of("user_id", "user_123")) + .build(); + + // SystemMessage with builder + SystemMessage systemMsg = SystemMessage.builder() + .text("你是一个 Spring 框架专家") + .metadata(Map.of("version", "1.0")) + .build(); + + // AssistantMessage with builder + AssistantMessage assistantMsg = AssistantMessage.builder() + .content("我很乐意帮助你学习 Spring AI Alibaba!") + .build(); + } + + /** + * 示例21:消息复制和修改 + */ + public static void messageCopyAndModify() { + // 复制消息 + UserMessage original = new UserMessage("原始消息"); + UserMessage copy = original.copy(); + + // 使用 mutate 创建修改的副本 + UserMessage modified = original.mutate() + .text("修改后的消息") + .metadata(Map.of("modified", true)) + .build(); + } + + // ==================== 在 ReactAgent 中使用 ==================== + + /** + * 示例22:在 ReactAgent 中使用消息 + */ + public static void messagesInReactAgent() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .systemPrompt("你是一个有帮助的助手") + .build(); + + // 使用字符串 + AssistantMessage response1 = agent.call("你好"); + + // 使用 UserMessage + UserMessage userMsg = new UserMessage("帮我写一首诗"); + AssistantMessage response2 = agent.call(userMsg); + + // 使用消息列表 + List messages = List.of( + new UserMessage("我喜欢春天"), + new UserMessage("写一首关于春天的诗") + ); + AssistantMessage response3 = agent.call(messages); + } + + // ==================== Main 方法 ==================== + + public static void main(String[] args) { + System.out.println("=== Messages Tutorial Examples ==="); + System.out.println("注意:需要设置 AI_DASHSCOPE_API_KEY 环境变量\n"); + + try { +// System.out.println("\n--- 示例1:基础消息使用 ---"); +// basicMessageUsage(); +// +// System.out.println("\n--- 示例2:文本提示使用 ---"); +// textPromptUsage(); +// +// System.out.println("\n--- 示例3:消息提示使用 ---"); +// messagePromptUsage(); +// +// System.out.println("\n--- 示例4:基础系统消息 ---"); +// basicSystemMessage(); +// +// System.out.println("\n--- 示例5:详细系统消息 ---"); +// detailedSystemMessage(); +// +// System.out.println("\n--- 示例6:文本用户消息 ---"); +// textUserMessage(); +// +// System.out.println("\n--- 示例7:用户消息元数据 ---"); +// userMessageMetadata(); +// +// System.out.println("\n--- 示例8:多模态图像消息 ---"); +// multimodalImageMessage(); +// +// System.out.println("\n--- 示例9:基础助手消息 ---"); +// basicAssistantMessage(); +// +// System.out.println("\n--- 示例10:手动助手消息 ---"); +// manualAssistantMessage(); +// +// System.out.println("\n--- 示例11:工具调用在助手消息中 ---"); +// toolCallsInAssistantMessage(); +// +// System.out.println("\n--- 示例12:Token 使用 ---"); +// tokenUsage(); +// +// System.out.println("\n--- 示例13:流式消息 ---"); +// streamingMessages(); +// +// System.out.println("\n--- 示例14:工具响应消息 ---"); +// toolResponseMessage(); +// +// System.out.println("\n--- 示例15:从 URL 输入图像 ---"); +// imageInputFromURL(); +// +// System.out.println("\n--- 示例16:从文件输入图像 ---"); + imageInputFromFile(); +// +// System.out.println("\n--- 示例17:音频输入 ---"); +// audioInput(); +// +// System.out.println("\n--- 示例18:视频输入 ---"); +// videoInput(); +// +// System.out.println("\n--- 示例19:基础对话示例 ---"); +// basicConversationExample(); +// +// System.out.println("\n--- 示例20:构建器模式 ---"); +// builderPattern(); +// + System.out.println("\n=== 所有示例执行完成 ==="); + } + catch (Exception e) { + System.err.println("执行示例时发生错误: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/ModelsExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/ModelsExample.java new file mode 100644 index 00000000..41541c5b --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/ModelsExample.java @@ -0,0 +1,457 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.tutorials; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; + +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; + +import java.util.List; + +import reactor.core.publisher.Flux; + +/** + * Models Tutorial - 完整代码示例 + * 展示如何使用Chat Model API与各种AI模型交互 + * + * 来源:models.md + */ +public class ModelsExample { + + // ==================== DashScopeChatModel ==================== + + /** + * 示例1:创建 ChatModel + */ + public static void createChatModel() { + // 创建 DashScope API 实例 + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + // 创建 ChatModel + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + } + + /** + * 示例2:简单调用 + */ + public static void simpleCall() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 使用字符串直接调用 + String response = chatModel.call("介绍一下Spring框架"); + System.out.println(response); + } + + /** + * 示例3:使用 Prompt + */ + public static void usePrompt() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建 Prompt + Prompt prompt = new Prompt(new UserMessage("解释什么是微服务架构")); + + // 调用并获取响应 + ChatResponse response = chatModel.call(prompt); + String answer = response.getResult().getOutput().getText(); + System.out.println(answer); + } + + // ==================== 配置选项 ==================== + + /** + * 示例4:使用 ChatOptions + */ + public static void useChatOptions() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + DashScopeChatOptions options = DashScopeChatOptions.builder() + .withModel("qwen-plus") // 模型名称 + .withTemperature(0.7) // 温度参数 + .withMaxToken(2000) // 最大令牌数 + .withTopP(0.9) // Top-P 采样 + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .defaultOptions(options) + .build(); + } + + /** + * 示例5:运行时覆盖选项 + */ + public static void runtimeOptionsOverride() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建带有特定选项的 Prompt + DashScopeChatOptions runtimeOptions = DashScopeChatOptions.builder() + .withTemperature(0.3) // 更低的温度,更确定的输出 + .withMaxToken(500) + .build(); + + Prompt prompt = new Prompt( + new UserMessage("用一句话总结Java的特点"), + runtimeOptions + ); + + ChatResponse response = chatModel.call(prompt); + } + + // ==================== 流式响应 ==================== + + /** + * 示例6:流式响应 + */ + public static void streamingResponse() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 使用流式 API + Flux responseStream = chatModel.stream( + new Prompt("详细解释Spring Boot的自动配置原理") + ); + + // 订阅并处理流式响应 + responseStream.subscribe( + chatResponse -> { + String content = chatResponse.getResult() + .getOutput() + .getText(); + System.out.print(content); + }, + error -> System.err.println("错误: " + error.getMessage()), + () -> System.out.println("\n流式响应完成") + ); + } + + // ==================== 多轮对话 ==================== + + /** + * 示例7:多轮对话 + */ + public static void multiTurnConversation() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建对话历史 + List messages = List.of( + new SystemMessage("你是一个Java专家"), + new UserMessage("什么是Spring Boot?"), + new AssistantMessage("Spring Boot是..."), + new UserMessage("它有什么优势?") + ); + + Prompt prompt = new Prompt(messages); + ChatResponse response = chatModel.call(prompt); + } + + // ==================== 函数调用 ==================== + + /** + * 示例8:函数调用 + */ + public static void functionCalling() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 定义函数 + ToolCallback weatherFunction = FunctionToolCallback.builder("getWeather", (city) -> { + // 实际的天气查询逻辑 + return "晴朗,25°C"; + }) + .description("获取指定城市的天气") + .inputType(String.class) + .build(); + + // 使用函数 + DashScopeChatOptions options = DashScopeChatOptions.builder() + .withToolCallbacks(List.of(weatherFunction)) + .build(); + + Prompt prompt = new Prompt("北京的天气怎么样?", options); + ChatResponse response = chatModel.call(prompt); + } + + // ==================== 与 ReactAgent 集成 ==================== + + /** + * 示例9:与 ReactAgent 集成 + */ + public static void integrationWithReactAgent() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .systemPrompt("你是一个有帮助的AI助手") + .build(); + + // 调用 Agent + AssistantMessage response = agent.call("帮我分析这个问题"); + } + + // ==================== 高级配置示例 ==================== + + /** + * 示例10:完整的配置示例 + */ + public static void comprehensiveConfiguration() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + // 配置各种选项 + DashScopeChatOptions options = DashScopeChatOptions.builder() + .withModel("qwen-max") // 使用旗舰版模型 + .withTemperature(0.7) // 控制随机性 + .withMaxToken(4000) // 最大输出长度 + .withTopP(0.9) // 核采样 + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .defaultOptions(options) + .build(); + + // 创建复杂的对话 + List messages = List.of( + new SystemMessage("你是一位资深的软件架构师,精通微服务和云原生技术。"), + new UserMessage("如何设计一个高可用的微服务系统?") + ); + + Prompt prompt = new Prompt(messages); + ChatResponse response = chatModel.call(prompt); + + System.out.println("Response: " + response.getResult().getOutput().getText()); + } + + /** + * 示例11:不同模型的使用 + */ + public static void differentModelsUsage() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + // qwen-turbo: 通义千问超大规模语言模型 + ChatModel turboModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .defaultOptions(DashScopeChatOptions.builder() + .withModel("qwen-turbo") + .build()) + .build(); + + // qwen-plus: 通义千问增强版 + ChatModel plusModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .defaultOptions(DashScopeChatOptions.builder() + .withModel("qwen-plus") + .build()) + .build(); + + // qwen-max: 通义千问旗舰版 + ChatModel maxModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .defaultOptions(DashScopeChatOptions.builder() + .withModel("qwen-max") + .build()) + .build(); + + // 使用不同的模型 + String question = "什么是人工智能?"; + String turboResponse = turboModel.call(question); + String plusResponse = plusModel.call(question); + String maxResponse = maxModel.call(question); + } + + /** + * 示例12:错误处理 + */ + public static void errorHandling() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + try { + ChatResponse response = chatModel.call(new Prompt("你好")); + System.out.println("Response: " + response.getResult().getOutput().getText()); + } + catch (Exception e) { + System.err.println("Error calling model: " + e.getMessage()); + // 处理错误,例如重试、降级等 + } + } + + /** + * 示例13:温度参数的影响 + */ + public static void temperatureEffect() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + String question = "给我讲一个有趣的故事"; + + // 低温度 - 更确定、更保守的输出 + ChatModel conservativeModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .defaultOptions(DashScopeChatOptions.builder() + .withTemperature(0.1) + .build()) + .build(); + + // 中温度 - 平衡的输出 + ChatModel balancedModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .defaultOptions(DashScopeChatOptions.builder() + .withTemperature(0.7) + .build()) + .build(); + + // 高温度 - 更有创意、更随机的输出 + ChatModel creativeModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .defaultOptions(DashScopeChatOptions.builder() + .withTemperature(1.5) + .build()) + .build(); + + String conservativeResponse = conservativeModel.call(question); + String balancedResponse = balancedModel.call(question); + String creativeResponse = creativeModel.call(question); + + System.out.println("Conservative (temp=0.1): " + conservativeResponse); + System.out.println("Balanced (temp=0.7): " + balancedResponse); + System.out.println("Creative (temp=1.5): " + creativeResponse); + } + + // ==================== Main 方法 ==================== + + public static void main(String[] args) { + System.out.println("=== Models Tutorial Examples ==="); + System.out.println("注意:需要设置 AI_DASHSCOPE_API_KEY 环境变量\n"); + + try { + System.out.println("\n--- 示例1:创建 ChatModel ---"); + createChatModel(); + + System.out.println("\n--- 示例2:简单调用 ---"); + simpleCall(); + + System.out.println("\n--- 示例3:使用 Prompt ---"); + usePrompt(); + + System.out.println("\n--- 示例4:使用 ChatOptions ---"); + useChatOptions(); + + System.out.println("\n--- 示例5:运行时选项覆盖 ---"); + runtimeOptionsOverride(); + + System.out.println("\n--- 示例6:流式响应 ---"); + streamingResponse(); + + System.out.println("\n--- 示例7:多轮对话 ---"); + multiTurnConversation(); + + System.out.println("\n--- 示例8:函数调用 ---"); + functionCalling(); + + System.out.println("\n--- 示例9:与 ReactAgent 集成 ---"); + integrationWithReactAgent(); + + System.out.println("\n--- 示例10:综合配置 ---"); + comprehensiveConfiguration(); + + System.out.println("\n--- 示例11:不同模型使用 ---"); + differentModelsUsage(); + + System.out.println("\n--- 示例12:错误处理 ---"); + errorHandling(); + + System.out.println("\n--- 示例13:温度效果 ---"); + temperatureEffect(); + + System.out.println("\n=== 所有示例执行完成 ==="); + } + catch (Exception e) { + System.err.println("执行示例时发生错误: " + e.getMessage()); + e.printStackTrace(); + } + } + +} diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/StructuredOutputExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/StructuredOutputExample.java new file mode 100644 index 00000000..21ee704c --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/StructuredOutputExample.java @@ -0,0 +1,608 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.tutorials; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; + +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.converter.BeanOutputConverter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Structured Output Tutorial - 完整代码示例 + * 展示如何让Agent返回特定格式的结构化数据 + * + * 来源:structured-output.md + */ +public class StructuredOutputExample { + + // ==================== 基础类定义 ==================== + + /** + * 示例1:基本 JSON Schema + */ + public static void basicJsonSchema() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // Use BeanOutputConverter to generate outputSchema + BeanOutputConverter outputConverter = new BeanOutputConverter<>(ContactInfo.class); + String format = outputConverter.getFormat(); + + ReactAgent agent = ReactAgent.builder() + .name("contact_extractor") + .model(chatModel) + .outputSchema(format) + .build(); + + AssistantMessage result = agent.call( + "从以下信息提取联系方式:张三,zhangsan@example.com,(555) 123-4567" + ); + + System.out.println(result.getText()); + // 输出: {"name": "张三", "email": "zhangsan@example.com", "phone": "(555) 123-4567"} + } + + /** + * 示例2:复杂嵌套 Schema + */ + public static void complexNestedSchema() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // Use BeanOutputConverter to generate outputSchema + BeanOutputConverter outputConverter = new BeanOutputConverter<>(ProductReview.class); + String format = outputConverter.getFormat(); + + ReactAgent agent = ReactAgent.builder() + .name("review_analyzer") + .model(chatModel) + .outputSchema(format) + .build(); + + AssistantMessage result = agent.call( + "分析评价:这个产品很棒,5星好评。配送快速,但价格稍贵。" + ); + + System.out.println(result.getText()); + // 输出: {"rating": 5, "sentiment": "正面", "keyPoints": [...], "details": {...}} + } + + /** + * 示例3:结构化分析 Schema + */ + public static void structuredAnalysisSchema() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // Use BeanOutputConverter to generate outputSchema + BeanOutputConverter outputConverter = new BeanOutputConverter<>(TextAnalysis.class); + String format = outputConverter.getFormat(); + + ReactAgent agent = ReactAgent.builder() + .name("text_analyzer") + .model(chatModel) + .outputSchema(format) + .build(); + + AssistantMessage result = agent.call( + "分析这段文字:昨天,李明在北京参加了阿里巴巴公司的技术大会,感受到了创新的力量。" + ); + + System.out.println(result.getText()); + } + + // ==================== 输出 Schema 策略 ==================== + + /** + * 示例4:使用 outputType - ContactInfo + */ + public static void outputTypeContactInfo() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("contact_extractor") + .model(chatModel) + .outputType(ContactInfo.class) + .saver(new MemorySaver()) + .build(); + + AssistantMessage result = agent.call( + "从以下信息提取联系方式:张三,zhangsan@example.com,(555) 123-4567" + ); + + System.out.println(result.getText()); + } + + /** + * 示例5:使用 outputType - ProductReview + */ + public static void outputTypeProductReview() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("review_analyzer") + .model(chatModel) + .outputType(ProductReview.class) + .saver(new MemorySaver()) + .build(); + + AssistantMessage result = agent.call( + "分析评价:这个产品很棒,5星好评。配送快速,但价格稍贵。" + ); + + System.out.println(result.getText()); + } + + /** + * 示例6:使用 outputType - TextAnalysis + */ + public static void outputTypeTextAnalysis() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("text_analyzer") + .model(chatModel) + .outputType(TextAnalysis.class) + .saver(new MemorySaver()) + .build(); + + AssistantMessage result = agent.call( + "分析这段文字:昨天,李明在北京参加了阿里巴巴公司的技术大会,感受到了创新的力量。" + ); + + System.out.println(result.getText()); + } + + // ==================== 输出类型策略 ==================== + + /** + * 示例7:Try-Catch 模式 + */ + public static void tryCatchPattern() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("data_extractor") + .model(chatModel) + .outputType(ContactInfo.class) + .build(); + + try { + AssistantMessage result = agent.call("提取数据"); + ObjectMapper mapper = new ObjectMapper(); + ContactInfo data = mapper.readValue(result.getText(), ContactInfo.class); + // 处理数据 + System.out.println("Name: " + data.getName()); + } + catch (JsonProcessingException | GraphRunnerException e) { + System.err.println("JSON解析失败: " + e.getMessage()); + // 回退处理 + } + } + + /** + * 示例8:验证模式 + */ + public static void validationPattern() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("validated_agent") + .model(chatModel) + .outputType(ValidatedOutput.class) + .build(); + + try { + AssistantMessage result = agent.call("生成评价"); + ObjectMapper mapper = new ObjectMapper(); + ValidatedOutput output = mapper.readValue(result.getText(), ValidatedOutput.class); + output.validate(); // 如果无效则抛出异常 + System.out.println("Valid output: " + output.getTitle()); + } + catch (Exception e) { + System.err.println("Validation failed: " + e.getMessage()); + } + } + + /** + * 示例9:重试模式 + */ + public static void retryPattern() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ReactAgent agent = ReactAgent.builder() + .name("retry_agent") + .model(chatModel) + .outputType(ContactInfo.class) + .build(); + + int maxRetries = 3; + ContactInfo data = null; + ObjectMapper mapper = new ObjectMapper(); + + for (int i = 0; i < maxRetries; i++) { + try { + AssistantMessage result = agent.call("提取数据"); + data = mapper.readValue(result.getText(), ContactInfo.class); + break; // 成功 + } + catch (Exception e) { + if (i == maxRetries - 1) { + throw new RuntimeException("多次尝试后仍然失败", e); + } + System.out.println("第" + (i + 1) + "次尝试失败,重试中..."); + } + } + + if (data != null) { + System.out.println("Successfully extracted: " + data.getName()); + } + } + + // ==================== 错误处理 ==================== + + /** + * 示例10:完整的结构化输出示例 + */ + public static void comprehensiveExample() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 使用 outputType + ReactAgent typeAgent = ReactAgent.builder() + .name("type_agent") + .model(chatModel) + .outputType(ContactInfo.class) + .saver(new MemorySaver()) + .build(); + + // 使用 outputSchema (通过 BeanOutputConverter 生成) + BeanOutputConverter outputConverter = new BeanOutputConverter<>(ContactInfo.class); + String format = outputConverter.getFormat(); + + ReactAgent schemaAgent = ReactAgent.builder() + .name("schema_agent") + .model(chatModel) + .outputSchema(format) + .saver(new MemorySaver()) + .build(); + + String input = "联系人:王五,wangwu@example.com,13800138000"; + + // 使用 outputType + AssistantMessage typeResult = typeAgent.call(input); + System.out.println("Type-based: " + typeResult.getText()); + + // 使用 outputSchema + AssistantMessage schemaResult = schemaAgent.call(input); + System.out.println("Schema-based: " + schemaResult.getText()); + } + + public static void main(String[] args) { + System.out.println("=== Structured Output Tutorial Examples ==="); + System.out.println("注意:需要设置 AI_DASHSCOPE_API_KEY 环境变量\n"); + + try { +// System.out.println("\n--- 示例1:基础 JSON Schema ---"); +// basicJsonSchema(); + + System.out.println("\n--- 示例2:复杂嵌套 Schema ---"); + // complexNestedSchema(); +// +// System.out.println("\n--- 示例3:结构化分析 Schema ---"); + structuredAnalysisSchema(); +// +// System.out.println("\n--- 示例4:OutputType - 联系信息 ---"); +// outputTypeContactInfo(); +// +// System.out.println("\n--- 示例5:OutputType - 产品评论 ---"); +// outputTypeProductReview(); +// +// System.out.println("\n--- 示例6:OutputType - 文本分析 ---"); +// outputTypeTextAnalysis(); +// +// System.out.println("\n--- 示例7:Try-Catch 模式 ---"); +// tryCatchPattern(); +// +// System.out.println("\n--- 示例8:验证模式 ---"); +// validationPattern(); +// +// System.out.println("\n--- 示例9:重试模式 ---"); +// retryPattern(); +// +// System.out.println("\n--- 示例10:综合示例 ---"); +// comprehensiveExample(); + + System.out.println("\n=== 所有示例执行完成 ==="); + } + catch (Exception e) { + System.err.println("执行示例时发生错误: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 联系信息输出类 + */ + public static class ContactInfo { + private String name; + private String email; + private String phone; + + // Getters and Setters + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + } + + /** + * 产品评价输出类 + */ + public static class ProductReview { + private int rating; + private String sentiment; + private String[] keyPoints; + private ReviewDetails details; + + public int getRating() { + return rating; + } + + public void setRating(int rating) { + this.rating = rating; + } + + public String getSentiment() { + return sentiment; + } + + public void setSentiment(String sentiment) { + this.sentiment = sentiment; + } + + public String[] getKeyPoints() { + return keyPoints; + } + + public void setKeyPoints(String[] keyPoints) { + this.keyPoints = keyPoints; + } + + public ReviewDetails getDetails() { + return details; + } + + public void setDetails(ReviewDetails details) { + this.details = details; + } + + public static class ReviewDetails { + private String[] pros; + private String[] cons; + + public String[] getPros() { + return pros; + } + + public void setPros(String[] pros) { + this.pros = pros; + } + + public String[] getCons() { + return cons; + } + + public void setCons(String[] cons) { + this.cons = cons; + } + } + } + + // ==================== 综合示例 ==================== + + /** + * 文本分析输出类 + */ + public static class TextAnalysis { + private String summary; + private String[] keywords; + private String sentiment; + private Entities entities; + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public String[] getKeywords() { + return keywords; + } + + public void setKeywords(String[] keywords) { + this.keywords = keywords; + } + + public String getSentiment() { + return sentiment; + } + + public void setSentiment(String sentiment) { + this.sentiment = sentiment; + } + + public Entities getEntities() { + return entities; + } + + public void setEntities(Entities entities) { + this.entities = entities; + } + + public static class Entities { + private String[] persons; + private String[] locations; + private String[] organizations; + + public String[] getPersons() { + return persons; + } + + public void setPersons(String[] persons) { + this.persons = persons; + } + + public String[] getLocations() { + return locations; + } + + public void setLocations(String[] locations) { + this.locations = locations; + } + + public String[] getOrganizations() { + return organizations; + } + + public void setOrganizations(String[] organizations) { + this.organizations = organizations; + } + } + } + + // ==================== Main 方法 ==================== + + /** + * 验证输出类 + */ + public static class ValidatedOutput { + private String title; + private Integer rating; + + public void validate() throws IllegalArgumentException { + if (title == null || title.isEmpty()) { + throw new IllegalArgumentException("标题不能为空"); + } + if (rating != null && (rating < 1 || rating > 5)) { + throw new IllegalArgumentException("评分必须在1-5之间"); + } + } + + // Getter 和 Setter 方法 + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Integer getRating() { + return rating; + } + + public void setRating(Integer rating) { + this.rating = rating; + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/ToolsExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/ToolsExample.java new file mode 100644 index 00000000..c59fa8a3 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/ToolsExample.java @@ -0,0 +1,957 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.framework.tutorials; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; + +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.ai.tool.annotation.ToolParam; +import org.springframework.ai.tool.function.FunctionToolCallback; +import org.springframework.ai.tool.resolution.StaticToolCallbackResolver; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Tools Tutorial - 完整代码示例 + * 展示如何创建和使用Tools让Agent与外部系统交互 + * + * 来源:tools.md + */ +public class ToolsExample { + + // ==================== 基础工具定义 ==================== + + /** + * 示例1:编程方式规范 - FunctionToolCallback + */ + public static void programmaticToolSpecification() { + ToolCallback toolCallback = FunctionToolCallback + .builder("currentWeather", new WeatherService()) + .description("Get the weather in location") + .inputType(WeatherRequest.class) + .build(); + } + + /** + * 示例2:添加工具到 ChatClient(使用编程规范) + */ + public static void addToolToChatClient() { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ToolCallback toolCallback = FunctionToolCallback + .builder("currentWeather", new WeatherService()) + .description("Get the weather in location") + .inputType(WeatherRequest.class) + .build(); + + // Note: ChatClient usage would be shown here in actual implementation + // This is a simplified example + } + + /** + * 示例3:自定义工具名称 + */ + public static void customToolName() { + ToolCallback searchTool = FunctionToolCallback + .builder("web_search", new SearchFunction()) // 自定义名称 + .description("Search the web for information") + .inputType(String.class) + .build(); + + System.out.println(searchTool.getToolDefinition().name()); // web_search + } + + /** + * 示例4:自定义工具描述 + */ + public static void customToolDescription() { + ToolCallback calculatorTool = FunctionToolCallback + .builder("calculator", new CalculatorFunction()) + .description("Performs arithmetic calculations. Use this for any math problems.") + .inputType(String.class) + .build(); + } + + /** + * 示例5:高级模式定义 + */ + public static void advancedSchemaDefinition() { + ToolCallback weatherTool = FunctionToolCallback + .builder("get_weather", new WeatherFunction()) + .description("Get current weather and optional forecast") + .inputType(WeatherInput.class) + .build(); + } + + /** + * 示例6:访问状态 + */ + public static void accessingState() { + // 创建工具 + ToolCallback summaryTool = FunctionToolCallback + .builder("summarize_conversation", new ConversationSummaryTool()) + .description("Summarize the conversation so far") + .inputType(String.class) + .build(); + } + + // ==================== 自定义工具属性 ==================== + + /** + * 示例7:访问上下文 + */ + public static void accessingContext() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + ToolCallback accountTool = FunctionToolCallback + .builder("get_account_info", new AccountInfoTool()) + .description("Get the current user's account information") + .inputType(String.class) + .build(); + + // 在 ReactAgent 中使用 + ReactAgent agent = ReactAgent.builder() + .name("financial_assistant") + .model(chatModel) + .tools(accountTool) + .systemPrompt("You are a financial assistant.") + .build(); + + // 调用时传递上下文 + RunnableConfig config = RunnableConfig.builder() + .addMetadata("user_id", "user123") + .build(); + + agent.call("question", config); + } + + /** + * 示例8:使用存储访问跨对话的持久数据 + */ + public static void accessingMemoryStore() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 配置持久化存储 + MemorySaver memorySaver = new MemorySaver(); + + // 创建工具 + ToolCallback saveUserInfoTool = createSaveUserInfoTool(); + ToolCallback getUserInfoTool = createGetUserInfoTool(); + + // 创建带有持久化记忆的 Agent + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .tools(saveUserInfoTool, getUserInfoTool) + .saver(memorySaver) + .build(); + + // 第一个会话:保存用户信息 + RunnableConfig config1 = RunnableConfig.builder() + .threadId("session_1") + .build(); + + agent.call("Save user: userid: abc123, name: Foo, age: 25, email: foo@example.com", config1); + + // 第二个会话:获取用户信息,注意这里用的是不同的 threadId + RunnableConfig config2 = RunnableConfig.builder() + .threadId("session_2") + .build(); + + agent.call("Get user info for user with id 'abc123'", config2); + } + + /** + * 示例9:在 ReactAgent 中使用工具 + */ + public static void toolsInReactAgent() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建工具 + ToolCallback weatherTool = FunctionToolCallback + .builder("get_weather", new WeatherFunction()) + .description("Get weather for a given city") + .inputType(WeatherInput.class) + .build(); + + ToolCallback searchTool = FunctionToolCallback + .builder("search", new SearchFunction()) + .description("Search for information") + .inputType(String.class) + .build(); + + // 创建带有工具的 Agent + ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .tools(weatherTool, searchTool) + .systemPrompt("You are a helpful assistant with access to weather and search tools.") + .saver(new MemorySaver()) + .build(); + + // 使用 Agent + AssistantMessage response = agent.call("What's the weather like in San Francisco?"); + System.out.println(response.getText()); + } + + /** + * 示例10:完整的工具使用示例(使用 tools 方法) + */ + public static void comprehensiveToolExample() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 定义多个工具 + ToolCallback weatherTool = FunctionToolCallback + .builder("get_weather", new WeatherFunction()) + .description("Get current weather and optional forecast for a city") + .inputType(WeatherInput.class) + .build(); + + ToolCallback calculatorTool = FunctionToolCallback + .builder("calculator", new CalculatorFunction()) + .description("Perform arithmetic calculations") + .inputType(String.class) + .build(); + + ToolCallback searchTool = FunctionToolCallback + .builder("web_search", new SearchFunction()) + .description("Search the web for information") + .inputType(String.class) + .build(); + + // 创建 Agent + ReactAgent agent = ReactAgent.builder() + .name("multi_tool_agent") + .model(chatModel) + .tools(weatherTool, calculatorTool, searchTool) + .systemPrompt(""" + You are a helpful AI assistant with access to multiple tools: + - Weather information + - Calculator for math operations + - Web search for general information + + Use the appropriate tool based on the user's question. + """) + .saver(new MemorySaver()) + .build(); + + // 使用不同的工具 + RunnableConfig config = RunnableConfig.builder() + .threadId("session_1") + .build(); + + agent.call("What's the weather in New York?", config); + agent.call("Calculate 25 * 4 + 10", config); + agent.call("Search for latest AI news", config); + } + + /** + * 示例11:使用 methodTools - 基于 @Tool 注解的方法工具 + */ + public static void methodToolsExample() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建带有 @Tool 注解方法的工具对象 + CalculatorTools calculatorTools = new CalculatorTools(); + + // 使用 methodTools 方法,传入带有 @Tool 注解方法的对象 + ReactAgent agent = ReactAgent.builder() + .name("calculator_agent") + .model(chatModel) + .description("An agent that can perform calculations") + .instruction("You are a helpful calculator assistant. Use the available tools to perform calculations.") + .methodTools(calculatorTools) // 传入带有 @Tool 注解方法的对象 + .saver(new MemorySaver()) + .build(); + + RunnableConfig config = RunnableConfig.builder() + .threadId("method_tools_session") + .build(); + + agent.call("What is 15 + 27?", config); + agent.call("What is 8 * 9?", config); + } + + /** + * 示例12:使用多个 methodTools 对象 + */ + public static void multipleMethodToolsExample() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建多个工具对象 + CalculatorTools calculatorTools = new CalculatorTools(); + WeatherTools weatherTools = new WeatherTools(); + + // 可以传入多个 methodTools 对象 + ReactAgent agent = ReactAgent.builder() + .name("multi_method_tool_agent") + .model(chatModel) + .description("An agent with multiple method-based tools") + .instruction("You are a helpful assistant with calculator and weather tools.") + .methodTools(calculatorTools, weatherTools) // 传入多个工具对象 + .saver(new MemorySaver()) + .build(); + + RunnableConfig config = RunnableConfig.builder() + .threadId("multi_method_tools_session") + .build(); + + agent.call("What is 10 * 8 and what's the weather in Beijing?", config); + } + + /** + * 示例13:使用 ToolCallbackProvider + */ + public static void toolCallbackProviderExample() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建工具 + ToolCallback searchTool = FunctionToolCallback.builder("search", new SearchToolWithContext()) + .description("Search for information") + .inputType(String.class) + .build(); + + // 创建 ToolCallbackProvider + ToolCallbackProvider toolProvider = new CustomToolCallbackProvider(List.of(searchTool)); + + // 使用 toolCallbackProviders 方法 + ReactAgent agent = ReactAgent.builder() + .name("search_agent") + .model(chatModel) + .description("An agent that can search for information") + .instruction("You are a helpful assistant with search capabilities.") + .toolCallbackProviders(toolProvider) // 使用 ToolCallbackProvider + .saver(new MemorySaver()) + .build(); + + RunnableConfig config = RunnableConfig.builder() + .threadId("tool_provider_session") + .build(); + + agent.call("Search for information about Spring AI", config); + } + + /** + * 示例14:使用 toolNames 和 resolver(必须配合使用) + */ + public static void toolNamesWithResolverExample() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建工具(使用复合类型) + ToolCallback searchTool = FunctionToolCallback.builder("search", new SearchFunctionWithRequest()) + .description("Search for information") + .inputType(SearchRequest.class) + .build(); + + ToolCallback calculatorTool = FunctionToolCallback.builder("calculator", new CalculatorFunctionWithRequest()) + .description("Perform arithmetic calculations") + .inputType(CalculatorRequest.class) + .build(); + + // 创建 StaticToolCallbackResolver,包含所有工具 + StaticToolCallbackResolver resolver = new StaticToolCallbackResolver( + List.of(calculatorTool, searchTool)); + + // 使用 toolNames 指定要使用的工具名称,必须配合 resolver 使用 + ReactAgent agent = ReactAgent.builder() + .name("multi_tool_agent") + .model(chatModel) + .description("An agent with multiple tools") + .instruction("You are a helpful assistant with access to calculator and search tools.") + .toolNames("calculator", "search") // 使用工具名称而不是 ToolCallback 实例 + .resolver(resolver) // 必须提供 resolver 来解析工具名称 + .saver(new MemorySaver()) + .build(); + + RunnableConfig config = RunnableConfig.builder() + .threadId("tool_names_session") + .build(); + + agent.call("Calculate 25 + 4 and then search for information about the result", config); + } + + /** + * 示例15:使用 resolver 直接解析工具 + */ + public static void resolverExample() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建工具 + ToolCallback calculatorTool = FunctionToolCallback.builder("calculator", new CalculatorFunctionWithContext()) + .description("Perform arithmetic calculations") + .inputType(String.class) + .build(); + + // 创建 resolver + StaticToolCallbackResolver resolver = new StaticToolCallbackResolver( + List.of(calculatorTool)); + + // 使用 resolver,可以直接在 tools 中使用,也可以仅通过 resolver 提供 + ReactAgent agent = ReactAgent.builder() + .name("resolver_agent") + .model(chatModel) + .description("An agent using ToolCallbackResolver") + .instruction("You are a helpful calculator assistant.") + .tools(calculatorTool) // 直接指定工具 + .resolver(resolver) // 同时设置 resolver 供工具节点使用 + .saver(new MemorySaver()) + .build(); + + RunnableConfig config = RunnableConfig.builder() + .threadId("resolver_session") + .build(); + + agent.call("What is 100 divided by 4?", config); + } + + /** + * 示例16:组合使用多种工具提供方式 + */ + public static void combinedToolProvisionExample() throws GraphRunnerException { + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // Method tools + CalculatorTools calculatorTools = new CalculatorTools(); + + // Direct tool + ToolCallback searchTool = FunctionToolCallback.builder("search", new SearchToolWithContext()) + .description("Search for information") + .inputType(String.class) + .build(); + + // ToolCallbackProvider + ToolCallbackProvider toolProvider = new CustomToolCallbackProvider(List.of(searchTool)); + + // 组合使用多种方式 + ReactAgent agent = ReactAgent.builder() + .name("combined_tool_agent") + .model(chatModel) + .description("An agent with multiple tool provision methods") + .instruction("You are a helpful assistant with calculator and search capabilities.") + .methodTools(calculatorTools) // Method-based tools + .toolCallbackProviders(toolProvider) // Provider-based tools + .tools(searchTool) // Direct tools + .saver(new MemorySaver()) + .build(); + + RunnableConfig config = RunnableConfig.builder() + .threadId("combined_session") + .build(); + + agent.call("Calculate 50 + 75 and search for information about mathematics", config); + } + + // ==================== 高级模式定义 ==================== + + /** + * 创建保存用户信息工具 + */ + private static ToolCallback createSaveUserInfoTool() { + return FunctionToolCallback.builder("save_user_info", (String input) -> { + // 简化的实现 + return "User info saved: " + input; + }) + .description("Save user information") + .inputType(String.class) + .build(); + } + + /** + * 创建获取用户信息工具 + */ + private static ToolCallback createGetUserInfoTool() { + return FunctionToolCallback.builder("get_user_info", (String userId) -> { + // 简化的实现 + return "User info for: " + userId; + }) + .description("Get user information by ID") + .inputType(String.class) + .build(); + } + + public static void main(String[] args) { + System.out.println("=== Tools Tutorial Examples ==="); + System.out.println("注意:需要设置 AI_DASHSCOPE_API_KEY 环境变量\n"); + + try { + System.out.println("\n--- 示例1:编程式工具规范 ---"); + programmaticToolSpecification(); + +// System.out.println("\n--- 示例2:添加工具到 ChatClient ---"); +// addToolToChatClient(); +// +// System.out.println("\n--- 示例3:自定义工具名称 ---"); +// customToolName(); +// +// System.out.println("\n--- 示例4:自定义工具描述 ---"); +// customToolDescription(); +// +// System.out.println("\n--- 示例5:高级 Schema 定义 ---"); +// advancedSchemaDefinition(); +// +// System.out.println("\n--- 示例6:访问状态 ---"); +// accessingState(); +// +// System.out.println("\n--- 示例7:访问上下文 ---"); +// accessingContext(); +// +// System.out.println("\n--- 示例8:访问内存存储 ---"); +// accessingMemoryStore(); +// +// System.out.println("\n--- 示例9:ReactAgent 中的工具 ---"); +// toolsInReactAgent(); +// +// System.out.println("\n--- 示例10:综合工具示例(tools 方法) ---"); +// comprehensiveToolExample(); +// +// System.out.println("\n--- 示例11:使用 methodTools(@Tool 注解) ---"); +// methodToolsExample(); +// +// System.out.println("\n--- 示例12:多个 methodTools 对象 ---"); +// multipleMethodToolsExample(); +// +// System.out.println("\n--- 示例13:使用 ToolCallbackProvider ---"); +// toolCallbackProviderExample(); +// +// System.out.println("\n--- 示例14:使用 toolNames 和 resolver ---"); +// toolNamesWithResolverExample(); +// +// System.out.println("\n--- 示例15:使用 resolver ---"); +// resolverExample(); +// +// System.out.println("\n--- 示例16:组合使用多种工具提供方式 ---"); +// combinedToolProvisionExample(); +// +// System.out.println("\n=== 所有示例执行完成 ==="); + } + catch (Exception e) { + System.err.println("执行示例时发生错误: " + e.getMessage()); + e.printStackTrace(); + } + } + + public enum Unit {C, F} + + // ==================== 访问上下文 ==================== + + public enum UnitType {CELSIUS, FAHRENHEIT} + + /** + * 天气服务 + */ + public static class WeatherService implements Function { + @Override + public WeatherResponse apply(WeatherRequest request) { + return new WeatherResponse(30.0, Unit.C); + } + } + + // ==================== Context(上下文) ==================== + + public record WeatherRequest( + @ToolParam(description = "城市或坐标") String location, + Unit unit + ) { } + + public record WeatherResponse(double temp, Unit unit) { } + + // ==================== Memory(存储) ==================== + + /** + * 搜索函数 + */ + public static class SearchFunction implements Function { + @Override + public String apply(String query) { + return "Search results for: " + query; + } + } + + // ==================== 在 ReactAgent 中使用工具 ==================== + + /** + * 计算器函数 + */ + public static class CalculatorFunction implements Function { + @Override + public String apply(String expression) { + // 简化的计算逻辑 + return "Result: " + expression; + } + } + + // ==================== 完整示例 ==================== + + /** + * 天气输入(使用记录类) + */ + public record WeatherInput( + @ToolParam(description = "City name or coordinates") String location, + @ToolParam(description = "Temperature unit preference") Unit units, + @ToolParam(description = "Include 5-day forecast") boolean includeForecast + ) { } + + // ==================== 辅助方法 ==================== + + /** + * 天气函数(高级版) + */ + public static class WeatherFunction implements Function { + @Override + public String apply(WeatherInput input) { + double temp = input.units() == Unit.F ? 22 : 72; + String result = String.format( + "Current weather in %s: %.0f degrees %s", + input.location(), + temp, + input.units().toString().substring(0, 1).toUpperCase() + ); + + if (input.includeForecast()) { + result += "\nNext 5 days: Sunny"; + } + + return result; + } + } + + /** + * 对话摘要工具 + */ + public static class ConversationSummaryTool implements BiFunction { + + @Override + public String apply(String input, ToolContext toolContext) { + OverAllState state = (OverAllState) toolContext.getContext().get("state"); + RunnableConfig config = (RunnableConfig) toolContext.getContext().get("config"); + + // 从state中获取消息 + Optional messagesOpt = state.value("messages"); + List messages = messagesOpt.isPresent() + ? (List) messagesOpt.get() + : new ArrayList<>(); + + if (messages.isEmpty()) { + return "No conversation history available"; + } + + long userMsgs = messages.stream() + .filter(m -> m.getMessageType().getValue().equals("user")) + .count(); + long aiMsgs = messages.stream() + .filter(m -> m.getMessageType().getValue().equals("assistant")) + .count(); + long toolMsgs = messages.stream() + .filter(m -> m.getMessageType().getValue().equals("tool")) + .count(); + + return String.format( + "Conversation has %d user messages, %d AI responses, and %d tool results", + userMsgs, aiMsgs, toolMsgs + ); + } + } + + // ==================== Main 方法 ==================== + + /** + * 账户信息工具 + */ + public static class AccountInfoTool implements BiFunction { + + private static final Map> USER_DATABASE = Map.of( + "user123", Map.of( + "name", "Alice Johnson", + "account_type", "Premium", + "balance", 5000, + "email", "alice@example.com" + ), + "user456", Map.of( + "name", "Bob Smith", + "account_type", "Standard", + "balance", 1200, + "email", "bob@example.com" + ) + ); + + @Override + public String apply(String query, ToolContext toolContext) { + RunnableConfig config = (RunnableConfig) toolContext.getContext().get("config"); + String userId = (String) config.metadata("user_id").orElse(null); + + if (userId == null) { + return "User ID not provided"; + } + + Map user = USER_DATABASE.get(userId); + if (user != null) { + return String.format( + "Account holder: %s\nType: %s\nBalance: $%d", + user.get("name"), + user.get("account_type"), + user.get("balance") + ); + } + + return "User not found"; + } + } + + // ==================== MethodTools 相关类 ==================== + + /** + * 计算器工具类 - 使用 @Tool 注解 + */ + public static class CalculatorTools { + public static int callCount = 0; + + @Tool(description = "Add two numbers together") + public String add( + @ToolParam(description = "First number") int a, + @ToolParam(description = "Second number") int b) { + callCount++; + return String.valueOf(a + b); + } + + @Tool(description = "Multiply two numbers together") + public String multiply( + @ToolParam(description = "First number") int a, + @ToolParam(description = "Second number") int b) { + callCount++; + return String.valueOf(a * b); + } + + @Tool(description = "Subtract second number from first number") + public String subtract( + @ToolParam(description = "First number") int a, + @ToolParam(description = "Second number") int b) { + callCount++; + return String.valueOf(a - b); + } + } + + /** + * 天气工具类 - 使用 @Tool 注解 + */ + public static class WeatherTools { + @Tool(description = "Get current weather for a location") + public String getWeather(@ToolParam(description = "City name") String city) { + return "Sunny, 25°C in " + city; + } + + @Tool(description = "Get weather forecast for a location") + public String getForecast( + @ToolParam(description = "City name") String city, + @ToolParam(description = "Number of days") int days) { + return String.format("Weather forecast for %s for next %d days: Mostly sunny", city, days); + } + } + + // ==================== ToolCallbackProvider 相关类 ==================== + + /** + * 自定义 ToolCallbackProvider 实现 + */ + public static class CustomToolCallbackProvider implements ToolCallbackProvider { + private final List toolCallbacks; + + public CustomToolCallbackProvider(List toolCallbacks) { + this.toolCallbacks = toolCallbacks; + } + + @Override + public ToolCallback[] getToolCallbacks() { + return toolCallbacks.toArray(new ToolCallback[0]); + } + } + + /** + * 带上下文的搜索工具 + */ + public static class SearchToolWithContext implements BiFunction { + @Override + public String apply(String query, ToolContext toolContext) { + return "Search results for: " + query; + } + } + + // ==================== Resolver 相关类 ==================== + + /** + * 搜索请求类(用于复合类型) + */ + public static class SearchRequest { + @JsonProperty(required = true) + @JsonPropertyDescription("The search query string") + public String query; + + public SearchRequest() { + } + + public SearchRequest(String query) { + this.query = query; + } + } + + /** + * 使用复合类型的搜索函数 + */ + public static class SearchFunctionWithRequest implements BiFunction { + @Override + public String apply(SearchRequest request, ToolContext toolContext) { + return "Search results for: " + request.query; + } + } + + /** + * 计算器请求类(用于复合类型) + */ + public static class CalculatorRequest { + @JsonProperty(required = true) + @JsonPropertyDescription("First number for the calculation") + public int a; + + @JsonProperty(required = true) + @JsonPropertyDescription("Second number for the calculation") + public int b; + + public CalculatorRequest() { + } + + public CalculatorRequest(int a, int b) { + this.a = a; + this.b = b; + } + } + + /** + * 使用复合类型的计算器函数 + */ + public static class CalculatorFunctionWithRequest implements BiFunction { + @Override + public String apply(CalculatorRequest request, ToolContext toolContext) { + return String.valueOf(request.a + request.b); + } + } + + /** + * 带上下文的计算器函数 + */ + public static class CalculatorFunctionWithContext implements BiFunction { + @Override + public String apply(String expression, ToolContext toolContext) { + // 简单的计算解析(用于演示) + if (expression.contains("/")) { + String[] parts = expression.split("/"); + double result = Double.parseDouble(parts[0].trim()) / Double.parseDouble(parts[1].trim()); + return String.valueOf(result); + } + if (expression.contains("*")) { + String[] parts = expression.split("\\*"); + double result = Double.parseDouble(parts[0].trim()) * Double.parseDouble(parts[1].trim()); + return String.valueOf(result); + } + return "Calculation result for: " + expression; + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/QuickStartExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/QuickStartExample.java new file mode 100644 index 00000000..ae0a6014 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/QuickStartExample.java @@ -0,0 +1,666 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.*; +import com.alibaba.cloud.ai.graph.action.NodeAction; +import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; +import com.alibaba.fastjson.JSON; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import reactor.core.publisher.Flux; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * Graph 工作流编排快速入门示例 + * + * 本示例演示如何通过将客服邮件处理流程分解为离散步骤来使用 Spring AI Alibaba Graph 构建智能工作流。 + * + * 示例包含: + * 1. 状态定义(EmailClassification) + * 2. 节点实现(读取邮件、分类意图、搜索文档、Bug跟踪、起草回复、人工审核、发送回复) + * 3. Graph 组装和配置 + * 4. 测试执行 + */ +public class QuickStartExample { + + private static final Logger log = LoggerFactory.getLogger(QuickStartExample.class); + + // ==================== 状态定义 ==================== + + /** + * 邮件分类结构 + */ + public static class EmailClassification { + private String intent; // "question", "bug", "billing", "feature", "complex" + private String urgency; // "low", "medium", "high", "critical" + private String topic; + private String summary; + + public EmailClassification() { + } + + public EmailClassification(String intent, String urgency, String topic, String summary) { + this.intent = intent; + this.urgency = urgency; + this.topic = topic; + this.summary = summary; + } + + public String getIntent() { + return intent; + } + + public void setIntent(String intent) { + this.intent = intent; + } + + public String getUrgency() { + return urgency; + } + + public void setUrgency(String urgency) { + this.urgency = urgency; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + @Override + public String toString() { + return String.format("EmailClassification{intent='%s', urgency='%s', topic='%s', summary='%s'}", + intent, urgency, topic, summary); + } + } + + /** + * 配置状态键策略 + */ + public static KeyStrategyFactory createKeyStrategyFactory() { + return () -> { + HashMap strategies = new HashMap<>(); + strategies.put("email_content", new ReplaceStrategy()); + strategies.put("sender_email", new ReplaceStrategy()); + strategies.put("email_id", new ReplaceStrategy()); + strategies.put("classification", new ReplaceStrategy()); + strategies.put("search_results", new ReplaceStrategy()); + strategies.put("customer_history", new ReplaceStrategy()); + strategies.put("draft_response", new ReplaceStrategy()); + strategies.put("messages", new AppendStrategy()); + strategies.put("next_node", new ReplaceStrategy()); + strategies.put("status", new ReplaceStrategy()); + strategies.put("review_data", new ReplaceStrategy()); + return strategies; + }; + } + + // ==================== 节点实现 ==================== + + /** + * 读取邮件节点 + */ + public static class ReadEmailNode implements NodeAction { + + @Override + public Map apply(OverAllState state) throws Exception { + // 在生产环境中,这将连接到您的邮件服务 + String emailContent = state.value("email_content") + .map(v -> (String) v) + .orElse(""); + + log.info("ReadEmailNode----Processing email: {}", emailContent); + + List messages = new ArrayList<>(); + messages.add("Processing email: " + emailContent); + + return Map.of("messages", messages); + } + } + + /** + * 分类意图节点 + */ + public static class ClassifyIntentNode implements NodeAction { + + private final ChatClient chatClient; + + public ClassifyIntentNode(ChatClient.Builder chatClientBuilder) { + this.chatClient = chatClientBuilder.build(); + } + + @Override + public Map apply(OverAllState state) throws Exception { + String emailContent = state.value("email_content") + .map(v -> (String) v) + .orElseThrow(() -> new IllegalStateException("No email content")); + String senderEmail = state.value("sender_email") + .map(v -> (String) v) + .orElse("unknown"); + + // 按需格式化提示,不存储在状态中 + String classificationPrompt = String.format(""" + 分析这封客户邮件并进行分类: + + 邮件: %s + 发件人: %s + + 提供分类,包括意图、紧急程度、主题和摘要。 + + 意图应该是以下之一: question, bug, billing, feature, complex + 紧急程度应该是以下之一: low, medium, high, critical + + 以JSON格式返回: {"intent": "...", "urgency": "...", "topic": "...", "summary": "..."} + """, emailContent, senderEmail); + + // 获取结构化响应 + String response = chatClient.prompt() + .user(classificationPrompt) + .call() + .content(); + + // 解析为 EmailClassification 对象 + EmailClassification classification = JSON.parseObject(response, EmailClassification.class); + + // 根据分类确定下一个节点 + String nextNode; + if ("billing".equals(classification.getIntent()) || + "critical".equals(classification.getUrgency())) { + nextNode = "human_review"; + } else if (List.of("question", "feature").contains(classification.getIntent())) { + nextNode = "search_documentation"; + } else if ("bug".equals(classification.getIntent())) { + nextNode = "bug_tracking"; + } else { + nextNode = "draft_response"; + } + + // 将分类作为单个对象存储在状态中 + return Map.of( + "classification", classification, + "next_node", nextNode + ); + } + + /** + * 简化的JSON解析(实际应用中使用Jackson或Gson) + */ + private EmailClassification parseClassification(String jsonResponse) { + EmailClassification classification = new EmailClassification(); + + // 简单的正则表达式解析 + Pattern intentPattern = Pattern.compile("\"intent\"\\s*:\\s*\"([^\"]+)\""); + Pattern urgencyPattern = Pattern.compile("\"urgency\"\\s*:\\s*\"([^\"]+)\""); + Pattern topicPattern = Pattern.compile("\"topic\"\\s*:\\s*\"([^\"]+)\""); + Pattern summaryPattern = Pattern.compile("\"summary\"\\s*:\\s*\"([^\"]+)\""); + + Matcher matcher = intentPattern.matcher(jsonResponse); + if (matcher.find()) { + classification.setIntent(matcher.group(1)); + } + + matcher = urgencyPattern.matcher(jsonResponse); + if (matcher.find()) { + classification.setUrgency(matcher.group(1)); + } + + matcher = topicPattern.matcher(jsonResponse); + if (matcher.find()) { + classification.setTopic(matcher.group(1)); + } + + matcher = summaryPattern.matcher(jsonResponse); + if (matcher.find()) { + classification.setSummary(matcher.group(1)); + } + + // 如果解析失败,设置默认值 + if (classification.getIntent() == null) { + classification.setIntent("question"); + } + if (classification.getUrgency() == null) { + classification.setUrgency("medium"); + } + if (classification.getTopic() == null) { + classification.setTopic("general"); + } + if (classification.getSummary() == null) { + classification.setSummary("需要处理的客户邮件"); + } + + return classification; + } + } + + /** + * 文档搜索节点 + */ + public static class SearchDocumentationNode implements NodeAction { + + @Override + public Map apply(OverAllState state) throws Exception { + // 从分类构建搜索查询 + EmailClassification classification = state.value("classification") + .map(v -> (EmailClassification) v) + .orElse(new EmailClassification()); + String query = classification.getIntent() + " " + classification.getTopic(); + + try { + // 实现您的搜索逻辑 + // 存储原始搜索结果,而不是格式化的文本 + List searchResults = List.of( + "通过设置 > 安全 > 更改密码重置密码", + "密码必须至少12个字符", + "包含大写字母、小写字母、数字和符号" + ); + + log.info("SearchDocumentationNode--Searching documentation for: {}", query); + + return Map.of( + "search_results", searchResults, + "next_node", "draft_response" + ); + } catch (Exception e) { + // 对于可恢复的搜索错误,存储错误并继续 + log.warn("SearchDocumentationNode--Search error: {}", e.getMessage()); + List errorResult = List.of("搜索暂时不可用: " + e.getMessage()); + return Map.of( + "search_results", errorResult, + "next_node", "draft_response" + ); + } + } + } + + /** + * Bug跟踪节点 + */ + public static class BugTrackingNode implements NodeAction { + + @Override + public Map apply(OverAllState state) throws Exception { + // 在您的bug跟踪系统中创建票据 + String ticketId = "BUG-12345"; // 将通过API创建 + + log.info("BugTrackingNode---Created bug ticket: {}", ticketId); + + return Map.of( + "search_results", List.of("已创建Bug票据 " + ticketId), + "current_step", "bug_tracked", + "next_node", "draft_response" + ); + } + } + + /** + * 起草回复节点 + */ + public static class DraftResponseNode implements NodeAction { + + private final ChatClient chatClient; + + public DraftResponseNode(ChatClient.Builder chatClientBuilder) { + this.chatClient = chatClientBuilder.build(); + } + + @Override + public Map apply(OverAllState state) throws Exception { + EmailClassification classification = state.value("classification") + .map(v -> (EmailClassification) v) + .orElse(new EmailClassification()); + String emailContent = state.value("email_content") + .map(v -> (String) v) + .orElse(""); + + // 从原始状态数据按需格式化上下文 + List contextSections = new ArrayList<>(); + + Optional> searchResults = state.value("search_results") + .map(v -> (List) v); + if (searchResults.isPresent()) { + // 为提示格式化搜索结果 + List docs = searchResults.get(); + String formattedDocs = docs.stream() + .map(doc -> "- " + doc) + .collect(Collectors.joining("\n")); + contextSections.add("相关文档:\n" + formattedDocs); + } + + Optional> customerHistory = state.value("customer_history") + .map(v -> (Map) v); + if (customerHistory.isPresent()) { + // 为提示格式化客户数据 + Map history = customerHistory.get(); + contextSections.add("客户等级: " + history.getOrDefault("tier", "standard")); + } + + // 使用格式化的上下文构建提示 + String draftPrompt = String.format(""" + 为这封客户邮件起草回复: + %s + + 邮件意图: %s + 紧急程度: %s + + %s + + 指南: + - 专业且有帮助 + - 解决他们的具体问题 + - 在相关时使用提供的文档 + """, + emailContent, + classification.getIntent(), + classification.getUrgency(), + String.join("\n", contextSections) + ); + + String response = chatClient.prompt() + .user(draftPrompt) + .call() + .content(); + + // 根据紧急程度和意图确定是否需要人工审核 + boolean needsReview = + List.of("high", "critical").contains(classification.getUrgency()) || + "complex".equals(classification.getIntent()); + + // 路由到适当的下一个节点 + String nextNode = needsReview ? "human_review" : "send_reply"; + log.info("DraftResponseNode--Routing to {}", nextNode); + return Map.of( + "draft_response", response, // 仅存储原始响应 + "next_node", nextNode + ); + } + } + + /** + * 人工审核节点 + * + * 注意:在 interruptBefore 模式下,中断是在编译配置中设置的(见 createEmailAgentGraph 方法)。 + * 节点本身不需要做任何特殊处理,只需要正常返回状态即可。 + * 当执行到此节点前时,Graph 会自动中断,等待人工输入。 + */ + public static class HumanReviewNode implements NodeAction { + + @Override + public Map apply(OverAllState state) throws Exception { + EmailClassification classification = state.value("classification") + .map(v -> (EmailClassification) v) + .orElse(new EmailClassification()); + + // 准备审核数据 + Map reviewData = Map.of( + "email_id", state.value("email_id").map(v -> (String) v).orElse(""), + "original_email", state.value("email_content").map(v -> (String) v).orElse(""), + "draft_response", state.value("draft_response").map(v -> (String) v).orElse(""), + "urgency", classification.getUrgency(), + "intent", classification.getIntent(), + "action", "请审核并批准/编辑此响应" + ); + + log.info("HumanReviewNode---Waiting for human review: {}", reviewData); + + // 返回审核数据和下一个节点 + // 注意:在 interruptBefore 模式下,此节点在人工输入后才会执行 + return Map.of( + "review_data", reviewData, + "status", "waiting_for_review", + "next_node", "send_reply" + ); + } + } + + /** + * 发送回复节点 + */ + public static class SendReplyNode implements NodeAction { + + @Override + public Map apply(OverAllState state) throws Exception { + String draftResponse = state.value("draft_response") + .map(v -> (String) v) + .orElse(""); + + // 与邮件服务集成 + log.info("SendReplyNode---Sending reply: {}...", + draftResponse.length() > 100 + ? draftResponse.substring(0, 100) + : draftResponse); + + return Map.of("status", "sent"); + } + } + + // ==================== Graph 组装 ==================== + + /** + * 创建邮件处理 Graph + */ + public static CompiledGraph createEmailAgentGraph(ChatModel chatModel) throws GraphStateException { + // 配置 ChatClient + ChatClient.Builder chatClientBuilder = ChatClient.builder(chatModel); + + // 创建节点 + var readEmail = node_async(new ReadEmailNode()); + var classifyIntent = node_async(new ClassifyIntentNode(chatClientBuilder)); + var searchDocumentation = node_async(new SearchDocumentationNode()); + var bugTracking = node_async(new BugTrackingNode()); + var draftResponse = node_async(new DraftResponseNode(chatClientBuilder)); + var humanReview = node_async(new HumanReviewNode()); + var sendReply = node_async(new SendReplyNode()); + + // 创建图 + StateGraph workflow = new StateGraph(createKeyStrategyFactory()) + .addNode("read_email", readEmail) + .addNode("classify_intent", classifyIntent) + .addNode("search_documentation", searchDocumentation) + .addNode("bug_tracking", bugTracking) + .addNode("draft_response", draftResponse) + .addNode("human_review", humanReview) + .addNode("send_reply", sendReply); + + // 添加基本边 + workflow.addEdge(START, "read_email"); + workflow.addEdge("read_email", "classify_intent"); + workflow.addEdge("send_reply", END); + + // 添加条件边(基于节点返回的 next_node) + workflow.addConditionalEdges("classify_intent", + edge_async(state -> { + return (String) state.value("next_node").orElse("draft_response"); + }), + Map.of( + "search_documentation", "search_documentation", + "bug_tracking", "bug_tracking", + "human_review", "human_review", + "draft_response", "draft_response" + )); + + workflow.addConditionalEdges("draft_response", + edge_async(state -> { + return (String) state.value("next_node").orElse("send_reply"); + }), + Map.of( + "human_review", "human_review", + "send_reply", "send_reply" + )); + + workflow.addConditionalEdges("human_review", + edge_async(state -> { + return (String) state.value("next_node").orElse("send_reply"); + }), + Map.of( + "send_reply", "send_reply" + )); + + workflow.addEdge("search_documentation", "draft_response"); + workflow.addEdge("bug_tracking", "draft_response"); + + // 配置持久化 + var memory = new MemorySaver(); + var compileConfig = CompileConfig.builder() + .saverConfig(SaverConfig.builder() + .register(memory) + .build()) + .interruptBefore("human_review") // 在人工审核前中断 + .build(); + + return workflow.compile(compileConfig); + } + + // ==================== 测试方法 ==================== + + /** + * 测试紧急账单问题 + */ + public static void testBillingIssue(CompiledGraph app) throws Exception { + log.info("=== 测试紧急账单问题 ==="); + + // 测试紧急账单问题 + Map initialState = Map.of( + "email_content", "我的订阅被收费两次了!这很紧急!", + "sender_email", "customer@example.com", + "email_id", "email_123", + "messages", new ArrayList() + ); + + // 使用 thread_id 运行以实现持久化 + var config = RunnableConfig.builder() + .threadId("customer_123") + .build(); + + // 使用 stream 执行,直到中断点(human_review) + // 图将在 human_review 处暂停(因为配置了 interruptBefore) + Flux stream = app.stream(initialState, config); + stream + .doOnNext(output -> log.info("节点输出: {}", output)) + .doOnError(error -> log.error("执行错误: {}", error.getMessage())) + .doOnComplete(() -> log.info("流完成")) + .blockLast(); + + // 获取当前状态,检查是否有草稿回复 + var currentState = app.getState(config); + Map stateData = currentState.state().data(); + String draftResponse = (String) stateData.get("draft_response"); + if (draftResponse != null) { + log.info("Draft ready for review: {}...", + draftResponse.length() > 100 + ? draftResponse.substring(0, 100) + : draftResponse); + } + + // 准备好后,提供人工输入以恢复 + // 使用 updateState 更新状态(interruptBefore 模式下,传入 null 作为节点 ID) + var updatedConfig = app.updateState(config, Map.of( + "approved", true, + "edited_response", "我们对重复收费深表歉意。我已经立即启动了退款..." + ), null); + + // 继续执行(input 为 null,使用之前的状态) + app.stream(null, updatedConfig) + .doOnNext(output -> log.info("节点输出: {}", output)) + .doOnError(error -> log.error("执行错误: {}", error.getMessage())) + .doOnComplete(() -> log.info("流完成")) + .blockLast(); + +// 获取最终状态 + var finalState = app.getState(updatedConfig); + String status = (String) finalState.state().data().get("status"); + log.info("Email sent successfully! Status: {}", status); + TimeUnit.SECONDS.sleep(999999); + } + + /** + * 测试简单问题 + */ + public static void testSimpleQuestion(CompiledGraph app) throws Exception { + log.info("=== 测试简单问题 ==="); + + Map initialState = Map.of( + "email_content", "如何重置我的密码?", + "sender_email", "user@example.com", + "email_id", "email_456", + "messages", new ArrayList() + ); + + var config = RunnableConfig.builder() + .threadId("user_456") + .build(); + + // invoke 返回 Optional,需要使用 orElseThrow() 获取结果 + var result = app.invoke(initialState, config).orElseThrow(); + log.info("Simple question processed. Status: {}", result.data().get("status")); + TimeUnit.SECONDS.sleep(999999); + } + + /** + * 主方法 + */ + public static void main(String[] args) throws Exception { + log.info("========================================"); + log.info("Graph 工作流编排快速入门示例"); + log.info("========================================\n"); + + // 注意:实际使用时需要提供 ChatModel 实例 + // 创建 DashScope API 实例 + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(System.getenv("ALI_AI_KEY")) + .build(); + + // 创建 ChatModel + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + CompiledGraph app = createEmailAgentGraph(chatModel); + + testBillingIssue(app); +// testSimpleQuestion(app); + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/QuickStartExample1.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/QuickStartExample1.java new file mode 100644 index 00000000..9ca7bf06 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/QuickStartExample1.java @@ -0,0 +1,264 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph; + +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.action.NodeAction; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; +import com.alibaba.fastjson.JSON; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.client.ChatClient; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Graph 工作流编排快速入门示例 + * + * 本示例演示如何通过将客服邮件处理流程分解为离散步骤来使用 Spring AI Alibaba Graph 构建智能工作流。 + * + * 示例包含: + * 1. 状态定义(EmailClassification) + * 2. 节点实现(读取邮件、分类意图、搜索文档、Bug跟踪、起草回复、人工审核、发送回复) + * 3. Graph 组装和配置 + * 4. 测试执行 + */ +public class QuickStartExample1 { + + private static final Logger log = LoggerFactory.getLogger(QuickStartExample1.class); + + /** + * 邮件分类 + */ + public static class EmailClassification { + private String intent; + + private String urgency; + + private String topic; + + private String summary; + + public EmailClassification() { + } + + public EmailClassification(String intent, String urgency, String topic, String summary) { + this.intent = intent; + this.urgency = urgency; + this.topic = topic; + this.summary = summary; + } + + public String getIntent() { + return intent; + } + + public void setIntent(String intent) { + this.intent = intent; + } + + public String getUrgency() { + return urgency; + } + + public void setUrgency(String urgency) { + this.urgency = urgency; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + @Override + public String toString() { + return String.format("EmailClassification{intent='%s', urgency='%s', topic='%s', summary='%s'}", + intent, urgency, topic, summary); + } + } + + /** + * 配置key状态策略 + */ + public static KeyStrategyFactory createKeyStrategyFactory() { + return () -> { + HashMap strategies = new HashMap<>(); + strategies.put("email_content", new ReplaceStrategy()); + strategies.put("sender_email", new ReplaceStrategy()); + strategies.put("email_id", new ReplaceStrategy()); + strategies.put("classification", new ReplaceStrategy()); + strategies.put("search_results", new ReplaceStrategy()); + strategies.put("customer_history", new ReplaceStrategy()); + strategies.put("draft_response", new ReplaceStrategy()); + strategies.put("messages", new AppendStrategy()); + strategies.put("next_node", new ReplaceStrategy()); + strategies.put("status", new ReplaceStrategy()); + strategies.put("review_data", new ReplaceStrategy()); + return strategies; + }; + } + + /** + * 读取邮件 + */ + public static class ReadEmailNode implements NodeAction { + + @Override + public Map apply(OverAllState state) throws Exception { + String emailContent = state.value("email_content").map(v -> (String)v).orElse(""); + log.info("ReadEmailNode----Processing email: {}", emailContent); + ArrayList messages = new ArrayList<>(); + messages.add("Processing email: " + emailContent); + return Map.of("messages", messages); + } + } + + /** + * 分类意图节点 + */ + public static class ClassifyIntentNode implements NodeAction { + + private final ChatClient chatClient; + + public ClassifyIntentNode(ChatClient.Builder chatClientBuilder) { + this.chatClient = chatClientBuilder.build(); + } + + @Override + public Map apply(OverAllState state) throws Exception { + String emailContent = state.value("email_content") + .map(v -> (String) v) + .orElseThrow(() -> new IllegalStateException("No email content")); + String senderEmail = state.value("sender_email") + .map(v -> (String) v) + .orElse("unknown"); + + // 按需格式化提示,不存储在状态中 + String classificationPrompt = String.format(""" + 分析这封客户邮件并进行分类: + + 邮件: %s + 发件人: %s + + 提供分类,包括意图、紧急程度、主题和摘要。 + + 意图应该是以下之一: question, bug, billing, feature, complex + 紧急程度应该是以下之一: low, medium, high, critical + + 以JSON格式返回: {"intent": "...", "urgency": "...", "topic": "...", "summary": "..."} + """, emailContent, senderEmail); + + // 获取结构化响应 + String response = chatClient.prompt() + .user(classificationPrompt) + .call() + .content(); + + // 解析为 EmailClassification 对象 + QuickStartExample.EmailClassification classification = JSON.parseObject(response, QuickStartExample.EmailClassification.class); + + // 根据分类确定下一个节点 + String nextNode; + if ("billing".equals(classification.getIntent()) || + "critical".equals(classification.getUrgency())) { + nextNode = "human_review"; + } else if (List.of("question", "feature").contains(classification.getIntent())) { + nextNode = "search_documentation"; + } else if ("bug".equals(classification.getIntent())) { + nextNode = "bug_tracking"; + } else { + nextNode = "draft_response"; + } + + // 将分类作为单个对象存储在状态中 + return Map.of( + "classification", classification, + "next_node", nextNode + ); + } + + /** + * 简化的JSON解析(实际应用中使用Jackson或Gson) + */ + private QuickStartExample.EmailClassification parseClassification(String jsonResponse) { + QuickStartExample.EmailClassification classification = new QuickStartExample.EmailClassification(); + + // 简单的正则表达式解析 + Pattern intentPattern = Pattern.compile("\"intent\"\\s*:\\s*\"([^\"]+)\""); + Pattern urgencyPattern = Pattern.compile("\"urgency\"\\s*:\\s*\"([^\"]+)\""); + Pattern topicPattern = Pattern.compile("\"topic\"\\s*:\\s*\"([^\"]+)\""); + Pattern summaryPattern = Pattern.compile("\"summary\"\\s*:\\s*\"([^\"]+)\""); + + Matcher matcher = intentPattern.matcher(jsonResponse); + if (matcher.find()) { + classification.setIntent(matcher.group(1)); + } + + matcher = urgencyPattern.matcher(jsonResponse); + if (matcher.find()) { + classification.setUrgency(matcher.group(1)); + } + + matcher = topicPattern.matcher(jsonResponse); + if (matcher.find()) { + classification.setTopic(matcher.group(1)); + } + + matcher = summaryPattern.matcher(jsonResponse); + if (matcher.find()) { + classification.setSummary(matcher.group(1)); + } + + // 如果解析失败,设置默认值 + if (classification.getIntent() == null) { + classification.setIntent("question"); + } + if (classification.getUrgency() == null) { + classification.setUrgency("medium"); + } + if (classification.getTopic() == null) { + classification.setTopic("general"); + } + if (classification.getSummary() == null) { + classification.setSummary("需要处理的客户邮件"); + } + + return classification; + } + } + + +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/core/CoreLibraryExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/core/CoreLibraryExample.java new file mode 100644 index 00000000..8ceb832e --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/core/CoreLibraryExample.java @@ -0,0 +1,158 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.core; + +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.RemoveByHash; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; + +import java.util.HashMap; +import java.util.Map; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 核心库概念指南示例 + * 演示 State、Nodes、Edges 的基本用法 + */ +public class CoreLibraryExample { + + /** + * 示例 A: 使用 AppendStrategy + */ + public static KeyStrategyFactory createKeyStrategyFactory() { + return () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("messages", new AppendStrategy()); + return keyStrategyMap; + }; + } + + /** + * 示例 B: 自定义 KeyStrategyFactory + */ + public static KeyStrategyFactory createCustomKeyStrategyFactory() { + return () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("property", (oldValue, newValue) -> + ((String) newValue).toUpperCase() + ); + return keyStrategyMap; + }; + } + + /** + * 基本节点示例 + */ + public static void basicNodeExample() throws GraphStateException { + KeyStrategyFactory keyStrategyFactory = createKeyStrategyFactory(); + + var myNode = node_async(state -> { + System.out.println("In myNode: "); + String input = (String) state.value("input").orElse(""); + return Map.of("results", "Hello " + input); + }); + + var myOtherNode = node_async(state -> Map.of()); + + StateGraph builder = new StateGraph(keyStrategyFactory) + .addNode("myNode", myNode) + .addNode("myOtherNode", myOtherNode) + .addEdge(START, "myNode") + .addEdge("myNode", "myOtherNode") + .addEdge("myOtherNode", END); + + CompiledGraph graph = builder.compile(); + System.out.println("Graph compiled successfully"); + } + + /** + * 使用 RemoveByHash 删除消息示例 + */ + public static void removeMessagesExample() throws GraphStateException { + KeyStrategyFactory keyStrategyFactory = createKeyStrategyFactory(); + + var workflow = new StateGraph(keyStrategyFactory) + .addNode("agent_1", node_async(state -> + Map.of("messages", "message1"))) + .addNode("agent_2", node_async(state -> + Map.of("messages", "message2.1"))) + .addNode("agent_3", node_async(state -> + Map.of("messages", RemoveByHash.of("message2.1")))) + .addEdge(START, "agent_1") + .addEdge("agent_1", "agent_2") + .addEdge("agent_2", "agent_3") + .addEdge("agent_3", END); + + CompiledGraph graph = workflow.compile(); + System.out.println("Remove messages graph compiled successfully"); + } + + /** + * 条件边示例 + */ + public static void conditionalEdgesExample() throws GraphStateException { + KeyStrategyFactory keyStrategyFactory = createKeyStrategyFactory(); + + var workflow = new StateGraph(keyStrategyFactory) + .addNode("nodeA", node_async(state -> Map.of("data", "A"))) + .addNode("nodeB", node_async(state -> Map.of("data", "B"))) + .addNode("nodeC", node_async(state -> Map.of("data", "C"))) + .addEdge(START, "nodeA") + .addConditionalEdges("nodeA", edge_async(state -> "nodeB"), + Map.of("nodeB", "nodeB", "nodeC", "nodeC")) + .addEdge("nodeB", END) + .addEdge("nodeC", END); + + CompiledGraph graph = workflow.compile(); + System.out.println("Conditional edges graph compiled successfully"); + } + + public static void main(String[] args) { + System.out.println("=== 核心库概念示例 ===\n"); + + try { + // 示例 1: 基本节点示例 + System.out.println("示例 1: 基本节点示例"); + basicNodeExample(); + System.out.println(); + + // 示例 2: 使用 RemoveByHash 删除消息示例 + System.out.println("示例 2: 使用 RemoveByHash 删除消息示例"); + removeMessagesExample(); + System.out.println(); + + // 示例 3: 条件边示例 + System.out.println("示例 3: 条件边示例"); + conditionalEdgesExample(); + System.out.println(); + + System.out.println("所有示例执行完成"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/core/MemoryExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/core/MemoryExample.java new file mode 100644 index 00000000..8bbfef71 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/core/MemoryExample.java @@ -0,0 +1,400 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.core; + +import com.alibaba.cloud.ai.graph.CompileConfig; +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; +import com.alibaba.cloud.ai.graph.store.Store; +import com.alibaba.cloud.ai.graph.store.StoreItem; +import com.alibaba.cloud.ai.graph.store.stores.MemoryStore; + +import org.springframework.ai.chat.client.ChatClient; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 内存管理示例 + * 演示短期和长期内存管理 + */ +public class MemoryExample { + + /** + * 示例 1: 添加短期内存 + */ + public static void addShortTermMemory(ChatClient.Builder chatClientBuilder) throws GraphStateException { + // 创建内存检查点器 + MemorySaver checkpointer = new MemorySaver(); + + SaverConfig saverConfig = SaverConfig.builder() + .register(checkpointer) + .build(); + + // 定义状态策略 + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("messages", new AppendStrategy()); + return keyStrategyMap; + }; + + // 创建聊天节点 + var chatNode = node_async(state -> { + List> messages = + (List>) state.value("messages").orElse(List.of()); + + // 使用 ChatClient 调用 AI 模型 + ChatClient chatClient = chatClientBuilder.build(); + String response = chatClient.prompt() + .user(messages.get(messages.size() - 1).get("content")) + .call() + .content(); + + return Map.of("messages", List.of( + Map.of("role", "assistant", "content", response) + )); + }); + + // 构建图 + StateGraph stateGraph = new StateGraph(keyStrategyFactory) + .addNode("chat", chatNode) + .addEdge(START, "chat") + .addEdge("chat", END); + + // 编译图 + CompiledGraph graph = stateGraph.compile( + CompileConfig.builder() + .saverConfig(saverConfig) + .build() + ); + + // 第一轮对话 + RunnableConfig config = RunnableConfig.builder() + .threadId("conversation-1") + .build(); + + graph.invoke(Map.of("messages", List.of( + Map.of("role", "user", "content", "你好!我是 Bob") + )), config); + + // 第二轮对话(使用相同的 threadId) + graph.invoke(Map.of("messages", List.of( + Map.of("role", "user", "content", "我的名字是什么?") + )), config); + // AI 将能够记住之前的对话,回答 "Bob" + System.out.println("Short-term memory example executed"); + } + + /** + * 示例 2: 使用 Store 实现长期内存 + */ + public static void longTermMemoryWithDatabase() throws GraphStateException { + // 在节点中使用 Store 存储用户信息 + var userProfileNode = com.alibaba.cloud.ai.graph.action.AsyncNodeActionWithConfig.node_async((state, config) -> { + String userId = (String) state.value("userId").orElse(""); + + if (userId.isEmpty()) { + return Map.of("userProfile", Map.of("name", "Unknown", "preferences", "default")); + } + + // 从 Store 获取用户配置 + Store store = config.store(); + if (store != null) { + Optional itemOpt = store.getItem(List.of("user_profiles"), userId); + if (itemOpt.isPresent()) { + Map userProfile = itemOpt.get().getValue(); + return Map.of("userProfile", userProfile); + } + } + + // 如果未找到,返回默认值 + Map userProfile = Map.of("name", "User", "preferences", "default"); + return Map.of("userProfile", userProfile); + }); + + // 创建图 + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("userId", new ReplaceStrategy()); + keyStrategyMap.put("userProfile", new ReplaceStrategy()); + return keyStrategyMap; + }; + + StateGraph stateGraph = new StateGraph(keyStrategyFactory) + .addNode("load_profile", userProfileNode) + .addEdge(START, "load_profile") + .addEdge("load_profile", END); + + CompiledGraph graph = stateGraph.compile(CompileConfig.builder().build()); + + // 创建长期记忆存储并预填充数据 + MemoryStore memoryStore = new MemoryStore(); + Map profileData = new HashMap<>(); + profileData.put("name", "张三"); + profileData.put("preferences", "喜欢编程"); + StoreItem profileItem = StoreItem.of(List.of("user_profiles"), "user_001", profileData); + memoryStore.putItem(profileItem); + + // 运行图 + RunnableConfig config = RunnableConfig.builder() + .threadId("profile_thread") + .store(memoryStore) + .build(); + + Optional stateOptiona = graph.invoke(Map.of("userId", "user_001"), config); + Map result = stateOptiona.get().data(); + System.out.println("加载的用户配置: " + result.get("userProfile")); + + System.out.println("Long-term memory with Store example executed"); + } + + /** + * 示例 3: 使用 Store 缓存实现长期内存 + */ + public static void longTermMemoryWithRedis() throws GraphStateException { + var cacheNode = com.alibaba.cloud.ai.graph.action.AsyncNodeActionWithConfig.node_async((state, config) -> { + String key = (String) state.value("cacheKey").orElse(""); + + if (key.isEmpty()) { + return Map.of("result", "no_key"); + } + + // 从 Store 获取缓存数据 + Store store = config.store(); + if (store != null) { + Optional itemOpt = store.getItem(List.of("cache"), key); + if (itemOpt.isPresent()) { + // 缓存命中 + Map cachedData = itemOpt.get().getValue(); + return Map.of("result", cachedData.get("value")); + } + } + + // 缓存未命中,执行计算或查询 + Object computedData = performExpensiveOperation(key); + + // 存储到 Store + if (store != null) { + Map cacheValue = new HashMap<>(); + cacheValue.put("value", computedData); + StoreItem cacheItem = StoreItem.of(List.of("cache"), key, cacheValue); + store.putItem(cacheItem); + } + + return Map.of("result", computedData); + }); + + // 创建图 + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("cacheKey", new ReplaceStrategy()); + keyStrategyMap.put("result", new ReplaceStrategy()); + return keyStrategyMap; + }; + + StateGraph stateGraph = new StateGraph(keyStrategyFactory) + .addNode("cache", cacheNode) + .addEdge(START, "cache") + .addEdge("cache", END); + + CompiledGraph graph = stateGraph.compile(CompileConfig.builder().build()); + + // 创建长期记忆存储 + MemoryStore memoryStore = new MemoryStore(); + + // 第一次调用(缓存未命中) + RunnableConfig config = RunnableConfig.builder() + .threadId("cache_thread") + .store(memoryStore) + .build(); + + Optional stateOptional = graph.invoke(Map.of("cacheKey", "expensive_key"), config); + Map result1 = stateOptional.get().data(); + System.out.println("第一次调用结果: " + result1.get("result")); + + // 第二次调用(缓存命中) + Optional stateOptiona = graph.invoke(Map.of("cacheKey", "expensive_key"), config); + Map result2 = stateOptional.get().data(); + System.out.println("第二次调用结果(从缓存): " + result2.get("result")); + + System.out.println("Long-term memory with Store cache example executed"); + } + + // 模拟耗时操作 + private static Object performExpensiveOperation(String key) { + // 模拟耗时计算 + return "computed_result_for_" + key; + } + + /** + * 示例 4: 结合短期和长期内存 + */ + public static void combinedMemoryExample(ChatClient.Builder chatClientBuilder) throws GraphStateException { + // 定义状态 + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("userId", new ReplaceStrategy()); + keyStrategyMap.put("messages", new AppendStrategy()); + keyStrategyMap.put("userPreferences", new ReplaceStrategy()); + return keyStrategyMap; + }; + + // 加载用户偏好(长期内存) + var loadUserPreferences = com.alibaba.cloud.ai.graph.action.AsyncNodeActionWithConfig.node_async((state, config) -> { + String userId = (String) state.value("userId").orElse(""); + + if (userId.isEmpty()) { + return Map.of("userPreferences", Map.of("theme", "default", "language", "zh")); + } + + // 从 Store 加载用户偏好 + Store store = config.store(); + if (store != null) { + Optional itemOpt = store.getItem(List.of("user_preferences"), userId); + if (itemOpt.isPresent()) { + Map preferences = itemOpt.get().getValue(); + return Map.of("userPreferences", preferences); + } + } + + // 如果未找到,返回默认偏好 + Map preferences = Map.of("theme", "dark", "language", "zh"); + return Map.of("userPreferences", preferences); + }); + + // 聊天节点(使用短期和长期内存) + var chatNode = node_async(state -> { + List> messages = + (List>) state.value("messages").orElse(List.of()); + Map preferences = + (Map) state.value("userPreferences").orElse(Map.of()); + + // 构建包含用户偏好的提示 + String userPrompt = messages.get(messages.size() - 1).get("content"); + String enhancedPrompt = "用户偏好: " + preferences + "\n用户问题: " + userPrompt; + + // 调用 AI + ChatClient chatClient = chatClientBuilder.build(); + String response = chatClient.prompt() + .user(enhancedPrompt) + .call() + .content(); + + return Map.of("messages", List.of( + Map.of("role", "assistant", "content", response) + )); + }); + + // 构建图 + StateGraph stateGraph = new StateGraph(keyStrategyFactory) + .addNode("load_preferences", loadUserPreferences) + .addNode("chat", chatNode) + .addEdge(START, "load_preferences") + .addEdge("load_preferences", "chat") + .addEdge("chat", END); + + // 配置检查点(短期内存) + SaverConfig saverConfig = SaverConfig.builder() + .register(new MemorySaver()) + .build(); + + // 编译图 + CompiledGraph graph = stateGraph.compile( + CompileConfig.builder() + .saverConfig(saverConfig) + .build() + ); + + // 创建长期记忆存储并预填充用户偏好 + MemoryStore memoryStore = new MemoryStore(); + Map preferencesData = new HashMap<>(); + preferencesData.put("theme", "dark"); + preferencesData.put("language", "zh"); + preferencesData.put("timezone", "Asia/Shanghai"); + StoreItem preferencesItem = StoreItem.of(List.of("user_preferences"), "user_002", preferencesData); + memoryStore.putItem(preferencesItem); + + // 运行图 + RunnableConfig config = RunnableConfig.builder() + .threadId("combined_thread") + .store(memoryStore) + .build(); + + // 第一轮对话(加载偏好并开始对话) + graph.invoke(Map.of( + "userId", "user_002", + "messages", List.of(Map.of("role", "user", "content", "你好")) + ), config); + + // 第二轮对话(使用短期和长期记忆) + graph.invoke(Map.of( + "userId", "user_002", + "messages", List.of(Map.of("role", "user", "content", "根据我的偏好给我一些建议")) + ), config); + + System.out.println("Combined memory example created"); + } + + public static void main(String[] args) { + System.out.println("=== 内存管理示例 ===\n"); + + try { + // 示例 1: 添加短期内存(需要 ChatClient) + System.out.println("示例 1: 添加短期内存"); + System.out.println("注意: 此示例需要 ChatClient,跳过执行"); + // addShortTermMemory(ChatClient.builder(...)); + System.out.println(); + + // 示例 2: 使用 Store 实现长期内存 + System.out.println("示例 2: 使用 Store 实现长期内存"); + longTermMemoryWithDatabase(); + System.out.println(); + + // 示例 3: 使用 Store 缓存实现长期内存 + System.out.println("示例 3: 使用 Store 缓存实现长期内存"); + longTermMemoryWithRedis(); + System.out.println(); + + // 示例 4: 结合短期和长期内存(需要 ChatClient) + System.out.println("示例 4: 结合短期和长期内存"); + System.out.println("注意: 此示例需要 ChatClient,跳过执行"); + // combinedMemoryExample(ChatClient.builder(...)); + System.out.println(); + + System.out.println("所有示例执行完成"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/core/PersistenceExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/core/PersistenceExample.java new file mode 100644 index 00000000..300af15f --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/core/PersistenceExample.java @@ -0,0 +1,245 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.core; + +import com.alibaba.cloud.ai.graph.CompileConfig; +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.StateSnapshot; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 持久化示例 + * 演示如何使用 Checkpointer 实现工作流状态持久化 + */ +public class PersistenceExample { + + /** + * 示例 1: 基本持久化配置 + */ + public static void basicPersistenceExample() throws GraphStateException { + // 定义状态策略 + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("foo", new ReplaceStrategy()); + keyStrategyMap.put("bar", new AppendStrategy()); + return keyStrategyMap; + }; + + // 定义节点操作 + var nodeA = node_async(state -> { + return Map.of("foo", "a", "bar", List.of("a")); + }); + + var nodeB = node_async(state -> { + return Map.of("foo", "b", "bar", List.of("b")); + }); + + // 创建图 + StateGraph stateGraph = new StateGraph(keyStrategyFactory) + .addNode("node_a", nodeA) + .addNode("node_b", nodeB) + .addEdge(START, "node_a") + .addEdge("node_a", "node_b") + .addEdge("node_b", END); + + // 配置检查点 + SaverConfig saverConfig = SaverConfig.builder() + .register(new MemorySaver()) + .build(); + + // 编译图 + CompiledGraph graph = stateGraph.compile( + CompileConfig.builder() + .saverConfig(saverConfig) + .build() + ); + + // 运行图 + RunnableConfig config = RunnableConfig.builder() + .threadId("1") + .build(); + + Map input = new HashMap<>(); + input.put("foo", ""); + + graph.invoke(input, config); + System.out.println("Basic persistence example executed"); + } + + /** + * 示例 2: 获取状态 + */ + public static void getStateExample(CompiledGraph graph) { + RunnableConfig config = RunnableConfig.builder() + .threadId("1") + .build(); + + // 获取最新的状态快照 + StateSnapshot stateSnapshot = graph.getState(config); + System.out.println("Current state: " + stateSnapshot.state()); + System.out.println("Current node: " + stateSnapshot.node()); + + // 获取特定 checkpoint_id 的状态快照 + RunnableConfig configWithCheckpoint = RunnableConfig.builder() + .threadId("1") + .checkPointId("1ef663ba-28fe-6528-8002-5a559208592c") + .build(); + StateSnapshot specificSnapshot = graph.getState(configWithCheckpoint); + System.out.println("Specific checkpoint state: " + specificSnapshot.state()); + } + + /** + * 示例 3: 获取状态历史 + */ + public static void getStateHistoryExample(CompiledGraph graph) { + RunnableConfig config = RunnableConfig.builder() + .threadId("1") + .build(); + + List history = (List) graph.getStateHistory(config); + System.out.println("State history:"); + for (int i = 0; i < history.size(); i++) { + StateSnapshot snapshot = history.get(i); + System.out.printf("Step %d: %s\n", i, snapshot.state()); + System.out.printf(" Checkpoint ID: %s\n", snapshot.config().checkPointId()); + System.out.printf(" Node: %s\n", snapshot.node()); + } + } + + /** + * 示例 4: 更新状态 + */ + public static void updateStateExample(CompiledGraph graph) throws Exception { + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("foo", new ReplaceStrategy()); // 替换策略 + keyStrategyMap.put("bar", new AppendStrategy()); // 追加策略 + return keyStrategyMap; + }; + + RunnableConfig config = RunnableConfig.builder() + .threadId("1") + .build(); + + Map updates = new HashMap<>(); + updates.put("foo", 2); + updates.put("bar", List.of("b")); + + graph.updateState(config, updates, null); + System.out.println("State updated successfully"); + } + + /** + * 示例 5: 重放(Replay) + */ + public static void replayExample(CompiledGraph graph) { + RunnableConfig config = RunnableConfig.builder() + .threadId("1") + .checkPointId("0c62ca34-ac19-445d-bbb0-5b4984975b2a") + .build(); + + graph.invoke(Map.of(), config); + System.out.println("Replay executed"); + } + + public static void main(String[] args) { + System.out.println("=== 持久化示例 ===\n"); + + try { + // 示例 1: 基本持久化配置 + System.out.println("示例 1: 基本持久化配置"); + basicPersistenceExample(); + System.out.println(); + + // 创建图用于后续示例 + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("foo", new ReplaceStrategy()); + keyStrategyMap.put("bar", new AppendStrategy()); + return keyStrategyMap; + }; + + StateGraph stateGraph = new StateGraph(keyStrategyFactory) + .addNode("node_a", node_async(state -> Map.of("foo", "a", "bar", List.of("a")))) + .addNode("node_b", node_async(state -> Map.of("foo", "b", "bar", List.of("b")))) + .addEdge(START, "node_a") + .addEdge("node_a", "node_b") + .addEdge("node_b", END); + + SaverConfig saverConfig = SaverConfig.builder() + .register(new MemorySaver()) + .build(); + + CompiledGraph graph = stateGraph.compile( + CompileConfig.builder() + .saverConfig(saverConfig) + .build() + ); + + RunnableConfig config = RunnableConfig.builder() + .threadId("1") + .build(); + + Map input = new HashMap<>(); + input.put("foo", ""); + graph.invoke(input, config); + + // 示例 2: 获取状态 + System.out.println("示例 2: 获取状态"); + getStateExample(graph); + System.out.println(); + + // 示例 3: 获取状态历史 + System.out.println("示例 3: 获取状态历史"); + getStateHistoryExample(graph); + System.out.println(); + + // 示例 4: 更新状态 + System.out.println("示例 4: 更新状态"); + updateStateExample(graph); + System.out.println(); + + // 示例 5: 重放(需要有效的 checkpointId) + System.out.println("示例 5: 重放(需要有效的 checkpointId)"); + System.out.println("注意: 此示例需要有效的 checkpointId,跳过执行"); + // replayExample(graph); + System.out.println(); + + System.out.println("所有示例执行完成"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/core/StreamingExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/core/StreamingExample.java new file mode 100644 index 00000000..a729f229 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/core/StreamingExample.java @@ -0,0 +1,179 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.core; + +import com.alibaba.cloud.ai.graph.CompileConfig; +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.action.AsyncNodeAction; +import com.alibaba.cloud.ai.graph.action.NodeAction; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.streaming.StreamingOutput; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatResponse; + +import java.util.HashMap; +import java.util.Map; + +import reactor.core.publisher.Flux; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; + +/** + * 流式输出示例 + * 演示如何在 Spring AI Alibaba Graph 中实现流式输出 + */ +public class StreamingExample { + + /** + * 使用 StateGraph 实现流式输出的完整示例 + * + * @param chatClientBuilder ChatClient 构建器 + * @throws GraphStateException 图执行异常 + */ + public static void streamLLMTokens(ChatClient.Builder chatClientBuilder) throws GraphStateException { + // 定义状态策略 + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("query", new AppendStrategy()); + keyStrategyMap.put("messages", new AppendStrategy()); + keyStrategyMap.put("result", new AppendStrategy()); + return keyStrategyMap; + }; + + // 创建流式节点 + StreamingNode streamingNode = new StreamingNode(chatClientBuilder, "streaming_node"); + + // 创建处理节点 + ProcessStreamingNode processNode = new ProcessStreamingNode(); + + // 构建图 + StateGraph stateGraph = new StateGraph(keyStrategyFactory) + .addNode("streaming_node", AsyncNodeAction.node_async(streamingNode)) + .addNode("process_node", AsyncNodeAction.node_async(processNode)) + .addEdge(START, "streaming_node") + .addEdge("streaming_node", "process_node") + .addEdge("process_node", END); + + // 编译图 + CompiledGraph graph = stateGraph.compile( + CompileConfig.builder() + .build() + ); + + // 创建配置 + RunnableConfig config = RunnableConfig.builder() + .threadId("streaming_thread") + .build(); + + // 使用流式方式执行图 + System.out.println("开始流式输出...\n"); + + graph.stream(Map.of("query", "请用一句话介绍 Spring AI"), config) + .doOnNext(output -> { + // 处理流式输出 + if (output instanceof StreamingOutput streamingOutput) { + // 流式输出块 + String chunk = streamingOutput.chunk(); + if (chunk != null && !chunk.isEmpty()) { + System.out.print(chunk); // 实时打印流式内容 + } + } + else { + // 普通节点输出 + String nodeId = output.node(); + Map state = output.state().data(); + System.out.println("\n节点 '" + nodeId + "' 执行完成"); + if (state.containsKey("result")) { + System.out.println("最终结果: " + state.get("result")); + } + } + }) + .doOnComplete(() -> { + System.out.println("\n\n流式输出完成"); + }) + .doOnError(error -> { + System.err.println("流式输出错误: " + error.getMessage()); + }) + .blockLast(); // 阻塞等待流完成 + } + + public static void main(String[] args) { + System.out.println("=== 流式输出示例 ===\n"); + + try { + // 示例 1: 使用 Spring AI 的流式 LLM tokens(需要 ChatClient) + System.out.println("示例 1: 使用 Spring AI 的流式 LLM tokens"); + System.out.println("注意: 此示例需要 ChatClient,跳过执行"); + System.out.println("使用方法: streamLLMTokens(ChatClient.builder()...)"); + // streamLLMTokens(ChatClient.builder()...); + System.out.println(); + + System.out.println("所有示例执行完成"); + System.out.println("提示: 请配置 ChatClient 后运行完整示例"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } + + public static class StreamingNode implements NodeAction { + + private final ChatClient chatClient; + private final String nodeId; + + public StreamingNode(ChatClient.Builder chatClientBuilder, String nodeId) { + this.chatClient = chatClientBuilder.build(); + this.nodeId = nodeId; + } + + @Override + public Map apply(OverAllState state) { + String query = (String) state.value("query").orElse(""); + + // 获取流式响应 + Flux chatResponseFlux = chatClient.prompt() + .user(query) + .stream() + .chatResponse(); + + return Map.of("messages", chatResponseFlux); + } + } + + /** + * 处理流式输出的节点 - 接收并处理流式响应 + */ + public static class ProcessStreamingNode implements NodeAction { + + @Override + public Map apply(OverAllState state) { + // 从状态中获取流式响应结果 + Object messages = state.value("messages").orElse(""); + String result = "流式响应已处理完成: " + messages; + return Map.of("result", result); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/CancellationExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/CancellationExample.java new file mode 100644 index 00000000..0ec5376f --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/CancellationExample.java @@ -0,0 +1,171 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.NodeOutput; +import com.alibaba.cloud.ai.graph.RunnableConfig; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import reactor.core.Disposable; +import reactor.core.publisher.Flux; + +/** + * Graph 执行取消示例 + * 演示如何取消图的执行 + */ +public class CancellationExample { + + /** + * 示例 1: 使用 forEachAsync 消费流时取消 + */ + public static void cancelWithForEachAsync(CompiledGraph compiledGraph, boolean mayInterruptIfRunning) { + // 创建运行配置 + RunnableConfig runnableConfig = RunnableConfig.builder() + .threadId("test-thread") + .build(); + + // 准备输入数据 + Map inputData = new HashMap<>(); + // ... 添加输入数据 + + // 执行图并获取流 + Flux stream = compiledGraph.stream(inputData, runnableConfig); + + // 从新线程在 500 毫秒后请求取消 + CompletableFuture.runAsync(() -> { + try { + Thread.sleep(500); + // Flux 使用 dispose() 来取消 + System.out.println("请求取消执行"); + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + + // 异步处理每个输出 + var disposable = stream.subscribe( + output -> System.out.println("当前迭代节点: " + output), + error -> System.out.println("流错误: " + error.getMessage()), + () -> System.out.println("流完成") + ); + + // 等待流完成或取消 + try { + stream.blockLast(); + } + catch (Exception e) { + System.err.println("执行异常: " + e.getMessage()); + } + + // 验证是否已取消(Flux 使用 isDisposed 检查) + System.out.println("是否已取消: " + disposable.isDisposed()); + } + + /** + * 示例 2: 使用迭代器消费流时取消 + */ + public static void cancelWithIterator(CompiledGraph compiledGraph, boolean mayInterruptIfRunning) { + // 创建运行配置 + RunnableConfig runnableConfig = RunnableConfig.builder() + .threadId("test-thread") + .build(); + + // 准备输入数据 + Map inputData = new HashMap<>(); + // ... 添加输入数据 + + // 执行图并获取流 + Flux stream = compiledGraph.stream(inputData, runnableConfig); + + // 从新线程在 500 毫秒后请求取消 + var disposable = stream.subscribe( + output -> { + System.out.println("当前迭代节点: " + output); + }, + error -> { + System.out.println("流错误: " + error.getMessage()); + }, + () -> { + System.out.println("流完成"); + } + ); + + CompletableFuture.runAsync(() -> { + try { + Thread.sleep(500); + disposable.dispose(); // 取消流 + System.out.println("已请求取消执行"); + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + + // 等待流完成或取消 + try { + stream.blockLast(); + } + catch (Exception e) { + System.out.println("流被中断: " + e.getMessage()); + } + + // 验证取消状态 + System.out.println("是否已取消: " + disposable.isDisposed()); + } + + /** + * 检查取消状态 + */ + public static void checkCancellationStatus(Disposable disposable) { + if (disposable.isDisposed()) { + System.out.println("流已被取消"); + } + else { + System.out.println("流仍在运行"); + } + } + + public static void main(String[] args) { + System.out.println("=== Graph 执行取消示例 ===\n"); + + try { + // 示例 1: 使用 forEachAsync 消费流时取消(需要 CompiledGraph) + System.out.println("示例 1: 使用 forEachAsync 消费流时取消"); + System.out.println("注意: 此示例需要 CompiledGraph,跳过执行"); + // cancelWithForEachAsync(compiledGraph, true); + System.out.println(); + + // 示例 2: 使用迭代器消费流时取消(需要 CompiledGraph) + System.out.println("示例 2: 使用迭代器消费流时取消"); + System.out.println("注意: 此示例需要 CompiledGraph,跳过执行"); + // cancelWithIterator(compiledGraph, true); + System.out.println(); + + System.out.println("所有示例执行完成"); + System.out.println("提示: 请配置 CompiledGraph 后运行完整示例"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/CheckpointRedisExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/CheckpointRedisExample.java new file mode 100644 index 00000000..514564eb --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/CheckpointRedisExample.java @@ -0,0 +1,255 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompileConfig; +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; +import com.alibaba.cloud.ai.graph.checkpoint.savers.redis.RedisSaver; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.StateSnapshot; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * Redis 检查点持久化示例 + * 演示如何使用 Redis 数据库持久化工作流状态 + */ +public class CheckpointRedisExample { + + /** + * 初始化 RedisSaver + */ + public static RedisSaver createRedisSaver() { + // 配置 Redisson 客户端 + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://localhost:6379"); // Redis 地址 + + RedissonClient redisson = Redisson.create(config); + return RedisSaver.builder().redisson(redisson).build(); + } + + /** + * 使用自定义 Redis 地址创建 RedisSaver + */ + public static RedisSaver createRedisSaver(String host, int port) { + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://" + host + ":" + port); + + RedissonClient redisson = Redisson.create(config); + return RedisSaver.builder().redisson(redisson).build(); + } + + /** + * 完整示例: 使用 Redis 检查点持久化 + * + * @return + */ + public static void testCheckpointWithRedis(StateGraph stateGraph) throws Exception { + // 初始化 Redis Saver + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://localhost:6379"); + + RedissonClient redisson = Redisson.create(config); + try { + RedisSaver saver = RedisSaver.builder().redisson(redisson).build(); + + SaverConfig saverConfig = SaverConfig.builder() + .register(saver) + .build(); + + // 使用检查点编译图 + CompiledGraph workflow = stateGraph.compile( + CompileConfig.builder() + .saverConfig(saverConfig) + .build() + ); + + // 执行工作流 + RunnableConfig runnableConfig = RunnableConfig.builder() + .threadId("test-thread-1") + .build(); + + Map inputs = Map.of("input", "test1"); + OverAllState result = workflow.invoke(inputs, runnableConfig).orElseThrow(); + + // 获取检查点历史 + List history = (List) workflow.getStateHistory(runnableConfig); + + System.out.println("检查点历史数量: " + history.size()); + + // 获取最后保存的检查点 + StateSnapshot lastSnapshot = workflow.getState(runnableConfig); + + System.out.println("最后检查点节点: " + lastSnapshot.node()); + + } finally { + redisson.shutdown(); + } + } + + /** + * 从 Redis 重新加载检查点 + * + * @return + */ + public static void reloadCheckpointFromRedis(StateGraph stateGraph) throws GraphStateException { + // 创建新的 saver(重置缓存) + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://localhost:6379"); + + RedissonClient redisson = Redisson.create(config); + try { + RedisSaver newSaver = RedisSaver.builder().redisson(redisson).build(); + + SaverConfig newSaverConfig = SaverConfig.builder() + .register(newSaver) + .build(); + + // 重新编译图 + CompiledGraph reloadedWorkflow = stateGraph.compile( + CompileConfig.builder() + .saverConfig(newSaverConfig) + .build() + ); + + // 使用相同的 threadId 获取历史 + RunnableConfig reloadConfig = RunnableConfig.builder() + .threadId("test-thread-1") + .build(); + + Collection reloadedHistory = reloadedWorkflow.getStateHistory(reloadConfig); + + System.out.println("重新加载的检查点历史数量: " + reloadedHistory.size()); + } finally { + redisson.shutdown(); + } + + } + + /** + * 从特定检查点恢复 + */ + public static void restoreFromCheckpoint(StateGraph stateGraph) throws GraphStateException{ + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://localhost:6379"); + + RedissonClient redisson = Redisson.create(config); + try { + RedisSaver newSaver = RedisSaver.builder().redisson(redisson).build(); + + SaverConfig newSaverConfig = SaverConfig.builder() + .register(newSaver) + .build(); + + // 重新编译图 + CompiledGraph reloadedWorkflow = stateGraph.compile( + CompileConfig.builder() + .saverConfig(newSaverConfig) + .build() + ); + // 获取特定检查点 + RunnableConfig checkpointConfig = RunnableConfig.builder() + .threadId("thread-id") + .checkPointId("specific-checkpoint-id") + .build(); + + // 从该检查点继续 + reloadedWorkflow.invoke(Map.of(), checkpointConfig); + System.out.println("从检查点恢复执行完成"); + } + finally { + redisson.shutdown(); + } + + } + + public static void main(String[] args) { + System.out.println("=== Redis 检查点持久化示例 ===\n"); + + try { + + // 定义状态策略 + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("input", new ReplaceStrategy()); + keyStrategyMap.put("agent_1:prop1", new ReplaceStrategy()); + return keyStrategyMap; + }; + + // 定义节点 + var agent1 = node_async(state -> { + System.out.println("agent_1 执行中"); + return Map.of("agent_1:prop1", "agent_1:test"); + }); + + // 构建图 + StateGraph stateGraph = new StateGraph(keyStrategyFactory) + .addNode("agent_1", agent1) + .addEdge(START, "agent_1") + .addEdge("agent_1", END); + + // 示例 1: 完整示例 - 使用 Redis 检查点持久化 + System.out.println("示例 1: 使用 Redis 检查点持久化"); + System.out.println("注意: 此示例需要 Redis 连接"); + testCheckpointWithRedis(stateGraph); + System.out.println(); + + // 示例 2: 从 Redis 重新加载检查点 + System.out.println("示例 2: 从 Redis 重新加载检查点"); + System.out.println("注意: 此示例需要 Redis 连接"); + reloadCheckpointFromRedis(stateGraph); + System.out.println(); + + // 示例 3: 从特定检查点恢复 + System.out.println("示例 3: 从特定检查点恢复"); + System.out.println("注意: 此示例需要有效的 CompiledGraph 和 checkpointId"); + restoreFromCheckpoint(stateGraph); + System.out.println(); + + System.out.println("所有示例执行完成"); + System.out.println("提示: 请配置 Redis 连接后运行完整示例"); + System.out.println("提示: 需要添加 Redisson 依赖: org.redisson:redisson"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/HumanInTheLoopExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/HumanInTheLoopExample.java new file mode 100644 index 00000000..f86fe815 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/HumanInTheLoopExample.java @@ -0,0 +1,448 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompileConfig; +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.NodeOutput; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.action.AsyncNodeActionWithConfig; +import com.alibaba.cloud.ai.graph.action.InterruptableAction; +import com.alibaba.cloud.ai.graph.action.InterruptionMetadata; +import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 人类反馈(Human-in-the-Loop)示例 + * + * 在实际业务场景中,经常会遇到人类介入的场景,人类的不同操作将影响工作流不同的走向。 + * Spring AI Alibaba Graph 提供了两种方式来实现人类反馈: + * + * 1. InterruptionMetadata 模式:可以在任意节点随时中断,通过实现 InterruptableAction 接口来控制中断时机 + * 2. interruptBefore 模式:需要提前在编译配置中定义中断点,在指定节点执行前中断 + */ +public class HumanInTheLoopExample { + + // ==================== 模式一:InterruptionMetadata 模式 ==================== + + /** + * 定义带中断的 Graph(InterruptionMetadata 模式) + * 使用 InterruptableAction 实现中断,不需要 interruptBefore 配置 + */ + public static CompiledGraph createGraphWithInterruptableAction() throws GraphStateException { + // 定义普通节点 + var step1 = node_async(state -> { + return Map.of("messages", "Step 1"); + }); + + // 定义可中断节点(实现 InterruptableAction) + var humanFeedback = new InterruptableNodeAction("human_feedback", "等待用户输入"); + + var step3 = node_async(state -> { + return Map.of("messages", "Step 3"); + }); + + // 定义条件边:根据 human_feedback 的值决定路由 + var evalHumanFeedback = edge_async(state -> { + var feedback = (String) state.value("human_feedback").orElse("unknown"); + return (feedback.equals("next") || feedback.equals("back")) ? feedback : "unknown"; + }); + + // 配置 KeyStrategyFactory + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap keyStrategyHashMap = new HashMap<>(); + keyStrategyHashMap.put("messages", new AppendStrategy()); + keyStrategyHashMap.put("human_feedback", new ReplaceStrategy()); + return keyStrategyHashMap; + }; + + // 构建 Graph + StateGraph builder = new StateGraph(keyStrategyFactory) + .addNode("step_1", step1) + .addNode("human_feedback", humanFeedback) // 使用可中断节点 + .addNode("step_3", step3) + .addEdge(START, "step_1") + .addEdge("step_1", "human_feedback") + .addConditionalEdges("human_feedback", evalHumanFeedback, + Map.of("back", "step_1", "next", "step_3", "unknown", "human_feedback")) + .addEdge("step_3", END); + + // 配置内存保存器(用于状态持久化) + var saver = new MemorySaver(); + + var compileConfig = CompileConfig.builder() + .saverConfig(SaverConfig.builder() + .register(saver) + .build()) + // 不再需要 interruptBefore 配置,中断由 InterruptableAction 控制 + .build(); + + return builder.compile(compileConfig); + } + + /** + * 执行 Graph 直到中断(InterruptionMetadata 模式) + * 检查流式输出中的 InterruptionMetadata + */ + public static InterruptionMetadata executeUntilInterruptWithMetadata(CompiledGraph graph) { + // 初始输入 + Map initialInput = Map.of("messages", "Step 0"); + + // 配置线程 ID + var invokeConfig = RunnableConfig.builder() + .threadId("Thread1") + .build(); + + // 用于保存最后一个输出 + AtomicReference lastOutputRef = new AtomicReference<>(); + + // 运行 Graph 直到第一个中断点 + graph.stream(initialInput, invokeConfig) + .doOnNext(event -> { + System.out.println("节点输出: " + event); + lastOutputRef.set(event); + }) + .doOnError(error -> System.err.println("流错误: " + error.getMessage())) + .doOnComplete(() -> System.out.println("流完成")) + .blockLast(); + + // 检查最后一个输出是否是 InterruptionMetadata + NodeOutput lastOutput = lastOutputRef.get(); + if (lastOutput instanceof InterruptionMetadata) { + System.out.println("\n检测到中断: " + lastOutput); + return (InterruptionMetadata) lastOutput; + } + + return null; + } + + /** + * 等待用户输入并更新状态(InterruptionMetadata 模式) + */ + public static RunnableConfig waitUserInputAndUpdateStateWithMetadata(CompiledGraph graph, InterruptionMetadata interruption) throws Exception { + var invokeConfig = RunnableConfig.builder() + .threadId("Thread1") + .build(); + + // 检查当前状态 + System.out.printf("\n--State before update--\n%s\n", graph.getState(invokeConfig)); + + // 模拟用户输入 + var userInput = "back"; // "back" 表示返回上一个节点 + System.out.printf("\n--User Input--\n用户选择: '%s'\n\n", userInput); + + // 更新状态:添加 human_feedback + // 使用 updateState 更新状态,传入中断时的节点 ID + var updatedConfig = graph.updateState(invokeConfig, Map.of("human_feedback", userInput), interruption.node()); + + // 检查更新后的状态 + System.out.printf("--State after update--\n%s\n", graph.getState(updatedConfig)); + + return updatedConfig; + } + + /** + * 继续执行 Graph(InterruptionMetadata 模式) + * 使用 HUMAN_FEEDBACK_METADATA_KEY 来恢复执行 + */ + public static void continueExecutionWithMetadata(CompiledGraph graph, RunnableConfig updatedConfig) { + // 创建恢复配置,添加 HUMAN_FEEDBACK_METADATA_KEY + RunnableConfig resumeConfig = RunnableConfig.builder(updatedConfig) + .addMetadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY, "placeholder") + .build(); + + System.out.println("\n--继续执行 Graph--"); + + // 继续执行 Graph(input 为 null,使用之前的状态) + graph.stream(null, resumeConfig) + .doOnNext(event -> System.out.println("节点输出: " + event)) + .doOnError(error -> System.err.println("流错误: " + error.getMessage())) + .doOnComplete(() -> System.out.println("流完成")) + .blockLast(); + } + + // ==================== 模式二:interruptBefore 模式 ==================== + + /** + * 定义带中断的 Graph(interruptBefore 模式) + * 使用 interruptBefore 配置在指定节点前中断 + */ + public static CompiledGraph createGraphWithInterruptBefore() throws GraphStateException { + // 定义节点 + var step1 = node_async(state -> { + return Map.of("messages", "Step 1"); + }); + + var humanFeedback = node_async(state -> { + return Map.of(); // 等待用户输入,不修改状态 + }); + + var step3 = node_async(state -> { + return Map.of("messages", "Step 3"); + }); + + // 定义条件边 + var evalHumanFeedback = edge_async(state -> { + var feedback = (String) state.value("human_feedback").orElse("unknown"); + return (feedback.equals("next") || feedback.equals("back")) ? feedback : "unknown"; + }); + + // 配置 KeyStrategyFactory + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap keyStrategyHashMap = new HashMap<>(); + keyStrategyHashMap.put("messages", new AppendStrategy()); + keyStrategyHashMap.put("human_feedback", new ReplaceStrategy()); + return keyStrategyHashMap; + }; + + // 构建 Graph + StateGraph builder = new StateGraph(keyStrategyFactory) + .addNode("step_1", step1) + .addNode("human_feedback", humanFeedback) + .addNode("step_3", step3) + .addEdge(START, "step_1") + .addEdge("step_1", "human_feedback") + .addConditionalEdges("human_feedback", evalHumanFeedback, + Map.of("back", "step_1", "next", "step_3", "unknown", "human_feedback")) + .addEdge("step_3", END); + + // 配置内存保存器和中断点 + var saver = new MemorySaver(); + + var compileConfig = CompileConfig.builder() + .saverConfig(SaverConfig.builder() + .register(saver) + .build()) + .interruptBefore("human_feedback") // 在 human_feedback 节点前中断 + .build(); + + return builder.compile(compileConfig); + } + + /** + * 执行 Graph 直到中断(interruptBefore 模式) + */ + public static void executeUntilInterruptWithInterruptBefore(CompiledGraph graph) { + // 初始输入 + Map initialInput = Map.of("messages", "Step 0"); + + // 配置线程 ID + var invokeConfig = RunnableConfig.builder() + .threadId("Thread1") + .build(); + + // 运行 Graph 直到第一个中断点 + graph.stream(initialInput, invokeConfig) + .doOnNext(event -> System.out.println(event)) + .doOnError(error -> System.err.println("流错误: " + error.getMessage())) + .doOnComplete(() -> System.out.println("流完成")) + .blockLast(); + } + + /** + * 等待用户输入并更新状态(interruptBefore 模式) + */ + public static RunnableConfig waitUserInputAndUpdateStateWithInterruptBefore(CompiledGraph graph) throws Exception { + var invokeConfig = RunnableConfig.builder() + .threadId("Thread1") + .build(); + + // 检查当前状态 + System.out.printf("--State before update--\n%s\n", graph.getState(invokeConfig)); + + // 模拟用户输入 + var userInput = "back"; // "back" 表示返回上一个节点 + System.out.printf("\n--User Input--\n用户选择: '%s'\n\n", userInput); + + // 更新状态(模拟 human_feedback 节点的输出) + // 注意:interruptBefore 模式下,传入 null 作为节点 ID + var updateConfig = graph.updateState(invokeConfig, Map.of("human_feedback", userInput), null); + + // 检查更新后的状态 + System.out.printf("--State after update--\n%s\n", graph.getState(updateConfig)); + + return updateConfig; + } + + /** + * 继续执行 Graph(interruptBefore 模式) + */ + public static void continueExecutionWithInterruptBefore(CompiledGraph graph, RunnableConfig updateConfig) { + // 添加恢复执行的元数据标记 + RunnableConfig resumeConfig = RunnableConfig.builder(updateConfig) + .addMetadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY, "placeholder") + .build(); + + // 继续执行 Graph(input 为 null,使用之前的状态) + graph.stream(null, resumeConfig) + .doOnNext(event -> System.out.println(event)) + .doOnError(error -> System.err.println("流错误: " + error.getMessage())) + .doOnComplete(() -> System.out.println("流完成")) + .blockLast(); + } + + /** + * 第二次等待用户输入(interruptBefore 模式) + */ + public static RunnableConfig waitUserInputSecondTime(CompiledGraph graph, RunnableConfig invokeConfig) throws Exception { + var userInput = "next"; // "next" 表示继续下一个节点 + System.out.printf("\n--User Input--\n用户选择: '%s'\n", userInput); + + // 更新状态 + var updateConfig = graph.updateState(invokeConfig, Map.of("human_feedback", userInput), null); + + System.out.printf("\ngetNext()\n\twith invokeConfig:[%s]\n\twith updateConfig:[%s]\n", + graph.getState(invokeConfig).next(), + graph.getState(updateConfig).next()); + + return updateConfig; + } + + /** + * 继续执行直到完成(interruptBefore 模式) + */ + public static void continueExecutionUntilComplete(CompiledGraph graph, RunnableConfig updateConfig) { + // 添加恢复执行的元数据标记 + RunnableConfig resumeConfig = RunnableConfig.builder(updateConfig) + .addMetadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY, "placeholder") + .build(); + + // 继续执行 Graph + graph.stream(null, resumeConfig) + .doOnNext(event -> System.out.println(event)) + .doOnError(error -> System.err.println("流错误: " + error.getMessage())) + .doOnComplete(() -> System.out.println("流完成")) + .blockLast(); + } + + // ==================== 可中断的节点动作类 ==================== + + /** + * 可中断的节点动作 + * 实现 InterruptableAction 接口,可以在任意节点中断执行 + */ + public static class InterruptableNodeAction implements AsyncNodeActionWithConfig, InterruptableAction { + private final String nodeId; + private final String message; + + public InterruptableNodeAction(String nodeId, String message) { + this.nodeId = nodeId; + this.message = message; + } + + @Override + public CompletableFuture> apply(OverAllState state, RunnableConfig config) { + // 正常节点逻辑:更新状态 + return CompletableFuture.completedFuture(Map.of("messages", message)); + } + + @Override + public Optional interrupt(String nodeId, OverAllState state, RunnableConfig config) { + // 检查是否需要中断 + // 如果状态中没有 human_feedback,则中断等待用户输入 + Optional humanFeedback = state.value("human_feedback"); + + if (humanFeedback.isEmpty()) { + // 返回 InterruptionMetadata 来中断执行 + InterruptionMetadata interruption = InterruptionMetadata.builder(nodeId, state) + .addMetadata("message", "等待用户输入...") + .addMetadata("node", nodeId) + // 如果要做工具确认的话,可以在这里添加 toolFeedbacks,具体可参考 HumanInTheLoopHook 实现 + //.toolFeedbacks(List.of(InterruptionMetadata.ToolFeedback.builder().description("").build())) + .build(); + + return Optional.of(interruption); + } + + // 如果已经有 human_feedback,继续执行 + return Optional.empty(); + } + } + + // ==================== 主方法 ==================== + + public static void main(String[] args) throws Exception { + System.out.println("========================================"); + System.out.println("人类反馈(Human-in-the-Loop)示例"); + System.out.println("========================================\n"); + + // ========== 模式一:InterruptionMetadata 模式 ========== + System.out.println("=== 模式一:InterruptionMetadata 模式 ==="); + System.out.println("演示如何在任意节点实现 InterruptableAction,通过返回 InterruptionMetadata 实现中断\n"); + + CompiledGraph graph1 = createGraphWithInterruptableAction(); + + // 执行直到中断 + InterruptionMetadata interruption = executeUntilInterruptWithMetadata(graph1); + + if (interruption != null) { + // 等待用户输入并更新状态 + RunnableConfig updatedConfig = waitUserInputAndUpdateStateWithMetadata(graph1, interruption); + + // 继续执行 + continueExecutionWithMetadata(graph1, updatedConfig); + } + + System.out.println("\n模式一示例执行完成\n"); + + // ========== 模式二:interruptBefore 模式 ========== + System.out.println("=== 模式二:interruptBefore 模式 ==="); + System.out.println("演示如何使用 interruptBefore 配置在指定节点前中断\n"); + + CompiledGraph graph2 = createGraphWithInterruptBefore(); + + // 执行直到中断 + executeUntilInterruptWithInterruptBefore(graph2); + + // 等待用户输入并更新状态 + RunnableConfig updateConfig1 = waitUserInputAndUpdateStateWithInterruptBefore(graph2); + + // 继续执行 + continueExecutionWithInterruptBefore(graph2, updateConfig1); + + // 第二次等待用户输入 + var invokeConfig = RunnableConfig.builder() + .threadId("Thread1") + .build(); + RunnableConfig updateConfig2 = waitUserInputSecondTime(graph2, invokeConfig); + + // 继续执行直到完成 + continueExecutionUntilComplete(graph2, updateConfig2); + + System.out.println("\n模式二示例执行完成"); + System.out.println("\n========================================"); + System.out.println("所有示例执行完成"); + System.out.println("========================================"); + } +} diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/LlmStreamingSpringAiExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/LlmStreamingSpringAiExample.java new file mode 100644 index 00000000..25dd6a21 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/LlmStreamingSpringAiExample.java @@ -0,0 +1,120 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.action.NodeAction; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatResponse; + +import java.util.Map; + +import reactor.core.publisher.Flux; + +/** + * Spring AI Alibaba LLM 流式集成示例 + * 演示如何在 Spring AI Alibaba Graph 中使用 LLM 流式输出功能 + */ +public class LlmStreamingSpringAiExample { + + /** + * 使用流式 ChatClient + */ + public static void useStreamingChatClient(ChatClient chatClient) { + // 使用流式输出 + Flux flux = chatClient.prompt() + .user("tell me a joke") + .stream() + .chatResponse(); + + // 订阅流式响应 + flux.subscribe( + response -> { + String content = response.getResult().getOutput().getText(); + System.out.print(content); + }, + error -> System.err.println("Error: " + error.getMessage()), + () -> System.out.println("\nStream completed") + ); + } + + /** + * 使用 Reactor 的阻塞式处理 + */ + public static void useBlockingStreaming(ChatClient chatClient) { + Flux flux = chatClient.prompt() + .user("tell me a joke") + .stream() + .chatResponse(); + + // 使用 Reactor 的阻塞式处理 + flux.collectList().block().forEach(response -> { + System.out.println("Received: " + response.getResult().getOutput().getText()); + }); + } + + public static void main(String[] args) { + System.out.println("=== Spring AI Alibaba LLM 流式集成示例 ===\n"); + + try { + // 示例 1: 使用流式 ChatClient(需要 ChatClient) + System.out.println("示例 1: 使用流式 ChatClient"); + System.out.println("注意: 此示例需要 ChatClient,跳过执行"); + // useStreamingChatClient(chatClient); + System.out.println(); + + // 示例 2: 使用 Reactor 的阻塞式处理(需要 ChatClient) + System.out.println("示例 2: 使用 Reactor 的阻塞式处理"); + System.out.println("注意: 此示例需要 ChatClient,跳过执行"); + // useBlockingStreaming(chatClient); + System.out.println(); + + System.out.println("所有示例执行完成"); + System.out.println("提示: 请配置 ChatClient 后运行完整示例"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 在 Graph 节点中使用流式输出 + */ + public static class StreamingAgentNode implements NodeAction { + + private final ChatClient chatClient; + + public StreamingAgentNode(ChatClient.Builder builder) { + this.chatClient = builder.build(); + } + + @Override + public Map apply(OverAllState state) { + String userMessage = (String) state.value("query").orElse("Hello"); + + // 使用流式输出 + Flux contentFlux = chatClient.prompt() + .user(userMessage) + .stream() + .content(); + + return Map.of("answer", contentFlux); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/LongTimeRunningTaskExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/LongTimeRunningTaskExample.java new file mode 100644 index 00000000..3e3ac40f --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/LongTimeRunningTaskExample.java @@ -0,0 +1,172 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompileConfig; +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 持久化执行示例 + * 演示长时间运行任务的持久化执行 + */ +public class LongTimeRunningTaskExample { + + /** + * 示例: 长时间运行的数据处理任务 + */ + public static void longRunningDataProcessingTask() throws GraphStateException { + // 定义状态 + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("items", new ReplaceStrategy()); + keyStrategyMap.put("processedCount", new ReplaceStrategy()); + keyStrategyMap.put("results", new AppendStrategy()); + return keyStrategyMap; + }; + + // 处理数据的节点 + var processData = node_async(state -> { + List items = (List) state.value("items").orElse(List.of()); + int processedCount = (int) state.value("processedCount").orElse(0); + + // 批量处理(例如每次处理 100 个) + int batchSize = 100; + int start = processedCount; + int end = Math.min(start + batchSize, items.size()); + + List batch = items.subList(start, end); + List processedResults = batch.stream() + .map(item -> "Processed: " + item) + .collect(Collectors.toList()); + + return Map.of( + "processedCount", end, + "results", processedResults + ); + }); + + // 检查是否完成 + var checkComplete = edge_async(state -> { + int processedCount = (int) state.value("processedCount").orElse(0); + List items = (List) state.value("items").orElse(List.of()); + + return processedCount >= items.size() ? END : "process_data"; + }); + + // 创建图 + StateGraph stateGraph = new StateGraph(keyStrategyFactory) + .addNode("process_data", processData) + .addEdge(START, "process_data") + .addConditionalEdges("process_data", checkComplete, + Map.of(END, END, "process_data", "process_data")); + + // 配置持久化 + SaverConfig saverConfig = SaverConfig.builder() + .register(new MemorySaver()) + .build(); + + CompiledGraph graph = stateGraph.compile( + CompileConfig.builder() + .saverConfig(saverConfig) + .build() + ); + + // 执行长时间运行的任务 + RunnableConfig config = RunnableConfig.builder() + .threadId("long-running-task-" + UUID.randomUUID()) + .build(); + + // 创建大量数据 + List largeDataSet = IntStream.range(0, 10000) + .mapToObj(i -> "Item-" + i) + .collect(Collectors.toList()); + + // 执行(可能会被中断,但可以恢复) + graph.invoke(Map.of( + "items", largeDataSet, + "processedCount", 0 + ), config); + + System.out.println("Long-running task example executed"); + } + + /** + * 示例: 从错误中恢复 + */ + public static void errorRecoveryExample(CompiledGraph graph) { + String threadId = "error-recovery-thread"; + RunnableConfig config = RunnableConfig.builder() + .threadId(threadId) + .build(); + + try { + // 第一次执行可能会失败 + graph.invoke(Map.of("data", "test"), config); + } + catch (Exception e) { + System.err.println("第一次执行失败,准备重试: " + e.getMessage()); + + // 使用相同的 threadId 重新执行,将从检查点恢复 + // 传入 null 作为输入,表示从上次状态继续 + graph.invoke(Map.of(), config); + } + } + + public static void main(String[] args) { + System.out.println("=== 持久化执行示例 ===\n"); + + try { + // 示例 1: 长时间运行的数据处理任务 + System.out.println("示例 1: 长时间运行的数据处理任务"); + longRunningDataProcessingTask(); + System.out.println(); + + // 示例 2: 从错误中恢复(需要 CompiledGraph) + System.out.println("示例 2: 从错误中恢复"); + System.out.println("注意: 此示例需要 CompiledGraph,跳过执行"); + // errorRecoveryExample(graph); + System.out.println(); + + System.out.println("所有示例执行完成"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/McpNodeExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/McpNodeExample.java new file mode 100644 index 00000000..0e8c196b --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/McpNodeExample.java @@ -0,0 +1,92 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.action.NodeAction; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.tool.ToolCallback; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import reactor.core.publisher.Flux; + +/** + * MCP 节点示例 + * 演示如何为指定节点分配 MCP 工具 + */ +public class McpNodeExample { + + /** + * 配置 MCP 节点 + */ + public static void configureMcpNode(ChatClient.Builder chatClientBuilder, Set toolCallbacks) { + McpNode mcpNode = new McpNode(chatClientBuilder, toolCallbacks); + System.out.println("MCP node configured successfully"); + } + + public static void main(String[] args) { + System.out.println("=== MCP 节点示例 ===\n"); + + try { + // 示例: 配置 MCP 节点(需要 ChatClient 和 ToolCallbacks) + System.out.println("示例: 配置 MCP 节点"); + System.out.println("注意: 此示例需要 ChatClient 和 ToolCallbacks,跳过执行"); + // configureMcpNode(ChatClient.builder(...), toolCallbacks); + System.out.println(); + + System.out.println("所有示例执行完成"); + System.out.println("提示: 请配置 ChatClient 和 ToolCallbacks 后运行完整示例"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * MCP 节点实现 + */ + public static class McpNode implements NodeAction { + + private static final String NODENAME = "mcp-node"; + + private final ChatClient chatClient; + + public McpNode(ChatClient.Builder chatClientBuilder, Set toolCallbacks) { + // 为节点配置 MCP 工具 + this.chatClient = chatClientBuilder + .defaultToolCallbacks(toolCallbacks.toArray(ToolCallback[]::new)) + .build(); + } + + @Override + public Map apply(OverAllState state) { + String query = state.value("query", ""); + Flux streamResult = chatClient.prompt(query).stream().content(); + String result = streamResult.reduce("", (acc, item) -> acc + item).block(); + + HashMap resultMap = new HashMap<>(); + resultMap.put("mcpcontent", result); + + return resultMap; + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/MultiAgentSupervisorExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/MultiAgentSupervisorExample.java new file mode 100644 index 00000000..9c066040 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/MultiAgentSupervisorExample.java @@ -0,0 +1,482 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.action.NodeAction; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * Multi-Agent Supervisor 示例 + * + * 演示如何使用 LLM 来协调不同的 agents。 + * 创建一个 agent 组,其中包含一个 supervisor agent 来帮助委派任务。 + * + * 架构: + * - Supervisor Agent: 负责路由到不同的 worker agents + * - Researcher Agent: 负责研究任务,使用搜索工具 + * - Coder Agent: 负责代码执行任务,使用代码执行工具 + */ +public class MultiAgentSupervisorExample { + + private final ChatModel chatModel; + private final ChatModel chatModelWithTool; + + public MultiAgentSupervisorExample(ChatModel chatModel, ChatModel chatModelWithTool) { + this.chatModel = chatModel; + this.chatModelWithTool = chatModelWithTool; + } + + /** + * Main 方法 + */ + public static void main(String[] args) { + System.out.println("=== Multi-Agent Supervisor 示例 ===\n"); + + // 检查环境变量 + String apiKey = System.getenv("ALI_AI_KEY"); + if (apiKey == null || apiKey.isEmpty()) { + System.err.println("错误:请先配置 AI_DASHSCOPE_API_KEY 环境变量"); + System.err.println("示例需要 DashScope API Key 才能运行"); + return; + } + + try { + // 创建 DashScope API 实例 + DashScopeApi dashScopeApi = DashScopeApi.builder() + .apiKey(apiKey) + .build(); + + // 创建 ChatModel(用于 Supervisor) + ChatModel chatModel = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建 ChatModel(用于 Worker Agents,可以使用相同的模型) + ChatModel chatModelWithTool = DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .build(); + + // 创建示例实例 + MultiAgentSupervisorExample example = new MultiAgentSupervisorExample( + chatModel, chatModelWithTool); + + // 创建 Graph + System.out.println("创建 Multi-Agent Supervisor Graph..."); + CompiledGraph graph = example.createSupervisorGraph(); + System.out.println("Graph 创建完成\n"); + + // 执行示例 1: Supervisor -> Coder + example.executeGraphWithCoder(graph); + + // 执行示例 2: Supervisor -> Researcher + example.executeGraphWithResearcher(graph); + + System.out.println("\n所有示例执行完成"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 创建 Multi-Agent Supervisor Graph + */ + public CompiledGraph createSupervisorGraph() throws GraphStateException { + // 定义状态管理策略 + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("messages", new AppendStrategy()); + strategies.put("next", new ReplaceStrategy()); + return strategies; + }; + + // 创建 agents + String[] members = {"researcher", "coder"}; + SupervisorNode supervisor = new SupervisorNode(chatModel, members); + ResearcherNode researcher = new ResearcherNode(chatModelWithTool); + CoderNode coder = new CoderNode(chatModelWithTool); + + // 构建 StateGraph + StateGraph workflow = new StateGraph(keyStrategyFactory) + .addNode("supervisor", node_async(supervisor)) + .addNode("researcher", node_async(researcher)) + .addNode("coder", node_async(coder)) + .addEdge(START, "supervisor") + .addConditionalEdges( + "supervisor", + edge_async(state -> { + String next = (String) state.value("next").orElse("FINISH"); + return next; + }), + Map.of( + "FINISH", END, + "researcher", "researcher", + "coder", "coder" + ) + ) + .addEdge("researcher", "supervisor") + .addEdge("coder", "supervisor"); + + return workflow.compile(); + } + + /** + * 执行 Graph(Supervisor -> Coder) + */ + public void executeGraphWithCoder(CompiledGraph graph) { + System.out.println("\n=== 执行 Graph (Supervisor -> Coder) ==="); + + Map input = Map.of( + "messages", List.of( + Map.of("role", "user", "content", "1 + 1 的结果是多少?") + ) + ); + + RunnableConfig config = RunnableConfig.builder() + .threadId("supervisor-coder-thread") + .build(); + + graph.stream(input, config) + .doOnNext(event -> System.out.println("节点: " + event.node() + ", 状态: " + event.state())) + .doOnError(error -> System.err.println("流错误: " + error.getMessage())) + .doOnComplete(() -> System.out.println("流完成")) + .blockLast(); + } + + /** + * 执行 Graph(Supervisor -> Researcher) + */ + public void executeGraphWithResearcher(CompiledGraph graph) { + System.out.println("\n=== 执行 Graph (Supervisor -> Researcher) ==="); + + Map input = Map.of( + "messages", List.of( + Map.of("role", "user", "content", "下一届冬奥会在哪里举行?") + ) + ); + + RunnableConfig config = RunnableConfig.builder() + .threadId("supervisor-researcher-thread") + .build(); + + graph.stream(input, config) + .doOnNext(event -> System.out.println("节点: " + event.node() + ", 状态: " + event.state())) + .doOnError(error -> System.err.println("流错误: " + error.getMessage())) + .doOnComplete(() -> System.out.println("流完成")) + .blockLast(); + } + + /** + * 搜索工具(模拟实现) + */ + public static class SearchTool implements BiFunction { + + public static final String DESCRIPTION = """ + 使用此工具在互联网上执行搜索。 + + Usage: + - query 参数是要搜索的查询字符串 + - 工具会执行搜索并返回搜索结果 + - 这是一个模拟实现,返回固定的搜索结果 + """; + + @Override + public String apply(SearchRequest request, ToolContext toolContext) { + System.out.println("搜索查询: '" + request.query + "'"); + // 模拟搜索结果 + return "下一届冬奥会将在意大利的科尔蒂纳举行,时间是2026年"; + } + + /** + * 搜索请求结构 + */ + public static class SearchRequest { + @JsonProperty(required = true) + @JsonPropertyDescription("要搜索的查询字符串") + public String query; + + public SearchRequest() { + } + + public SearchRequest(String query) { + this.query = query; + } + } + } + + /** + * 代码执行工具(模拟实现) + */ + public static class CoderTool implements BiFunction { + + public static final String DESCRIPTION = """ + 使用此工具执行 Java 代码并进行数学计算。 + + Usage: + - request 参数是要执行的代码请求 + - 如果你想查看某个值的输出,应该使用 `System.out.println(...);` 打印出来 + - 这对用户可见 + - 这是一个模拟实现,返回固定的执行结果 + """; + + @Override + public String apply(CodeRequest request, ToolContext toolContext) { + System.out.println("代码执行请求: '" + request.request + "'"); + // 模拟代码执行结果 + return "2"; + } + + /** + * 代码执行请求结构 + */ + public static class CodeRequest { + @JsonProperty(required = true) + @JsonPropertyDescription("要执行的代码请求") + public String request; + + public CodeRequest() { + } + + public CodeRequest(String request) { + this.request = request; + } + } + } + + /** + * Supervisor Agent Node + * 负责决定将任务路由到哪个 worker + */ + public static class SupervisorNode implements NodeAction { + private final ChatClient chatClient; + private final String[] members; + + public SupervisorNode(ChatModel model, String[] members) { + this.chatClient = ChatClient.builder(model).build(); + this.members = members; + } + + @Override + public Map apply(OverAllState state) throws Exception { + // 获取最后一条消息 + List messages = (List) state.value("messages").orElse(List.of()); + if (messages.isEmpty()) { + throw new IllegalStateException("No messages in state"); + } + + // 获取最后一条消息的文本内容 + String lastMessageText = extractTextFromMessage(messages.get(messages.size() - 1)); + + // 构建系统提示 + String membersList = String.join(", ", members); + String systemPrompt = String.format( + "你是一个 supervisor,负责管理以下 workers 之间的对话:%s。\n" + + "根据以下用户请求,响应应该由哪个 worker 来处理。\n" + + "每个 worker 将执行任务并返回结果和状态。\n" + + "当任务完成时,响应 FINISH。\n" + + "只返回 worker 名称(%s)或 FINISH,不要返回其他内容。", + membersList, membersList + ); + + // 调用 LLM 决定路由 + String result = chatClient.prompt() + .system(systemPrompt) + .user("用户消息: " + lastMessageText) + .call() + .content(); + + // 清理结果,确保只返回 worker 名称或 FINISH + String next = normalizeRoute(result, members); + + return Map.of("next", next); + } + + /** + * 规范化路由结果 + */ + private String normalizeRoute(String result, String[] members) { + if (result == null || result.trim().isEmpty()) { + return "FINISH"; + } + + String normalized = result.trim().toLowerCase(); + + // 检查是否是 FINISH + if (normalized.equals("finish") || normalized.contains("finish")) { + return "FINISH"; + } + + // 检查是否匹配任何成员 + for (String member : members) { + if (normalized.equals(member.toLowerCase()) || + normalized.contains(member.toLowerCase())) { + return member; + } + } + + // 如果无法确定,根据消息内容推断 + // 这里可以根据实际需求添加更智能的路由逻辑 + // 默认返回第一个 worker + return members.length > 0 ? members[0] : "FINISH"; + } + + private String extractTextFromMessage(Object message) { + if (message instanceof Map) { + Map msgMap = (Map) message; + Object content = msgMap.get("content"); + if (content != null) { + return content.toString(); + } + } + return message.toString(); + } + } + + /** + * Researcher Agent Node + * 负责执行研究任务 + */ + public static class ResearcherNode implements NodeAction { + private final ChatClient chatClient; + + public ResearcherNode(ChatModel model) { + ToolCallback searchTool = FunctionToolCallback.builder("search", new SearchTool()) + .description(SearchTool.DESCRIPTION) + .inputType(SearchTool.SearchRequest.class) + .build(); + + this.chatClient = ChatClient.builder(model) + .defaultTools(searchTool) + .build(); + } + + @Override + public Map apply(OverAllState state) throws Exception { + // 获取最后一条消息 + List messages = (List) state.value("messages").orElse(List.of()); + if (messages.isEmpty()) { + throw new IllegalStateException("No messages in state"); + } + + String lastMessageText = extractTextFromMessage(messages.get(messages.size() - 1)); + + // 使用 ChatClient 调用 LLM,LLM 可能会调用搜索工具 + String result = chatClient.prompt() + .user(lastMessageText) + .call() + .content(); + + // 返回结果消息 + return Map.of("messages", List.of( + Map.of("role", "assistant", "content", result) + )); + } + + private String extractTextFromMessage(Object message) { + if (message instanceof Map) { + Map msgMap = (Map) message; + Object content = msgMap.get("content"); + if (content != null) { + return content.toString(); + } + } + return message.toString(); + } + } + + /** + * Coder Agent Node + * 负责执行代码任务 + */ + public static class CoderNode implements NodeAction { + private final ChatClient chatClient; + + public CoderNode(ChatModel model) { + ToolCallback coderTool = FunctionToolCallback.builder("executeCode", new CoderTool()) + .description(CoderTool.DESCRIPTION) + .inputType(CoderTool.CodeRequest.class) + .build(); + + this.chatClient = ChatClient.builder(model) + .defaultTools(coderTool) + .build(); + } + + @Override + public Map apply(OverAllState state) throws Exception { + // 获取最后一条消息 + List messages = (List) state.value("messages").orElse(List.of()); + if (messages.isEmpty()) { + throw new IllegalStateException("No messages in state"); + } + + String lastMessageText = extractTextFromMessage(messages.get(messages.size() - 1)); + + // 使用 ChatClient 调用 LLM,LLM 可能会调用代码执行工具 + String result = chatClient.prompt() + .user(lastMessageText) + .call() + .content(); + + // 返回结果消息 + return Map.of("messages", List.of( + Map.of("role", "assistant", "content", result) + )); + } + + private String extractTextFromMessage(Object message) { + if (message instanceof Map) { + Map msgMap = (Map) message; + Object content = msgMap.get("content"); + if (content != null) { + return content.toString(); + } + } + return message.toString(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/ParallelBranchExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/ParallelBranchExample.java new file mode 100644 index 00000000..af674525 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/ParallelBranchExample.java @@ -0,0 +1,168 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.action.AsyncNodeAction; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 创建并行节点执行分支示例 + * 演示如何创建并行分支以加速图执行 + */ +public class ParallelBranchExample { + + /** + * 创建节点的辅助方法 + */ + private static AsyncNodeAction makeNode(String message) { + return node_async(state -> Map.of("messages", List.of(message))); + } + + /** + * 定义带并行分支的 Graph + */ + public static CompiledGraph createParallelBranchGraph() throws GraphStateException { + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap keyStrategyHashMap = new HashMap<>(); + keyStrategyHashMap.put("messages", new AppendStrategy()); + return keyStrategyHashMap; + }; + + // 构建并行 Graph + StateGraph workflow = new StateGraph(keyStrategyFactory) + .addNode("A", makeNode("A")) + .addNode("A1", makeNode("A1")) + .addNode("A2", makeNode("A2")) + .addNode("A3", makeNode("A3")) + .addNode("B", makeNode("B")) + .addNode("C", makeNode("C")) + .addEdge("A", "A1") // A 到 A1 + .addEdge("A", "A2") // A 到 A2(并行) + .addEdge("A", "A3") // A 到 A3(并行) + .addEdge("A1", "B") // A1 汇聚到 B + .addEdge("A2", "B") // A2 汇聚到 B + .addEdge("A3", "B") // A3 汇聚到 B + .addEdge("B", "C") + .addEdge(START, "A") + .addEdge("C", END); + + return workflow.compile(); + } + + /** + * 执行并行 Graph + */ + public static void executeParallelGraph(CompiledGraph compiledGraph) { + // 执行 Graph + compiledGraph.stream(Map.of()) + .doOnNext(step -> System.out.println(step)) + .doOnError(error -> System.err.println("流错误: " + error.getMessage())) + .doOnComplete(() -> System.out.println("流完成")) + .blockLast(); + } + + /** + * 使用编译的子图作为并行节点 + */ + public static CompiledGraph useCompiledSubgraphAsParallelNode() throws GraphStateException { + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap keyStrategyHashMap = new HashMap<>(); + keyStrategyHashMap.put("messages", new AppendStrategy()); + return keyStrategyHashMap; + }; + + // 创建子图 A3 + StateGraph subgraphA3Builder = new StateGraph(keyStrategyFactory) + .addNode("A3.1", makeNode("A3.1")) + .addNode("A3.2", makeNode("A3.2")) + .addEdge(START, "A3.1") + .addEdge("A3.1", "A3.2") + .addEdge("A3.2", END); + + CompiledGraph subgraphA3 = subgraphA3Builder.compile(); + + // 创建子图 A1 + StateGraph subgraphA1Builder = new StateGraph(keyStrategyFactory) + .addNode("A1.1", makeNode("A1.1")) + .addNode("A1.2", makeNode("A1.2")) + .addEdge(START, "A1.1") + .addEdge("A1.1", "A1.2") + .addEdge("A1.2", END); + + CompiledGraph subgraphA1 = subgraphA1Builder.compile(); + + // 主图:混合使用节点和子图 + StateGraph workflow = new StateGraph(keyStrategyFactory) + .addNode("A", makeNode("A")) + .addNode("A1", node_async(state -> subgraphA1.invoke(state.data()).orElseThrow().data())) + .addNode("A2", makeNode("A2")) + .addNode("A3", node_async(state -> subgraphA3.invoke(state.data()).orElseThrow().data())) + .addNode("B", makeNode("B")) + .addEdge("A", "A1") + .addEdge("A", "A2") + .addEdge("A", "A3") + .addEdge("A1", "B") + .addEdge("A2", "B") + .addEdge("A3", "B") + .addEdge(START, "A") + .addEdge("B", END); + + return workflow.compile(); + } + + public static void main(String[] args) { + System.out.println("=== 创建并行节点执行分支示例 ===\n"); + + try { + // 示例 1: 定义带并行分支的 Graph + System.out.println("示例 1: 定义带并行分支的 Graph"); + CompiledGraph graph = createParallelBranchGraph(); + System.out.println("并行分支图创建完成"); + System.out.println(); + + // 示例 2: 执行并行 Graph + System.out.println("示例 2: 执行并行 Graph"); + executeParallelGraph(graph); + System.out.println(); + + // 示例 3: 使用编译的子图作为并行节点 + System.out.println("示例 3: 使用编译的子图作为并行节点"); + CompiledGraph subgraphGraph = useCompiledSubgraphAsParallelNode(); + System.out.println("子图作为并行节点示例创建完成"); + System.out.println(); + + System.out.println("所有示例执行完成"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/ParallelStreamingExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/ParallelStreamingExample.java new file mode 100644 index 00000000..8e0ceca2 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/ParallelStreamingExample.java @@ -0,0 +1,251 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompileConfig; +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.action.AsyncNodeAction; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.streaming.StreamingOutput; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; + +import reactor.core.publisher.Flux; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; + +/** + * 并行流式输出示例 + * 演示如何在并行分支中使用 Flux 实现流式输出 + * 每个并行节点可以独立产生流式输出,并保持各自的节点 ID + */ +public class ParallelStreamingExample { + + /** + * 示例 1: 并行节点流式输出 - 每个节点保持独立的节点 ID + * + * 演示如何创建多个并行节点,每个节点返回 Flux 流式输出 + * 流式输出会保持各自的节点 ID,便于区分不同节点的输出 + */ + public static void parallelStreamingWithNodeIdPreservation() throws GraphStateException { + // 定义状态策略 + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("messages", new AppendStrategy()); + keyStrategyMap.put("parallel_results", new AppendStrategy()); + return keyStrategyMap; + }; + + // 并行节点 1 - 返回 Flux 流式输出 + AsyncNodeAction node1 = state -> { + System.out.println("Node1 executing on thread: " + Thread.currentThread().getName()); + + // 创建流式数据 + Flux stream1 = Flux.just("节点1-块1", "节点1-块2", "节点1-块3") + .delayElements(Duration.ofMillis(50)) + .doOnNext(chunk -> + System.out.println("Node1 streaming emitting on thread: " + Thread.currentThread().getName()) + ); + + return CompletableFuture.completedFuture(Map.of("stream1", stream1)); + }; + + // 并行节点 2 - 返回 Flux 流式输出 + AsyncNodeAction node2 = state -> { + System.out.println("Node2 executing on thread: " + Thread.currentThread().getName()); + + // 创建流式数据(延迟时间不同,模拟不同的处理速度) + Flux stream2 = Flux.just("节点2-块1", "节点2-块2", "节点2-块3") + .delayElements(Duration.ofMillis(75)) + .doOnNext(chunk -> + System.out.println("Node2 streaming emitting on thread: " + Thread.currentThread().getName()) + ); + + return CompletableFuture.completedFuture(Map.of("stream2", stream2)); + }; + + // 合并节点 - 接收并行节点的结果 + AsyncNodeAction mergeNode = state -> { + System.out.println("\n合并节点接收到状态: " + state.data()); + return CompletableFuture.completedFuture( + Map.of("messages", "所有并行节点已完成,结果已合并") + ); + }; + + // 构建图:两个并行节点从 START 开始,都汇聚到 merge 节点 + StateGraph stateGraph = new StateGraph(keyStrategyFactory) + .addNode("node1", node1) + .addNode("node2", node2) + .addNode("merge", mergeNode) + .addEdge(START, "node1") // 并行分支 1 + .addEdge(START, "node2") // 并行分支 2 + .addEdge("node1", "merge") // 汇聚到合并节点 + .addEdge("node2", "merge") // 汇聚到合并节点 + .addEdge("merge", END); + + // 编译图 + CompiledGraph graph = stateGraph.compile( + CompileConfig.builder() + .build() + ); + + // 创建配置 + RunnableConfig config = RunnableConfig.builder() + .threadId("parallel_streaming_thread") + .build(); + + // 跟踪每个节点产生的流式输出数量 + Map nodeStreamCounts = new HashMap<>(); + AtomicInteger totalChunks = new AtomicInteger(0); + + System.out.println("开始并行流式输出...\n"); + + // 执行流式图并处理输出 + graph.stream(Map.of("input", "test"), config) + .doOnNext(output -> { + if (output instanceof StreamingOutput streamingOutput) { + // 处理流式输出 + String nodeId = streamingOutput.node(); + String chunk = streamingOutput.chunk(); + + // 统计每个节点的流式输出 + nodeStreamCounts.merge(nodeId, 1, Integer::sum); + totalChunks.incrementAndGet(); + + // 实时打印流式内容,显示节点 ID + System.out.println("[流式输出] 节点: " + nodeId + + ", 内容: " + chunk); + } + else { + // 处理普通节点输出 + String nodeId = output.node(); + Map state = output.state().data(); + System.out.println("\n[节点完成] " + nodeId + + ", 状态: " + state); + } + }) + .doOnComplete(() -> { + System.out.println("\n=== 并行流式输出完成 ==="); + System.out.println("总流式块数: " + totalChunks.get()); + System.out.println("各节点流式输出统计: " + nodeStreamCounts); + }) + .doOnError(error -> { + System.err.println("流式输出错误: " + error.getMessage()); + error.printStackTrace(); + }) + .blockLast(); // 阻塞等待流完成 + } + + /** + * 示例 2: 单个节点的流式输出 + * + * 演示单个节点使用 Flux 产生流式输出 + */ + public static void singleNodeStreaming() throws GraphStateException { + // 定义状态策略 + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("messages", new AppendStrategy()); + keyStrategyMap.put("stream_result", new AppendStrategy()); + return keyStrategyMap; + }; + + // 单个流式节点 + AsyncNodeAction streamingNode = state -> { + // 创建流式数据 + Flux dataStream = Flux.just("块1", "块2", "块3", "块4", "块5") + .delayElements(Duration.ofMillis(100)); + + + return CompletableFuture.completedFuture(Map.of("stream_output", dataStream)); + }; + + // 构建图 + StateGraph stateGraph = new StateGraph(keyStrategyFactory) + .addNode("streaming_node", streamingNode) + .addEdge(START, "streaming_node") + .addEdge("streaming_node", END); + + // 编译图 + CompiledGraph graph = stateGraph.compile( + CompileConfig.builder() + .build() + ); + + // 创建配置 + RunnableConfig config = RunnableConfig.builder() + .threadId("single_streaming_thread") + .build(); + + System.out.println("开始单节点流式输出...\n"); + + AtomicInteger streamCount = new AtomicInteger(0); + String[] lastNodeId = new String[1]; + + // 执行流式图 + graph.stream(Map.of("input", "test"), config) + .filter(output -> output instanceof StreamingOutput) + .map(output -> (StreamingOutput) output) + .doOnNext(streamingOutput -> { + streamCount.incrementAndGet(); + lastNodeId[0] = streamingOutput.node(); + System.out.println("[流式输出] 节点: " + streamingOutput.node() + + ", 内容: " + streamingOutput.chunk()); + }) + .doOnComplete(() -> { + System.out.println("\n=== 单节点流式输出完成 ==="); + System.out.println("节点 ID: " + lastNodeId[0]); + System.out.println("流式块数: " + streamCount.get()); + }) + .doOnError(error -> { + System.err.println("流式输出错误: " + error.getMessage()); + }) + .blockLast(); + } + + public static void main(String[] args) { + System.out.println("=== 并行流式输出示例 ===\n"); + + try { + // 示例 1: 并行节点流式输出 +// System.out.println("示例 1: 并行节点流式输出(保持节点 ID)"); +// parallelStreamingWithNodeIdPreservation(); +// System.out.println(); + + // 示例 2: 单个节点流式输出 + System.out.println("示例 2: 单个节点流式输出"); + singleNodeStreaming(); + System.out.println(); + + System.out.println("所有示例执行完成"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/PersistenceExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/PersistenceExample.java new file mode 100644 index 00000000..cf201708 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/PersistenceExample.java @@ -0,0 +1,251 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompileConfig; +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.StateSnapshot; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; + +import org.springframework.ai.chat.client.ChatClient; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 为图添加持久化(记忆)示例 + * 演示如何使用 Checkpointer 为 StateGraph 提供持久化记忆 + */ +public class PersistenceExample { + + /** + * 不使用 Checkpointer 的示例 + */ + public static CompiledGraph createGraphWithoutCheckpointer(ChatClient.Builder chatClientBuilder) throws GraphStateException { + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("messages", new AppendStrategy()); + strategies.put("user_name", new ReplaceStrategy()); + strategies.put("context", new ReplaceStrategy()); + return strategies; + }; + + StateGraph workflow = new StateGraph(keyStrategyFactory) + .addNode("agent", node_async(state -> { + List messages = (List) state.value("messages").orElse(List.of()); + String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1); + return Map.of("messages", "Response to: " + lastMessage); + })) + .addEdge(START, "agent") + .addEdge("agent", END); + + return workflow.compile(); + } + + /** + * 添加持久化(记忆) + */ + public static CompiledGraph createGraphWithCheckpointer(ChatClient.Builder chatClientBuilder) throws GraphStateException { + // 创建 Checkpointer + var checkpointer = new MemorySaver(); + + // 配置持久化 + var compileConfig = CompileConfig.builder() + .saverConfig(SaverConfig.builder() + .register(checkpointer) + .build()) + .build(); + + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("messages", new AppendStrategy()); + return strategies; + }; + + StateGraph workflow = new StateGraph(keyStrategyFactory) + .addNode("agent", node_async(state -> { + List messages = (List) state.value("messages").orElse(List.of()); + String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1); + return Map.of("messages", "Response to: " + lastMessage); + })) + .addEdge(START, "agent") + .addEdge("agent", END); + + // 编译带持久化的 Graph + return workflow.compile(compileConfig); + } + + /** + * 测试带持久化的 Graph + */ + public static void testGraphWithPersistence(CompiledGraph persistentGraph) { + // 创建运行配置(使用 threadId 标识会话) + var config = RunnableConfig.builder() + .threadId("user-alice-session") + .build(); + + // 第一次调用 - 介绍自己 + System.out.println("=== First call with persistence - Introduction ==="); + var result1 = persistentGraph.invoke( + Map.of("messages", List.of("Hi, I'm Alice, nice to meet you")), + config + ); + + List messages1 = (List) result1.get().data().get("messages"); + System.out.println("Response: " + messages1.get(messages1.size() - 1)); + + // 第二次调用 - 询问名字(有持久化,可以记住) + System.out.println("=== Second call with persistence - Ask name ==="); + var result2 = persistentGraph.invoke( + Map.of("messages", List.of("What's my name?")), + config + ); + + List messages2 = (List) result2.get().data().get("messages"); + System.out.println("Response: " + messages2.get(messages2.size() - 1)); + } + + /** + * 多会话隔离 + */ + public static void multiSessionIsolation(CompiledGraph persistentGraph) { + // Alice 的会话 + var aliceConfig = RunnableConfig.builder() + .threadId("user-alice") + .build(); + + persistentGraph.invoke(Map.of("messages", List.of("Hi, I'm Alice")), aliceConfig); + + // Bob 的会话 + var bobConfig = RunnableConfig.builder() + .threadId("user-bob") + .build(); + + persistentGraph.invoke(Map.of("messages", List.of("Hi, I'm Bob")), bobConfig); + + // Alice 询问名字 - 能记住 + var aliceResult = persistentGraph.invoke( + Map.of("messages", List.of("What's my name?")), + aliceConfig + ); + System.out.println("Alice: " + aliceResult.get().data().get("messages")); + + // Bob 询问名字 - 也能记住 + var bobResult = persistentGraph.invoke( + Map.of("messages", List.of("What's my name?")), + bobConfig + ); + System.out.println("Bob: " + bobResult.get().data().get("messages")); + } + + /** + * 获取当前状态 + */ + public static void getCurrentState(CompiledGraph graph) { + RunnableConfig config = RunnableConfig.builder() + .threadId("user-alice") + .build(); + + StateSnapshot snapshot = graph.getState(config); + + System.out.println("Current node: " + snapshot.node()); + System.out.println("Current state: " + snapshot.state()); + System.out.println("Next node: " + snapshot.next()); + System.out.println("Checkpoint ID: " + snapshot.config().checkPointId().orElse("N/A")); + } + + /** + * 获取状态历史 + */ + public static void getStateHistory(CompiledGraph graph) { + RunnableConfig config = RunnableConfig.builder() + .threadId("user-alice") + .build(); + + // 获取所有历史状态 + List history = (List) graph.getStateHistory(config); + + System.out.println("=== State History ==="); + for (int i = 0; i < history.size(); i++) { + StateSnapshot h = history.get(i); + System.out.println("Step " + i + ": node=" + h.node() + + ", messages count=" + ((List) h.state().data().get("messages")).size()); + } + } + + public static void main(String[] args) { + System.out.println("=== 持久化示例 ===\n"); + + try { + // 示例 1: 不使用 Checkpointer 的示例(需要 ChatClient) + System.out.println("示例 1: 不使用 Checkpointer 的示例"); + System.out.println("注意: 此示例需要 ChatClient,跳过执行"); + // CompiledGraph graphWithoutCheckpointer = createGraphWithoutCheckpointer(ChatClient.builder(...)); + System.out.println(); + + // 示例 2: 添加持久化(需要 ChatClient) + System.out.println("示例 2: 添加持久化"); + System.out.println("注意: 此示例需要 ChatClient,跳过执行"); + // CompiledGraph persistentGraph = createGraphWithCheckpointer(ChatClient.builder(...)); + System.out.println(); + + // 示例 3: 测试带持久化的 Graph(需要 CompiledGraph) + System.out.println("示例 3: 测试带持久化的 Graph"); + System.out.println("注意: 此示例需要 CompiledGraph,跳过执行"); + // testGraphWithPersistence(persistentGraph); + System.out.println(); + + // 示例 4: 多会话隔离(需要 CompiledGraph) + System.out.println("示例 4: 多会话隔离"); + System.out.println("注意: 此示例需要 CompiledGraph,跳过执行"); + // multiSessionIsolation(persistentGraph); + System.out.println(); + + // 示例 5: 获取当前状态(需要 CompiledGraph) + System.out.println("示例 5: 获取当前状态"); + System.out.println("注意: 此示例需要 CompiledGraph,跳过执行"); + // getCurrentState(persistentGraph); + System.out.println(); + + // 示例 6: 获取状态历史(需要 CompiledGraph) + System.out.println("示例 6: 获取状态历史"); + System.out.println("注意: 此示例需要 CompiledGraph,跳过执行"); + // getStateHistory(persistentGraph); + System.out.println(); + + System.out.println("所有示例执行完成"); + System.out.println("提示: 请配置 ChatClient 后运行完整示例"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/PlantUmlExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/PlantUmlExample.java new file mode 100644 index 00000000..da0d2f0a --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/PlantUmlExample.java @@ -0,0 +1,96 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.GraphRepresentation; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; + +import java.util.HashMap; +import java.util.Map; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * PlantUML 图表可视化示例 + * 演示如何使用 PlantUML 可视化 Spring AI Alibaba Graph 工作流结构 + */ +public class PlantUmlExample { + + /** + * 从 Graph 生成 PlantUML + */ + public static void generatePlantUmlFromGraph() throws GraphStateException { + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("result", new ReplaceStrategy()); + return strategies; + }; + + // 构建一个简单的 Graph + StateGraph graph = new StateGraph(keyStrategyFactory) + .addNode("step1", node_async(state -> Map.of("result", "Step 1"))) + .addNode("step2", node_async(state -> Map.of("result", "Step 2"))) + .addNode("step3", node_async(state -> Map.of("result", "Step 3"))) + .addEdge(START, "step1") + .addEdge("step1", "step2") + .addEdge("step2", "step3") + .addEdge("step3", END); + + CompiledGraph compiledGraph = graph.compile(); + + // 生成 PlantUML 表示 + GraphRepresentation representation = compiledGraph.getGraph( + GraphRepresentation.Type.PLANTUML, + "My Workflow" + ); + + // 显示 PlantUML 代码 + System.out.println("PlantUML representation:"); + System.out.println(representation.content()); + } + + /** + * 简单 PlantUML 代码示例 + */ + public static void simplePlantUmlExample() { + String code = """ + @startuml + title Spring AI Alibaba Graph + START --> NodeA + NodeA --> NodeB + NodeB --> END + @enduml + """; + + System.out.println("Simple PlantUML code:"); + System.out.println(code); + } + + public static void main(String[] args) throws GraphStateException { + System.out.println("=== PlantUML 图表可视化示例 ==="); + simplePlantUmlExample(); + generatePlantUmlFromGraph(); + System.out.println("所有示例执行完成"); + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/SubgraphAsCompiledGraphExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/SubgraphAsCompiledGraphExample.java new file mode 100644 index 00000000..9f4c09a5 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/SubgraphAsCompiledGraphExample.java @@ -0,0 +1,172 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.action.NodeAction; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; + +import java.util.HashMap; +import java.util.Map; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 子图作为 CompiledGraph 示例 + * 演示如何使用已编译的 Graph 作为子图 + */ +public class SubgraphAsCompiledGraphExample { + + /** + * 创建并编译子图 + */ + public static CompiledGraph createAndCompileSubGraph() throws GraphStateException { + KeyStrategyFactory subKeyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("input", new ReplaceStrategy()); + strategies.put("output", new ReplaceStrategy()); + return strategies; + }; + + // 定义并编译子图 + StateGraph subGraphDef = new StateGraph(subKeyFactory) + .addNode("process", node_async(state -> { + String input = (String) state.value("input").orElse(""); + String output = "Processed: " + input.toUpperCase(); + return Map.of("output", output); + })) + .addEdge(START, "process") + .addEdge("process", END); + + // 编译子图 + return subGraphDef.compile(); + } + + /** + * 在父图中使用 + */ + public static CompiledGraph useInParentGraph(CompiledGraph compiledSubGraph) throws GraphStateException { + KeyStrategyFactory parentKeyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("data", new ReplaceStrategy()); + strategies.put("result", new ReplaceStrategy()); + return strategies; + }; + + StateGraph parentGraph = new StateGraph(parentKeyFactory) + .addNode("prepare", node_async(state -> + Map.of("data", "hello world"))) + .addNode("subgraph", compiledSubGraph) + .addNode("finalize", node_async(state -> { + String result = (String) state.value("result").orElse(""); + return Map.of("final", "Done: " + result); + })) + .addEdge(START, "prepare") + .addEdge("prepare", "subgraph") + .addEdge("subgraph", "finalize") + .addEdge("finalize", END); + + return parentGraph.compile(); + } + + /** + * 多个子图复用 + */ + public static CompiledGraph reuseMultipleSubGraphs(CompiledGraph dataProcessor) throws GraphStateException { + KeyStrategyFactory keyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("data", new ReplaceStrategy()); + strategies.put("result", new ReplaceStrategy()); + return strategies; + }; + + // 在多个节点中复用 + StateGraph mainGraph = new StateGraph(keyFactory) + .addNode("process1", dataProcessor) + .addNode("process2", dataProcessor) + .addNode("process3", dataProcessor) + .addEdge(START, "process1") + .addEdge("process1", "process2") + .addEdge("process2", "process3") + .addEdge("process3", END); + + return mainGraph.compile(); + } + + public static void main(String[] args) { + System.out.println("=== 子图作为 CompiledGraph 示例 ===\n"); + + try { + // 示例 1: 创建并编译子图 + System.out.println("示例 1: 创建并编译子图"); + CompiledGraph subGraph = createAndCompileSubGraph(); + System.out.println("子图创建完成"); + System.out.println(); + + // 示例 2: 在父图中使用 + System.out.println("示例 2: 在父图中使用"); + CompiledGraph parentGraph = useInParentGraph(subGraph); + System.out.println("父图创建完成"); + System.out.println(); + + // 示例 3: 多个子图复用 + System.out.println("示例 3: 多个子图复用"); + CompiledGraph reusedGraph = reuseMultipleSubGraphs(subGraph); + System.out.println("多子图复用示例创建完成"); + System.out.println(); + + System.out.println("所有示例执行完成"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 在节点中使用 CompiledGraph + */ + public static class CompiledSubGraphNode implements NodeAction { + + private final CompiledGraph compiledGraph; + + public CompiledSubGraphNode(CompiledGraph compiledGraph) { + this.compiledGraph = compiledGraph; + } + + @Override + public Map apply(OverAllState state) { + // 从父状态提取输入 + String input = (String) state.value("data").orElse(""); + + // 执行编译好的子图 + Map subInput = Map.of("input", input); + OverAllState subResult = compiledGraph.invoke(subInput).orElseThrow(); + + // 提取子图输出 + String output = (String) subResult.value("output").orElse(""); + return Map.of("result", output); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/SubgraphAsNodeActionExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/SubgraphAsNodeActionExample.java new file mode 100644 index 00000000..9a178145 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/SubgraphAsNodeActionExample.java @@ -0,0 +1,160 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.action.NodeAction; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 子图作为节点操作示例 + * 演示如何将子图作为 NodeAction 在父图中使用 + */ +public class SubgraphAsNodeActionExample { + + /** + * 定义子图 + */ + public static CompiledGraph createSubGraph(KeyStrategyFactory keyStrategyFactory) throws GraphStateException { + StateGraph subGraph = new StateGraph(keyStrategyFactory) + .addNode("substep1", node_async(state -> { + String input = (String) state.value("input").orElse(""); + return Map.of("result", "SubStep1:" + input); + })) + .addNode("substep2", node_async(state -> { + String prev = (String) state.value("result").orElse(""); + return Map.of("result", prev + "->SubStep2"); + })) + .addEdge(START, "substep1") + .addEdge("substep1", "substep2") + .addEdge("substep2", END); + + return subGraph.compile(); + } + + /** + * 在父图中使用 + */ + public static CompiledGraph useInParentGraph(KeyStrategyFactory keyStrategyFactory, CompiledGraph subGraph) throws GraphStateException { + SubGraphNode subGraphNode = new SubGraphNode(subGraph); + + StateGraph parentGraph = new StateGraph(keyStrategyFactory) + .addNode("prepare", node_async(state -> { + return Map.of("data", "Input Data"); + })) + .addNode("process", node_async(subGraphNode)) // 使用子图作为节点 + .addNode("finalize", node_async(state -> { + String processed = (String) state.value("processed").orElse(""); + return Map.of("final", "Final:" + processed); + })) + .addEdge(START, "prepare") + .addEdge("prepare", "process") + .addEdge("process", "finalize") + .addEdge("finalize", END); + + return parentGraph.compile(); + } + + public static void main(String[] args) throws GraphStateException { + System.out.println("=== 子图作为节点操作示例 ==="); + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("data", new ReplaceStrategy()); + strategies.put("input", new ReplaceStrategy()); + strategies.put("result", new ReplaceStrategy()); + strategies.put("processed", new ReplaceStrategy()); + strategies.put("final", new ReplaceStrategy()); + return strategies; + }; + + CompiledGraph subGraph = createSubGraph(keyStrategyFactory); + CompiledGraph parentGraph = useInParentGraph(keyStrategyFactory, subGraph); + System.out.println("所有示例执行完成"); + } + + /** + * 将子图包装为 NodeAction + */ + public static class SubGraphNode implements NodeAction { + + private final CompiledGraph subGraph; + + public SubGraphNode(CompiledGraph subGraph) { + this.subGraph = subGraph; + } + + @Override + public Map apply(OverAllState state) { + // 从父状态提取子图需要的数据 + String input = (String) state.value("data").orElse(""); + + // 执行子图 + Map subInput = Map.of("input", input); + Optional subResult = subGraph.invoke(subInput); + + // 返回结果给父图 + String result = (String) subResult.get().value("result").orElse(""); + return Map.of("processed", result); + } + } + + /** + * 可配置的子图节点 + */ + public static class ConfigurableSubGraphNode implements NodeAction { + + private final CompiledGraph subGraph; + private final String inputKey; + private final String outputKey; + + public ConfigurableSubGraphNode( + CompiledGraph subGraph, + String inputKey, + String outputKey + ) { + this.subGraph = subGraph; + this.inputKey = inputKey; + this.outputKey = outputKey; + } + + @Override + public Map apply(OverAllState state) { + // 从父状态读取指定键的数据 + Object input = state.value(inputKey).orElse(null); + + // 执行子图 + OverAllState subResult = subGraph.invoke(Map.of("input", input)).orElseThrow(); + + // 将结果写入指定键 + Object output = subResult.value("result").orElse(null); + return Map.of(outputKey, output); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/SubgraphAsStateGraphExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/SubgraphAsStateGraphExample.java new file mode 100644 index 00000000..6701791d --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/SubgraphAsStateGraphExample.java @@ -0,0 +1,175 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.action.NodeAction; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 子图作为 StateGraph 示例 + * 演示如何将 StateGraph 组合使用 + */ +public class SubgraphAsStateGraphExample { + + /** + * 定义子图 + */ + public static StateGraph createProcessingSubGraph() throws GraphStateException { + KeyStrategyFactory keyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("input", new ReplaceStrategy()); + strategies.put("output", new ReplaceStrategy()); + strategies.put("valid", new ReplaceStrategy()); + return strategies; + }; + + return new StateGraph(keyFactory) + .addNode("validate", node_async(state -> { + String input = (String) state.value("input").orElse(""); + boolean isValid = input != null && !input.isEmpty(); + return Map.of("valid", isValid); + })) + .addNode("transform", node_async(state -> { + String input = (String) state.value("input").orElse(""); + String transformed = input.toUpperCase(); + return Map.of("output", transformed); + })) + .addEdge(START, "validate") + .addConditionalEdges("validate", + edge_async(state -> { + Boolean valid = (Boolean) state.value("valid").orElse(false); + return valid ? "valid" : "invalid"; + }), + Map.of( + "valid", "transform", + "invalid", END + )) + .addEdge("transform", END); + } + + /** + * 在父图中集成子图 - 方式 1: 直接嵌入 + */ + public static StateGraph createParentGraphWithDirectEmbedding() throws GraphStateException { + KeyStrategyFactory keyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("data", new ReplaceStrategy()); + strategies.put("output", new ReplaceStrategy()); + strategies.put("result", new ReplaceStrategy()); + return strategies; + }; + + StateGraph subGraph = createProcessingSubGraph(); + + return new StateGraph(keyFactory) + .addNode("prepare", node_async(state -> { + return Map.of("data", "hello world"); + })) + // 将子图作为节点添加 + .addNode("process", subGraph) + .addNode("finalize", node_async(state -> { + String output = (String) state.value("output").orElse(""); + return Map.of("result", "Final: " + output); + })) + .addEdge(START, "prepare") + .addEdge("prepare", "process") + .addEdge("process", "finalize") + .addEdge("finalize", END); + } + + /** + * 在父图中集成子图 - 方式 2: 使用编译后的子图 + */ + public static StateGraph createParentGraphWithCompiledSubGraph() throws GraphStateException { + KeyStrategyFactory keyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("data", new ReplaceStrategy()); + strategies.put("output", new ReplaceStrategy()); + strategies.put("processed", new ReplaceStrategy()); + return strategies; + }; + + // 先编译子图 + CompiledGraph compiledSubGraph = createProcessingSubGraph().compile(); + + // 在父图中使用 + return new StateGraph(keyFactory) + .addNode("prepare", node_async(state -> { + return Map.of("data", "input"); + })) + .addNode("process", node_async(state -> { + // 手动调用子图 + Map subInput = Map.of( + "input", state.value("data").orElse("") + ); + OverAllState subResult = compiledSubGraph.invoke(subInput).orElseThrow(); + return Map.of("processed", subResult.value("output").orElse("")); + })) + .addEdge(START, "prepare") + .addEdge("prepare", "process") + .addEdge("process", END); + } + + public static void main(String[] args) throws GraphStateException { + System.out.println("=== 子图作为 StateGraph 示例 ==="); + StateGraph parentGraph1 = createParentGraphWithDirectEmbedding(); + StateGraph parentGraph2 = createParentGraphWithCompiledSubGraph(); + System.out.println("所有示例执行完成"); + } + + /** + * 状态隔离示例 + */ + public static class IsolatedSubGraphNode implements NodeAction { + private final CompiledGraph subGraph; + + public IsolatedSubGraphNode(StateGraph subGraphDef) throws GraphStateException { + this.subGraph = subGraphDef.compile(); + } + + @Override + public Map apply(OverAllState parentState) { + // 提取父状态数据 + String input = (String) parentState.value("input").orElse(""); + + // 创建子图独立状态 + Map subState = Map.of("subInput", input); + + // 执行子图 + Optional subResult = subGraph.invoke(subState); + + // 将子图结果映射回父状态 + String output = (String) subResult.get().value("subOutput").orElse(""); + return Map.of("output", output); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/SubgraphExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/SubgraphExample.java new file mode 100644 index 00000000..335960ad --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/SubgraphExample.java @@ -0,0 +1,242 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.GraphRepresentation; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.NodeOutput; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import reactor.core.publisher.Flux; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 子图示例 + * 演示如何在 Spring AI Alibaba Graph 中使用子图 + */ +public class SubgraphExample { + + /** + * 示例 1: 添加编译的子图作为节点 + */ + public static void addCompiledSubgraphAsNode() throws GraphStateException { + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("sharedData", new ReplaceStrategy()); + keyStrategyMap.put("results", new AppendStrategy()); + return keyStrategyMap; + }; + + // 创建子图 + var childNode1 = node_async(state -> { + String data = (String) state.value("sharedData").orElse(""); + return Map.of("results", List.of("Child processed: " + data)); + }); + + StateGraph childGraph = new StateGraph(keyStrategyFactory) + .addNode("child_node1", childNode1) + .addEdge(START, "child_node1") + .addEdge("child_node1", END); + + CompiledGraph compiledChild = childGraph.compile(); + + // 创建父图 + var parentNode1 = node_async(state -> { + return Map.of("sharedData", "Parent data"); + }); + + StateGraph parentGraph = new StateGraph(keyStrategyFactory) + .addNode("parent_node1", parentNode1) + .addNode("call_child", node_async(state -> { + return compiledChild.invoke(state.data(), + RunnableConfig.builder().build()) + .orElseThrow() + .data(); + })) + .addEdge(START, "parent_node1") + .addEdge("parent_node1", "call_child") + .addEdge("call_child", END); + + CompiledGraph compiledParent = parentGraph.compile(); + System.out.println("Compiled subgraph as node example created"); + } + + /** + * 示例 2: 在节点操作中调用子图 + */ + public static void callSubgraphInNodeAction() throws GraphStateException { + // 父图状态 + KeyStrategyFactory parentKeyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("parentData", new ReplaceStrategy()); + keyStrategyMap.put("processedResult", new ReplaceStrategy()); + return keyStrategyMap; + }; + + // 子图状态(完全不同) + KeyStrategyFactory childKeyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("childInput", new ReplaceStrategy()); + keyStrategyMap.put("childOutput", new ReplaceStrategy()); + return keyStrategyMap; + }; + + // 创建子图 + var childProcessor = node_async(state -> { + String input = (String) state.value("childInput").orElse(""); + String output = "Processed: " + input; + return Map.of("childOutput", output); + }); + + StateGraph childGraph = new StateGraph(childKeyStrategyFactory) + .addNode("processor", childProcessor) + .addEdge(START, "processor") + .addEdge("processor", END); + + CompiledGraph compiledChild = childGraph.compile(); + + // 父图中的转换节点 + var transformAndCallChild = node_async(state -> { + // 1. 从父状态提取数据 + String parentData = (String) state.value("parentData").orElse(""); + + // 2. 转换为子图输入 + Map childInput = Map.of("childInput", parentData); + + // 3. 调用子图 + OverAllState childResult = compiledChild.invoke( + childInput, + RunnableConfig.builder().build() + ).orElseThrow(); + + // 4. 转换子图输出回父状态 + String childOutput = (String) childResult.value("childOutput").orElse(""); + return Map.of("processedResult", childOutput); + }); + + // 创建父图 + StateGraph parentGraph = new StateGraph(parentKeyStrategyFactory) + .addNode("call_child_with_transform", transformAndCallChild) + .addEdge(START, "call_child_with_transform") + .addEdge("call_child_with_transform", END); + + CompiledGraph compiledParent = parentGraph.compile(); + System.out.println("Call subgraph in node action example created"); + } + + /** + * 示例 3: 可视化子图 + */ + public static void visualizeSubgraph() throws GraphStateException { + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("data", new ReplaceStrategy()); + return keyStrategyMap; + }; + + StateGraph stateGraph = new StateGraph(keyStrategyFactory) + .addNode("node1", node_async(state -> Map.of("data", "processed"))) + .addNode("node2", node_async(state -> Map.of("data", "finalized"))) + .addEdge(START, "node1") + .addEdge("node1", "node2") + .addEdge("node2", END); + + // 获取 PlantUML 表示 + GraphRepresentation representation = stateGraph.getGraph( + GraphRepresentation.Type.PLANTUML, + "My Graph" + ); + + System.out.println("PlantUML representation:"); + System.out.println(representation.content()); + } + + /** + * 示例 4: 流式处理子图 + */ + public static void streamSubgraph() throws GraphStateException { + KeyStrategyFactory keyStrategyFactory = () -> { + Map keyStrategyMap = new HashMap<>(); + keyStrategyMap.put("data", new ReplaceStrategy()); + return keyStrategyMap; + }; + + StateGraph childGraph = new StateGraph(keyStrategyFactory) + .addNode("process", node_async(state -> Map.of("data", "processed"))) + .addEdge(START, "process") + .addEdge("process", END); + + CompiledGraph compiledChild = childGraph.compile(); + + // 执行父图并获取流式输出 + Flux stream = compiledChild.stream( + Map.of("data", "input"), + RunnableConfig.builder().threadId("parent-thread").build() + ); + + // 处理流式输出 + stream.subscribe(output -> { + System.out.println("Subgraph output: " + output); + }); + } + + public static void main(String[] args) { + System.out.println("=== 子图示例 ===\n"); + + try { + // 示例 1: 添加编译的子图作为节点 + System.out.println("示例 1: 添加编译的子图作为节点"); + addCompiledSubgraphAsNode(); + System.out.println(); + + // 示例 2: 在节点操作中调用子图 + System.out.println("示例 2: 在节点操作中调用子图"); + callSubgraphInNodeAction(); + System.out.println(); + + // 示例 3: 可视化子图 + System.out.println("示例 3: 可视化子图"); + visualizeSubgraph(); + System.out.println(); + + // 示例 4: 流式处理子图 + System.out.println("示例 4: 流式处理子图"); + streamSubgraph(); + System.out.println(); + + System.out.println("所有示例执行完成"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/TimeTravelExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/TimeTravelExample.java new file mode 100644 index 00000000..766f6756 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/TimeTravelExample.java @@ -0,0 +1,278 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompileConfig; +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.StateSnapshot; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 时光旅行示例 + * 演示如何查看和恢复 Graph 执行的历史状态 + */ +public class TimeTravelExample { + + /** + * 配置 Checkpoint + */ + public static CompiledGraph configureCheckpoint(StateGraph stateGraph) throws GraphStateException { + // 创建 Checkpointer + var checkpointer = new MemorySaver(); + + // 配置持久化 + var compileConfig = CompileConfig.builder() + .saverConfig(SaverConfig.builder() + .register(checkpointer) + .build()) + .build(); + + return stateGraph.compile(compileConfig); + } + + /** + * 执行 Graph 并生成历史 + */ + public static void executeGraphAndGenerateHistory(CompiledGraph graph) { + // 配置线程 ID + var config = RunnableConfig.builder() + .threadId("conversation-1") + .build(); + + // 执行 Graph + Map input = Map.of("query", "Hello"); + graph.invoke(input, config); + + // 再次执行 + graph.invoke(Map.of("query", "Follow-up question"), config); + } + + /** + * 查看状态历史 + */ + public static void viewStateHistory(CompiledGraph graph) { + var config = RunnableConfig.builder() + .threadId("conversation-1") + .build(); + + // 获取所有历史状态 + List history = (List) graph.getStateHistory(config); + + System.out.println("State history:"); + for (int i = 0; i < history.size(); i++) { + StateSnapshot snapshot = history.get(i); + System.out.printf("Step %d: %s\n", i, snapshot.state()); + System.out.printf(" Checkpoint ID: %s\n", snapshot.config().checkPointId().orElse("N/A")); + System.out.printf(" Node: %s\n", snapshot.node()); + } + } + + /** + * 回溯到历史状态 + */ + public static void travelBackToHistory(CompiledGraph graph) { + var config = RunnableConfig.builder() + .threadId("conversation-1") + .build(); + + // 获取所有历史状态 + List history = (List) graph.getStateHistory(config); + + // 获取特定的历史状态 + StateSnapshot historicalSnapshot = history.get(1); + + // 使用历史状态的 checkpoint ID 创建新配置 + var historicalConfig = RunnableConfig.builder() + .threadId("conversation-1") + .checkPointId(historicalSnapshot.config().checkPointId().orElse(null)) + .build(); + + // 从历史状态继续执行 + graph.invoke( + Map.of("query", "New question from historical state"), + historicalConfig + ); + } + + /** + * 分支创建 + */ + public static void createBranch(CompiledGraph graph) { + var config = RunnableConfig.builder() + .threadId("conversation-1") + .build(); + + // 获取所有历史状态 + List history = (List) graph.getStateHistory(config); + + // 获取特定的历史状态 + StateSnapshot historicalSnapshot = history.get(1); + + // 从历史状态创建新分支 + var branchConfig = RunnableConfig.builder() + .threadId("conversation-1-branch") // 新的线程 ID + .checkPointId(historicalSnapshot.config().checkPointId().orElse(null)) + .build(); + + // 在新分支上执行 + graph.invoke( + Map.of("query", "Alternative path"), + branchConfig + ); + } + + /** + * 完整示例 + */ + public static void completeExample() throws GraphStateException { + // 构建 Graph + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("messages", new AppendStrategy()); + strategies.put("step", new ReplaceStrategy()); + return strategies; + }; + + StateGraph builder = new StateGraph(keyStrategyFactory) + .addNode("step1", node_async(state -> + Map.of("messages", "Step 1", "step", 1))) + .addNode("step2", node_async(state -> + Map.of("messages", "Step 2", "step", 2))) + .addNode("step3", node_async(state -> + Map.of("messages", "Step 3", "step", 3))) + .addEdge(START, "step1") + .addEdge("step1", "step2") + .addEdge("step2", "step3") + .addEdge("step3", END); + + // 配置持久化 + var checkpointer = new MemorySaver(); + var compileConfig = CompileConfig.builder() + .saverConfig(SaverConfig.builder() + .register(checkpointer) + .build()) + .build(); + + CompiledGraph graph = builder.compile(compileConfig); + + // 执行 + var config = RunnableConfig.builder() + .threadId("demo") + .build(); + + graph.invoke(Map.of(), config); + + // 查看历史 + List history = (List) graph.getStateHistory(config); + history.forEach(snapshot -> { + System.out.println("State: " + snapshot.state()); + System.out.println("Node: " + snapshot.node()); + System.out.println("---"); + }); + + // 回溯到 step1 + StateSnapshot step1Snapshot = history.stream() + .filter(s -> "step1".equals(s.node())) + .findFirst() + .orElseThrow(); + + var replayConfig = RunnableConfig.builder() + .threadId("demo") + .checkPointId(step1Snapshot.config().checkPointId().orElse(null)) + .build(); + + // 从 step1 重新执行 + graph.invoke(Map.of(), replayConfig); + } + + public static void main(String[] args) { + System.out.println("=== 时光旅行示例 ===\n"); + + try { + // 示例 1: 配置 Checkpoint + System.out.println("示例 1: 配置 Checkpoint"); + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("messages", new AppendStrategy()); + strategies.put("step", new ReplaceStrategy()); + return strategies; + }; + + StateGraph builder = new StateGraph(keyStrategyFactory) + .addNode("step1", node_async(state -> Map.of("messages", "Step 1", "step", 1))) + .addNode("step2", node_async(state -> Map.of("messages", "Step 2", "step", 2))) + .addNode("step3", node_async(state -> Map.of("messages", "Step 3", "step", 3))) + .addEdge(START, "step1") + .addEdge("step1", "step2") + .addEdge("step2", "step3") + .addEdge("step3", END); + + CompiledGraph graph = configureCheckpoint(builder); + System.out.println("Checkpoint 配置完成"); + System.out.println(); + + // 示例 2: 执行 Graph 并生成历史 + System.out.println("示例 2: 执行 Graph 并生成历史"); + executeGraphAndGenerateHistory(graph); + System.out.println(); + + // 示例 3: 查看状态历史 + System.out.println("示例 3: 查看状态历史"); + viewStateHistory(graph); + System.out.println(); + + // 示例 4: 回溯到历史状态 + System.out.println("示例 4: 回溯到历史状态"); + System.out.println("注意: 此示例需要有效的历史状态,跳过执行"); + // travelBackToHistory(graph); + System.out.println(); + + // 示例 5: 分支创建 + System.out.println("示例 5: 分支创建"); + System.out.println("注意: 此示例需要有效的历史状态,跳过执行"); + // createBranch(graph); + System.out.println(); + + // 示例 6: 完整示例 + System.out.println("示例 6: 完整示例"); + completeExample(); + System.out.println(); + + System.out.println("所有示例执行完成"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/TimeTravelRedisExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/TimeTravelRedisExample.java new file mode 100644 index 00000000..8ab13fd0 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/TimeTravelRedisExample.java @@ -0,0 +1,308 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompileConfig; +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; +import com.alibaba.cloud.ai.graph.checkpoint.savers.redis.RedisSaver; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.StateSnapshot; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * Redis 时光旅行示例 + * 演示如何使用 Redis 持久化查看和恢复 Graph 执行的历史状态 + */ +public class TimeTravelRedisExample { + + /** + * 配置 Checkpoint + */ + public static CompiledGraph configureCheckpoint(StateGraph stateGraph, RedissonClient redisson) throws GraphStateException { + // 创建 Checkpointer + var checkpointer = RedisSaver.builder().redisson(redisson).build(); + + // 配置持久化 + var compileConfig = CompileConfig.builder() + .saverConfig(SaverConfig.builder() + .register(checkpointer) + .build()) + .build(); + + return stateGraph.compile(compileConfig); + } + + /** + * 执行 Graph 并生成历史 + */ + public static void executeGraphAndGenerateHistory(CompiledGraph graph) { + // 配置线程 ID + var config = RunnableConfig.builder() + .threadId("conversation-redis-1") + .build(); + + // 执行 Graph + Map input = Map.of("query", "Hello"); + graph.invoke(input, config); + + // 再次执行 + graph.invoke(Map.of("query", "Follow-up question"), config); + } + + /** + * 查看状态历史 + */ + public static void viewStateHistory(CompiledGraph graph) { + var config = RunnableConfig.builder() + .threadId("conversation-redis-1") + .build(); + + // 获取所有历史状态 + List history = (List) graph.getStateHistory(config); + + System.out.println("State history:"); + for (int i = 0; i < history.size(); i++) { + StateSnapshot snapshot = history.get(i); + System.out.printf("Step %d: %s\n", i, snapshot.state()); + System.out.printf(" Checkpoint ID: %s\n", snapshot.config().checkPointId().orElse("N/A")); + System.out.printf(" Node: %s\n", snapshot.node()); + } + } + + /** + * 回溯到历史状态 + */ + public static void travelBackToHistory(CompiledGraph graph) { + var config = RunnableConfig.builder() + .threadId("conversation-redis-1") + .build(); + + // 获取所有历史状态 + List history = (List) graph.getStateHistory(config); + + if (history.size() < 2) { + System.out.println("历史记录不足,无法回溯"); + return; + } + + // 获取特定的历史状态 (例如第二个状态) + StateSnapshot historicalSnapshot = history.get(1); + + // 使用历史状态的 checkpoint ID 创建新配置 + var historicalConfig = RunnableConfig.builder() + .threadId("conversation-redis-1") + .checkPointId(historicalSnapshot.config().checkPointId().orElse(null)) + .build(); + + // 从历史状态继续执行 + graph.invoke( + Map.of("query", "New question from historical state"), + historicalConfig + ); + } + + /** + * 分支创建 + */ + public static void createBranch(CompiledGraph graph) { + var config = RunnableConfig.builder() + .threadId("conversation-redis-1") + .build(); + + // 获取所有历史状态 + List history = (List) graph.getStateHistory(config); + + if (history.size() < 2) { + System.out.println("历史记录不足,无法创建分支"); + return; + } + + // 获取特定的历史状态 + StateSnapshot historicalSnapshot = history.get(1); + + // 从历史状态创建新分支 + var branchConfig = RunnableConfig.builder() + .threadId("conversation-redis-1-branch") // 新的线程 ID + .checkPointId(historicalSnapshot.config().checkPointId().orElse(null)) + .build(); + + // 在新分支上执行 + graph.invoke( + Map.of("query", "Alternative path"), + branchConfig + ); + } + + /** + * 完整示例 + */ + public static void completeExample(RedissonClient redisson) throws Exception { + // 构建 Graph + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("messages", new AppendStrategy()); + strategies.put("step", new ReplaceStrategy()); + return strategies; + }; + + StateGraph builder = new StateGraph(keyStrategyFactory) + .addNode("step1", node_async(state -> + Map.of("messages", "Step 1", "step", 1))) + .addNode("step2", node_async(state -> + Map.of("messages", "Step 2", "step", 2))) + .addNode("step3", node_async(state -> + Map.of("messages", "Step 3", "step", 3))) + .addEdge(START, "step1") + .addEdge("step1", "step2") + .addEdge("step2", "step3") + .addEdge("step3", END); + + // 配置持久化 + var checkpointer = RedisSaver.builder().redisson(redisson).build(); + var compileConfig = CompileConfig.builder() + .saverConfig(SaverConfig.builder() + .register(checkpointer) + .build()) + .build(); + + CompiledGraph graph = builder.compile(compileConfig); + + // 执行 + var config = RunnableConfig.builder() + .threadId("demo-redis") + .build(); + + // 清理之前的状态(如果存在) + checkpointer.release(config); + + graph.invoke(Map.of(), config); + + // 查看历史 + List history = (List) graph.getStateHistory(config); + history.forEach(snapshot -> { + System.out.println("State: " + snapshot.state()); + System.out.println("Node: " + snapshot.node()); + System.out.println("---"); + }); + + // 回溯到 step1 + StateSnapshot step1Snapshot = history.stream() + .filter(s -> "step1".equals(s.node())) + .findFirst() + .orElseThrow(); + + var replayConfig = RunnableConfig.builder() + .threadId("demo-redis") + .checkPointId(step1Snapshot.config().checkPointId().orElse(null)) + .build(); + + // 从 step1 重新执行 + graph.invoke(Map.of(), replayConfig); + } + + public static void main(String[] args) { + System.out.println("=== Redis 时光旅行示例 ===\n"); + + // 初始化 Redis 客户端 + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://localhost:6379"); + RedissonClient redisson = Redisson.create(config); + + try { + // 示例 1: 配置 Checkpoint + System.out.println("示例 1: 配置 Checkpoint"); + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap strategies = new HashMap<>(); + strategies.put("messages", new AppendStrategy()); + strategies.put("step", new ReplaceStrategy()); + return strategies; + }; + + StateGraph builder = new StateGraph(keyStrategyFactory) + .addNode("step1", node_async(state -> Map.of("messages", "Step 1", "step", 1))) + .addNode("step2", node_async(state -> Map.of("messages", "Step 2", "step", 2))) + .addNode("step3", node_async(state -> Map.of("messages", "Step 3", "step", 3))) + .addEdge(START, "step1") + .addEdge("step1", "step2") + .addEdge("step2", "step3") + .addEdge("step3", END); + + CompiledGraph graph = configureCheckpoint(builder, redisson); + + // 清理旧数据 + RunnableConfig cleanConfig = RunnableConfig.builder().threadId("conversation-redis-1").build(); + RedisSaver.builder().redisson(redisson).build().release(cleanConfig); + RunnableConfig cleanBranchConfig = RunnableConfig.builder().threadId("conversation-redis-1-branch").build(); + RedisSaver.builder().redisson(redisson).build().release(cleanBranchConfig); + + System.out.println("Checkpoint 配置完成"); + System.out.println(); + + // 示例 2: 执行 Graph 并生成历史 + System.out.println("示例 2: 执行 Graph 并生成历史"); + executeGraphAndGenerateHistory(graph); + System.out.println(); + + // 示例 3: 查看状态历史 + System.out.println("示例 3: 查看状态历史"); + viewStateHistory(graph); + System.out.println(); + + // 示例 4: 回溯到历史状态 + System.out.println("示例 4: 回溯到历史状态"); + travelBackToHistory(graph); + System.out.println(); + + // 示例 5: 分支创建 + System.out.println("示例 5: 分支创建"); + createBranch(graph); + System.out.println(); + + // 示例 6: 完整示例 + System.out.println("示例 6: 完整示例"); + completeExample(redisson); + System.out.println(); + + System.out.println("所有示例执行完成"); + System.out.println("提示: 请配置 Redis 连接后运行完整示例"); + System.out.println("提示: 需要添加 Redisson 依赖: org.redisson:redisson"); + } + catch (Exception e) { + System.err.println("执行示例时出错: " + e.getMessage()); + e.printStackTrace(); + } finally { + redisson.shutdown(); + } + } +} diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/WaitUserInputExample.java b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/WaitUserInputExample.java new file mode 100644 index 00000000..c88ec015 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/graph/examples/WaitUserInputExample.java @@ -0,0 +1,166 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.examples.documentation.graph.examples; + +import com.alibaba.cloud.ai.graph.CompileConfig; +import com.alibaba.cloud.ai.graph.CompiledGraph; +import com.alibaba.cloud.ai.graph.KeyStrategy; +import com.alibaba.cloud.ai.graph.KeyStrategyFactory; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.StateGraph; +import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.exception.GraphStateException; +import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy; +import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; + +import java.util.HashMap; +import java.util.Map; + +import static com.alibaba.cloud.ai.graph.StateGraph.END; +import static com.alibaba.cloud.ai.graph.StateGraph.START; +import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async; +import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async; + +/** + * 等待用户输入示例 + * 演示如何实现等待用户输入的交互式工作流 + */ +public class WaitUserInputExample { + + /** + * 定义带中断的 Graph + */ + public static CompiledGraph createGraphWithInterrupt() throws GraphStateException { + // 定义节点 + var step1 = node_async(state -> { + return Map.of("messages", "Step 1"); + }); + + var humanFeedback = node_async(state -> { + return Map.of(); // 等待用户输入,不修改状态 + }); + + var step3 = node_async(state -> { + return Map.of("messages", "Step 3"); + }); + + // 定义条件边 + var evalHumanFeedback = edge_async(state -> { + var feedback = (String) state.value("human_feedback").orElse("unknown"); + return (feedback.equals("next") || feedback.equals("back")) ? feedback : "unknown"; + }); + + // 配置 KeyStrategyFactory + KeyStrategyFactory keyStrategyFactory = () -> { + HashMap keyStrategyHashMap = new HashMap<>(); + keyStrategyHashMap.put("messages", new AppendStrategy()); + keyStrategyHashMap.put("human_feedback", new ReplaceStrategy()); + return keyStrategyHashMap; + }; + + // 构建 Graph + StateGraph builder = new StateGraph(keyStrategyFactory) + .addNode("step_1", step1) + .addNode("human_feedback", humanFeedback) + .addNode("step_3", step3) + .addEdge(START, "step_1") + .addEdge("step_1", "human_feedback") + .addConditionalEdges("human_feedback", evalHumanFeedback, + Map.of("back", "step_1", "next", "step_3", "unknown", "human_feedback")) + .addEdge("step_3", END); + + // 配置内存保存器和中断点 + var saver = new MemorySaver(); + + var compileConfig = CompileConfig.builder() + .saverConfig(SaverConfig.builder() + .register(saver) + .build()) + .interruptBefore("human_feedback") // 在 human_feedback 节点前中断 + .build(); + + return builder.compile(compileConfig); + } + + /** + * 执行 Graph 直到中断 + */ + public static void executeUntilInterrupt(CompiledGraph graph) { + // 初始输入 + Map initialInput = Map.of("messages", "Step 0"); + + // 配置线程 ID + var invokeConfig = RunnableConfig.builder() + .threadId("Thread1") + .build(); + + // 运行 Graph 直到第一个中断点 + graph.stream(initialInput, invokeConfig) + .doOnNext(event -> System.out.println(event)) + .doOnError(error -> System.err.println("流错误: " + error.getMessage())) + .doOnComplete(() -> System.out.println("流完成")) + .blockLast(); + } + + /** + * 等待用户输入并更新状态 + */ + public static void waitUserInputAndUpdateState(CompiledGraph graph) throws Exception { + var invokeConfig = RunnableConfig.builder() + .threadId("Thread1") + .build(); + + // 检查当前状态 + System.out.printf("--State before update--\n%s\n", graph.getState(invokeConfig)); + + // 模拟用户输入 + var userInput = "back"; // "back" 表示返回上一个节点 + System.out.printf("\n--User Input--\n用户选择: '%s'\n\n", userInput); + + // 更新状态(模拟 human_feedback 节点的输出) + var updateConfig = graph.updateState(invokeConfig, Map.of("human_feedback", userInput), null); + + // 检查更新后的状态 + System.out.printf("--State after update--\n%s\n", graph.getState(invokeConfig)); + } + + /** + * 继续执行 Graph + */ + public static void continueExecution(CompiledGraph graph) { + var invokeConfig = RunnableConfig.builder() + .threadId("Thread1") + .build(); + + // 继续执行 Graph + graph.stream(null, invokeConfig) + .doOnNext(event -> System.out.println(event)) + .doOnError(error -> System.err.println("流错误: " + error.getMessage())) + .doOnComplete(() -> System.out.println("流完成")) + .blockLast(); + } + + public static void main(String[] args) throws Exception { + System.out.println("=== 等待用户输入示例 ==="); + CompiledGraph graph = createGraphWithInterrupt(); + executeUntilInterrupt(graph); + waitUserInputAndUpdateState(graph); + continueExecution(graph); + System.out.println("所有示例执行完成"); + } +} + diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/resources/application.yml b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/resources/application.yml new file mode 100644 index 00000000..d295a990 --- /dev/null +++ b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/resources/application.yml @@ -0,0 +1,42 @@ +server: + port: 8080 + +spring: + application: + name: documentation-examples + ai: + # DashScope 配置(阿里云百炼) + dashscope: + api-key: ${DASHSCOPE_API_KEY:your-api-key} + chat: + options: + model: ${DASHSCOPE_MODEL:qwen-plus} + # A2A Nacos 配置 + alibaba: + a2a: + nacos: + server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848} + username: ${NACOS_USERNAME:nacos} + password: ${NACOS_PASSWORD:nacos} + discovery: + enabled: true # 启用 A2A 服务发现,用于发现远程智能体 + registry: + enabled: true # 启用 Nacos Registry,将本地 Agent 注册到 Nacos + server: + version: 1.0.0 + card: + name: data_analysis_agent + description: 专门用于数据分析和统计计算的本地智能体 + provider: + name: Spring AI Alibaba Documentation + organization: Spring AI Alibaba + url: https://sca.aliyun.com/ai/ + contact: + email: dev@alibabacloud.com + +# 日志配置 +logging: + level: + root: INFO + com.alibaba.cloud.ai: DEBUG + org.springframework.ai: DEBUG diff --git a/project/spingai/spring-ai-alibaba-examples/documentation/src/main/resources/images/photo.jpg b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/resources/images/photo.jpg new file mode 100644 index 00000000..504929f7 Binary files /dev/null and b/project/spingai/spring-ai-alibaba-examples/documentation/src/main/resources/images/photo.jpg differ diff --git a/project/spring-boot-web-01/.gitattributes b/project/spring-boot-web-01/.gitattributes new file mode 100644 index 00000000..3b41682a --- /dev/null +++ b/project/spring-boot-web-01/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/spring-cloud-2/api-gateway2/.gitignore b/project/spring-boot-web-01/.gitignore similarity index 62% rename from spring-cloud-2/api-gateway2/.gitignore rename to project/spring-boot-web-01/.gitignore index 4a453031..667aaef0 100644 --- a/spring-cloud-2/api-gateway2/.gitignore +++ b/project/spring-boot-web-01/.gitignore @@ -1,5 +1,8 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ ### STS ### .apt_generated @@ -22,7 +25,9 @@ /dist/ /nbdist/ /.nb-gradle/ -/build/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ ### VS Code ### .vscode/ diff --git a/spring-cloud-learn/eureka-client/pom.xml b/project/spring-boot-web-01/pom.xml similarity index 51% rename from spring-cloud-learn/eureka-client/pom.xml rename to project/spring-boot-web-01/pom.xml index 35336c43..0ad71725 100644 --- a/spring-cloud-learn/eureka-client/pom.xml +++ b/project/spring-boot-web-01/pom.xml @@ -1,59 +1,58 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - com.cpq - eureka-client - 0.0.1-SNAPSHOT - jar - - eureka-client - Demo project for Spring Boot eureka-client - org.springframework.boot spring-boot-starter-parent - 1.5.13.RELEASE + 3.5.5 - + org.cpq + spring-boot-web-01 + 0.0.1-SNAPSHOT + spring-boot-web-01 + spring-boot-web-01 + + + + + + + + + + + + + - UTF-8 - UTF-8 - 1.8 + 21 - - org.springframework.cloud - spring-cloud-starter-eureka + org.springframework.boot + spring-boot-starter-actuator org.springframework.boot spring-boot-starter-web + org.springframework.boot - spring-boot-starter-actuator + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-starter-test + test - - - - org.springframework.cloud - spring-cloud-dependencies - Edgware.SR4 - pom - import - - - - - - eureka-client org.springframework.boot @@ -62,5 +61,4 @@ - diff --git a/project/spring-boot-web-01/src/main/java/org/cpq/springbootweb01/SpringBootWeb01Application.java b/project/spring-boot-web-01/src/main/java/org/cpq/springbootweb01/SpringBootWeb01Application.java new file mode 100644 index 00000000..5667386a --- /dev/null +++ b/project/spring-boot-web-01/src/main/java/org/cpq/springbootweb01/SpringBootWeb01Application.java @@ -0,0 +1,13 @@ +package org.cpq.springbootweb01; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootWeb01Application { + + public static void main(String[] args) { + SpringApplication.run(SpringBootWeb01Application.class, args); + } + +} diff --git a/project/spring-boot-web-01/src/main/java/org/cpq/springbootweb01/completable/CompletableController.java b/project/spring-boot-web-01/src/main/java/org/cpq/springbootweb01/completable/CompletableController.java new file mode 100644 index 00000000..f4d5be55 --- /dev/null +++ b/project/spring-boot-web-01/src/main/java/org/cpq/springbootweb01/completable/CompletableController.java @@ -0,0 +1,73 @@ +package org.cpq.springbootweb01.completable; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +@RequestMapping("/c1") +@RestController +public class CompletableController { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private static final ConcurrentHashMap>> responseMap = new ConcurrentHashMap<>(); + + private static final AtomicLong idGenerator = new AtomicLong(123); // 模拟ID生成 + + @PostMapping("/send") + public Map send(@RequestBody Map body) throws Exception { + System.out.println("发送请求: "); + Map response = sendAndWayResp(body); + System.out.println("收到响应: " + response); + // 继续后续逻辑 + System.out.println("执行后续代码..."); + return response; + } + + @PostMapping("/resp") + public String resp(@RequestBody Map body) throws Exception { + complete(body); + return ""; + } + + private static Map sendAndWayResp(Map param) { + long messageId = idGenerator.getAndIncrement(); + // 将请求存入Map并启动发送线程 + responseMap.put(messageId, new CompletableFuture<>()); + + param.put("messageId", messageId); + System.out.println("发送请求,param: " + param); + + CompletableFuture> future = responseMap.get(messageId); + try { + return future.get(30, TimeUnit.SECONDS); + } catch (Exception ex) { + ex.printStackTrace(); + } + return new HashMap<>(); + } + + private static Map complete(Map body) { + String messageIdStr = body.get("messageId").toString(); + final Long messageId = Long.parseLong(messageIdStr); + responseMap.computeIfPresent(messageId, (id, future) -> { + Object data = body.get("data"); + try { + String content = objectMapper.writeValueAsString(data); + Map map = objectMapper.readValue(content, Map.class); + future.complete(map); + } catch (Exception ex) { + ex.printStackTrace(); + } + return null; // 移除已处理的条目 + }); + return new HashMap<>(); + } + +} diff --git a/project/spring-boot-web-01/src/main/resources/application.properties b/project/spring-boot-web-01/src/main/resources/application.properties new file mode 100644 index 00000000..0af799e5 --- /dev/null +++ b/project/spring-boot-web-01/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=spring-boot-web-01 diff --git a/project/spring-boot-web-01/src/test/java/org/cpq/springbootweb01/SpringBootWeb01ApplicationTests.java b/project/spring-boot-web-01/src/test/java/org/cpq/springbootweb01/SpringBootWeb01ApplicationTests.java new file mode 100644 index 00000000..c00c0cfe --- /dev/null +++ b/project/spring-boot-web-01/src/test/java/org/cpq/springbootweb01/SpringBootWeb01ApplicationTests.java @@ -0,0 +1,13 @@ +package org.cpq.springbootweb01; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SpringBootWeb01ApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git "a/project/\344\273\243\347\220\206/git\344\273\243\347\220\206.txt" "b/project/\344\273\243\347\220\206/git\344\273\243\347\220\206.txt" index cbf8b1c8..65af33c5 100644 --- "a/project/\344\273\243\347\220\206/git\344\273\243\347\220\206.txt" +++ "b/project/\344\273\243\347\220\206/git\344\273\243\347\220\206.txt" @@ -1,20 +1,22 @@ # 以下使用http代理 -git config http.proxy http://127.0.0.1:1643 -git config https.proxy https://127.0.0.1:1643 +git config http.proxy http://127.0.0.1:1865 + +git config https.proxy https://127.0.0.1:1865 # 以下使用socks5代理 -git config http.proxy socks5://127.0.0.1:14784 -git config https.proxy socks5://127.0.0.1:14784 +git config http.proxy socks5://127.0.0.1:14785 + +git config https.proxy socks5://127.0.0.1:14785 # 取消全局代理 git config --global --unset http.proxy + git config --global --unset https.proxy # 取消代理 git config --unset http.proxy -git config --unset https.proxy - +git config --unset https.proxy diff --git a/spring-cloud-2/api-gateway2/pom.xml b/spring-cloud-2/api-gateway2/pom.xml deleted file mode 100644 index 8dd04a07..00000000 --- a/spring-cloud-2/api-gateway2/pom.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.0.6.RELEASE - - - com.example - api-gateway2 - 0.0.1-SNAPSHOT - api-gateway2 - Demo project for Spring Boot - - - 1.8 - - - - - - org.springframework.cloud - spring-cloud-dependencies - Finchley.SR3 - pom - import - - - - - - - - org.springframework.cloud - spring-cloud-starter-gateway - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - - org.springframework.cloud - spring-cloud-starter-netflix-hystrix - - - - - org.springframework.cloud - spring-cloud-starter-sleuth - - - org.springframework.cloud - spring-cloud-starter-zipkin - - - - - - api-gateway2 - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-cloud-2/api-gateway2/src/main/java/com/cpq/apigateway/ApiGatewayApplication.java b/spring-cloud-2/api-gateway2/src/main/java/com/cpq/apigateway/ApiGatewayApplication.java deleted file mode 100644 index 585541b1..00000000 --- a/spring-cloud-2/api-gateway2/src/main/java/com/cpq/apigateway/ApiGatewayApplication.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.cpq.apigateway; - -import org.springframework.boot.SpringApplication; -import org.springframework.cloud.client.SpringCloudApplication; - -@SpringCloudApplication -public class ApiGatewayApplication { - - public static void main(String[] args) { - SpringApplication.run(ApiGatewayApplication.class, args); - } - - -} diff --git a/spring-cloud-2/api-gateway2/src/main/java/com/cpq/apigateway/AuthFilter.java b/spring-cloud-2/api-gateway2/src/main/java/com/cpq/apigateway/AuthFilter.java deleted file mode 100644 index 89a0f294..00000000 --- a/spring-cloud-2/api-gateway2/src/main/java/com/cpq/apigateway/AuthFilter.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.cpq.apigateway; - -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; - -/** - * @Description - * @Author chenpiqian - * @Date: 2019-03-27 - */ -@Component -public class AuthFilter implements GlobalFilter { - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - System.out.println("进入AuthFilter"); - return chain.filter(exchange); - - //boolean isAllow = true; - //if (isAllow) { - // return chain.filter(exchange); - // - //} else { - // //设置status和body - // return Mono.defer(() -> { - // //setResponseStatus(exchange, HttpStatus.UNAUTHORIZED); - // final ServerHttpResponse response = exchange.getResponse(); - // byte[] bytes = "Hello World".getBytes(StandardCharsets.UTF_8); - // DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes); - // response.getHeaders().set("aaa", "bbb"); - // return response.writeWith(Flux.just(buffer)); - // }); - //} - - } -} \ No newline at end of file diff --git a/spring-cloud-2/api-gateway2/src/main/resources/application.yml b/spring-cloud-2/api-gateway2/src/main/resources/application.yml deleted file mode 100644 index 26a9e1ab..00000000 --- a/spring-cloud-2/api-gateway2/src/main/resources/application.yml +++ /dev/null @@ -1,59 +0,0 @@ -server: - port: 10005 - -spring: - application: - name: api-gateway - cloud: - - gateway: - discovery: - locator: - enabled: true - routes: - - id: TO_APP01 - uri: lb://app001 - predicates: - - Path=/app001/** - filters: - - StripPrefix=1 - - id: TO_APP02 - uri: lb://app002 - predicates: - - Path=/app002/** - filters: - - StripPrefix=1 - sleuth: - web: - client: - enabled: true - sampler: - # 将采样比例设置为 1.0,也就是全部都需要。默认是 0.1 - probability: 1.0 - zipkin: - # 指定了 Zipkin 服务器的地址 - base-url: http://localhost:9411/ - -eureka: - instance: - prefer-ip-address: true - client: - service-url: - instance-id: ${spring.application.name}:${server.port} -# defaultZone: http://localhost:10001/eureka/, http://localhost:10002/eureka/, http://localhost:10003/eureka/ - defaultZone: http://localhost:10001/eureka/ - -# 启动熔断器,设置总超时、服务调用超时时间,超时则熔断 -feign: - hystrix: - enabled: true -hystrix: - command: - default: - execution: - isolation: - thread: - timeoutInMilliseconds: 32000 -ribbon: - ConnectTimeout: 2000 - ReadTimeout: 30000 diff --git a/spring-cloud-2/app001/pom.xml b/spring-cloud-2/app001/pom.xml deleted file mode 100644 index 52d3dd6a..00000000 --- a/spring-cloud-2/app001/pom.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.0.6.RELEASE - - - com.cpq - app001 - 0.0.1-SNAPSHOT - app001 - Demo project for Spring Boot - - - 1.8 - - - - - - org.springframework.cloud - spring-cloud-dependencies - Finchley.SR3 - pom - import - - - - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - - org.springframework.cloud - spring-cloud-starter-netflix-hystrix - - - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - - - org.springframework.cloud - spring-cloud-starter-sleuth - - - org.springframework.cloud - spring-cloud-starter-zipkin - - - - - - - - - - - - app001 - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-cloud-2/app001/src/main/java/com/cpq/app001/App001Application.java b/spring-cloud-2/app001/src/main/java/com/cpq/app001/App001Application.java deleted file mode 100644 index f9325280..00000000 --- a/spring-cloud-2/app001/src/main/java/com/cpq/app001/App001Application.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.cpq.app001; - -import org.springframework.boot.SpringApplication; -import org.springframework.cloud.client.SpringCloudApplication; -import org.springframework.cloud.openfeign.EnableFeignClients; - -@SpringCloudApplication -@EnableFeignClients -public class App001Application { - - public static void main(String[] args) { - SpringApplication.run(App001Application.class, args); - } - -} diff --git a/spring-cloud-2/app001/src/main/java/com/cpq/app001/App002Feign.java b/spring-cloud-2/app001/src/main/java/com/cpq/app001/App002Feign.java deleted file mode 100644 index 2aa19ed4..00000000 --- a/spring-cloud-2/app001/src/main/java/com/cpq/app001/App002Feign.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.cpq.app001; - -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; - -import java.util.Map; - -/** - * @Description - * @Author chenpiqian - * @Date: 2019-06-13 - */ - -//@FeignClient与@RequestMapping不能同时存在接口上,spring官方似乎不愿修复这坑爹问题 -@FeignClient(name = "app002", fallback = App002FeignHystrix.class ) -public interface App002Feign { - - @GetMapping(value = "/test02/get/{id}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) - String test02GetId(@PathVariable("id") Integer id); - - @PostMapping(value = "/test02/post", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) - Map test02Post(@RequestBody Map map); - - @PostMapping(value = "/test02/timeout1", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) - Map timeout1(@RequestBody Map map); - -} diff --git a/spring-cloud-2/app001/src/main/java/com/cpq/app001/App002FeignHystrix.java b/spring-cloud-2/app001/src/main/java/com/cpq/app001/App002FeignHystrix.java deleted file mode 100644 index 692d4304..00000000 --- a/spring-cloud-2/app001/src/main/java/com/cpq/app001/App002FeignHystrix.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.cpq.app001; - -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * @Description - * @Author chenpiqian - * @Date: 2019-06-13 - */ -@Component -public class App002FeignHystrix implements App002Feign { - - @Override - public String test02GetId(Integer id) { - return "降级test02Get"+id; - } - - @Override - public Map test02Post(Map map){ - map.put("熔断-降级", "test02Post"); - return map; - } - - @Override - public Map timeout1(Map map) { - map.put("熔断-降级", "timeout1"); - return map; - } -} diff --git a/spring-cloud-2/app001/src/main/java/com/cpq/app001/Test01Ctrl.java b/spring-cloud-2/app001/src/main/java/com/cpq/app001/Test01Ctrl.java deleted file mode 100644 index 96b2c3cd..00000000 --- a/spring-cloud-2/app001/src/main/java/com/cpq/app001/Test01Ctrl.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.cpq.app001; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -import java.util.Map; -import java.util.concurrent.TimeUnit; - -/** - * @Description - * @Author chenpiqian - * @Date: 2019-07-03 - */ - -@RestController -@RequestMapping("/test01") -public class Test01Ctrl { - - @Autowired - App002Feign app02Feign; - - - @GetMapping("/get") - public String test01Get(){ - System.out.println("/test01/get"); - return "test01/get"; - } - - @GetMapping("/get/timeout") - public String gettimeout() throws Exception{ - System.out.println("/test01/get"); - TimeUnit.MINUTES.sleep(60L); - return "test01/get"; - } - - @PostMapping("/post") - public Object test01Post(@RequestBody Map map){ - System.out.println(map.toString()); - map.put("app", "app001"); - return map; - } - - - - @PostMapping("/test02/post") - public Object test0102Post(@RequestBody Map map){ - System.out.println(map.toString()); - map.put("feign", "feign"); - return app02Feign.test02Post(map); - } - - @GetMapping("/test02/get/{id}") - public String test01Get(@PathVariable("id") Integer id){ - return app02Feign.test02GetId(id); - } - - @PostMapping("/test02/timeout1") - public Map timeout1(@RequestBody Map map){ - return app02Feign.timeout1(map); - } - -} diff --git a/spring-cloud-2/app001/src/main/java/com/cpq/app001/dashboard/HystrixServletDefinitions.java b/spring-cloud-2/app001/src/main/java/com/cpq/app001/dashboard/HystrixServletDefinitions.java deleted file mode 100644 index 8aa168ef..00000000 --- a/spring-cloud-2/app001/src/main/java/com/cpq/app001/dashboard/HystrixServletDefinitions.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.cpq.app001.dashboard; - -import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet; -import org.springframework.boot.web.servlet.ServletRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Description - * @Author chenpiqian - * @Date: 2019-07-03 - */ -@Configuration -public class HystrixServletDefinitions { - -/** - https://windmt.com/2018/04/16/spring-cloud-5-hystrix-dashboard/ - 配置本工程能被dashboard监控到,配置management.endpoints.web.exposure.include无效 - 需要添加这个bean - url:http://localhost:10011/hystrix.stream - */ - - @Bean(name = "hystrixRegistrationBean") - public ServletRegistrationBean servletRegistrationBean() { - ServletRegistrationBean registration = new ServletRegistrationBean( - new HystrixMetricsStreamServlet(), "/hystrix.stream"); - registration.setName("hystrixServlet"); - registration.setLoadOnStartup(1); - return registration; - } - -} diff --git a/spring-cloud-2/app001/src/main/resources/application-two.properties b/spring-cloud-2/app001/src/main/resources/application-two.properties deleted file mode 100644 index 13f19959..00000000 --- a/spring-cloud-2/app001/src/main/resources/application-two.properties +++ /dev/null @@ -1 +0,0 @@ -server.port=10021 diff --git a/spring-cloud-2/app001/src/main/resources/application.properties b/spring-cloud-2/app001/src/main/resources/application.properties deleted file mode 100644 index 7648d584..00000000 --- a/spring-cloud-2/app001/src/main/resources/application.properties +++ /dev/null @@ -1,20 +0,0 @@ -spring.application.name=app001 -server.port=10011 - -#\u4F7F\u7528ip\u66FF\u4EE3\u5B9E\u4F8B\u540D -eureka.instance.prefer-ip-address=true - -#\u8BBE\u7F6E\u5B9E\u4F8B\u7684ID\u4E3A\uFF1A \u670D\u52A1\u540D:port -eureka.client.instance-id=${spring.application.name}:${server.port} -#eureka.client.serviceUrl.defaultZone=http://localhost:10001/eureka/, http://localhost:10002/eureka/, http://localhost:10003/eureka/ -eureka.client.serviceUrl.defaultZone=http://localhost:10001/eureka/ - -# \u5F00\u542Fhystrix -feign.hystrix.enabled=true - -#zipkin\u914D\u7F6E -spring.application.sleuth.web.client.enabled=true -# \u5C06\u91C7\u6837\u6BD4\u4F8B\u8BBE\u7F6E\u4E3A 1.0\uFF0C\u4E5F\u5C31\u662F\u5168\u90E8\u90FD\u9700\u8981\u3002\u9ED8\u8BA4\u662F 0.1 -spring.application.sleuth.sampler.probability=1.0 -# \u6307\u5B9A\u4E86 Zipkin \u670D\u52A1\u5668\u7684\u5730\u5740 -spring.application.zipkin.base-url=http://localhost:9411/ diff --git a/spring-cloud-2/app002/pom.xml b/spring-cloud-2/app002/pom.xml deleted file mode 100644 index 86423329..00000000 --- a/spring-cloud-2/app002/pom.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.0.6.RELEASE - - - com.cpq - app002 - 0.0.1-SNAPSHOT - app002 - Demo project for Spring Boot - - - 1.8 - - - - - - org.springframework.cloud - spring-cloud-dependencies - Finchley.SR3 - pom - import - - - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - - org.springframework.cloud - spring-cloud-starter-netflix-hystrix - - - - - - app002 - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-cloud-2/app002/src/main/java/com/cpq/app002/App002Application.java b/spring-cloud-2/app002/src/main/java/com/cpq/app002/App002Application.java deleted file mode 100644 index a263ccce..00000000 --- a/spring-cloud-2/app002/src/main/java/com/cpq/app002/App002Application.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cpq.app002; - -import org.springframework.boot.SpringApplication; -import org.springframework.cloud.client.SpringCloudApplication; - -@SpringCloudApplication -public class App002Application { - - public static void main(String[] args) { - SpringApplication.run(App002Application.class, args); - } - -} diff --git a/spring-cloud-2/app002/src/main/java/com/cpq/app002/Test002Ctrl.java b/spring-cloud-2/app002/src/main/java/com/cpq/app002/Test002Ctrl.java deleted file mode 100644 index 6bbab19b..00000000 --- a/spring-cloud-2/app002/src/main/java/com/cpq/app002/Test002Ctrl.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.cpq.app002; - -import org.springframework.web.bind.annotation.*; - -import java.util.Map; -import java.util.concurrent.TimeUnit; - -/** - * @Description - * @Author chenpiqian - * @Date: 2019-07-03 - */ -@RestController -@RequestMapping("/test02") -public class Test002Ctrl { - - @GetMapping("/get/{id}") - public String test02Get(@PathVariable("id") Integer id){ - return "test02/get/"+id; - } - - @PostMapping("/post") - public Map test02Post(@RequestBody Map map){ - map.put("app", "app002"); - return map; - } - - @PostMapping("/timeout1") - public Map timeout1(@RequestBody Map map) throws Exception{ - TimeUnit.SECONDS.sleep(600L); - return map; - } - -} diff --git a/spring-cloud-2/app002/src/main/resources/application.properties b/spring-cloud-2/app002/src/main/resources/application.properties deleted file mode 100644 index 58bdc278..00000000 --- a/spring-cloud-2/app002/src/main/resources/application.properties +++ /dev/null @@ -1,14 +0,0 @@ -spring.application.name=app002 -server.port=10012 - -# \u4F7F\u7528ip\u66FF\u4EE3\u5B9E\u4F8B\u540D -eureka.instance.prefer-ip-address=true - -# \u8BBE\u7F6E\u5B9E\u4F8B\u7684ID\u4E3A\uFF1A \u670D\u52A1\u540D:port -eureka.client.instance-id=${spring.application.name}:${server.port} - -# \u591A\u96C6\u7FA4\u6CE8\u518C -# eureka.client.serviceUrl.defaultZone=http://localhost:10001/eureka/, http://localhost:10002/eureka/, http://localhost:10003/eureka/ - -eureka.client.serviceUrl.defaultZone=http://localhost:10001/eureka/ - diff --git a/spring-cloud-2/eureka-server2/pom.xml b/spring-cloud-2/eureka-server2/pom.xml deleted file mode 100644 index fb21c5f7..00000000 --- a/spring-cloud-2/eureka-server2/pom.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - 4.0.0 - - com.cpq - eureka-server2 - 0.0.1-SNAPSHOT - jar - - eureka-server2 - Demo project for Spring Boot eurreka - - - org.springframework.boot - spring-boot-starter-parent - 2.0.6.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - - org.springframework.cloud - spring-cloud-dependencies - Finchley.SR3 - pom - import - - - - - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-server - - - - - eureka-server2 - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-cloud-2/eureka-server2/src/main/java/com/cpq/eureka/EurekaServerApplication.java b/spring-cloud-2/eureka-server2/src/main/java/com/cpq/eureka/EurekaServerApplication.java deleted file mode 100644 index ce481773..00000000 --- a/spring-cloud-2/eureka-server2/src/main/java/com/cpq/eureka/EurekaServerApplication.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.cpq.eureka; - - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; - -@EnableEurekaServer -@SpringBootApplication -public class EurekaServerApplication { - - public static void main(String[] args) { - SpringApplication.run(EurekaServerApplication.class, args); - } -} diff --git a/spring-cloud-2/eureka-server2/src/main/resources/application-one.properties b/spring-cloud-2/eureka-server2/src/main/resources/application-one.properties deleted file mode 100644 index 8f62f48a..00000000 --- a/spring-cloud-2/eureka-server2/src/main/resources/application-one.properties +++ /dev/null @@ -1,11 +0,0 @@ -spring.application.name=eureka-server -server.port=10001 - -eureka.instance.hostname=eureka-server-one -eureka.client.register-with-eureka=true -eureka.client.fetch-registry=true - -eureka.client.serviceUrl.defaultZone=http://eureka-server-two:10002/eureka/, http://eureka-server-three:10003/eureka/ - -eureka.server.enable-self-preservation = false -eureka.server.eviction-interval-timer-in-ms=20000 diff --git a/spring-cloud-2/eureka-server2/src/main/resources/application-three.properties b/spring-cloud-2/eureka-server2/src/main/resources/application-three.properties deleted file mode 100644 index 47f9dc52..00000000 --- a/spring-cloud-2/eureka-server2/src/main/resources/application-three.properties +++ /dev/null @@ -1,11 +0,0 @@ -spring.application.name=eureka-server -server.port=10003 - -eureka.instance.hostname=eureka-server-three -eureka.client.register-with-eureka=true -eureka.client.fetch-registry=true - -eureka.client.serviceUrl.defaultZone=http://eureka-server-one:10001/eureka/, http://eureka-server-two:10002/eureka/ - -eureka.server.enable-self-preservation = false -eureka.server.eviction-interval-timer-in-ms=20000 \ No newline at end of file diff --git a/spring-cloud-2/eureka-server2/src/main/resources/application-two.properties b/spring-cloud-2/eureka-server2/src/main/resources/application-two.properties deleted file mode 100644 index 8f365a00..00000000 --- a/spring-cloud-2/eureka-server2/src/main/resources/application-two.properties +++ /dev/null @@ -1,11 +0,0 @@ -spring.application.name=eureka-server -server.port=10002 - -eureka.instance.hostname=eureka-server-two -eureka.client.register-with-eureka=true -eureka.client.fetch-registry=true - -eureka.client.serviceUrl.defaultZone=http://eureka-server-one:10001/eureka/, http://eureka-server-three:10003/eureka/ - -eureka.server.enable-self-preservation = false -eureka.server.eviction-interval-timer-in-ms=20000 \ No newline at end of file diff --git a/spring-cloud-2/eureka-server2/src/main/resources/application.properties b/spring-cloud-2/eureka-server2/src/main/resources/application.properties deleted file mode 100644 index d6d57740..00000000 --- a/spring-cloud-2/eureka-server2/src/main/resources/application.properties +++ /dev/null @@ -1,20 +0,0 @@ -#\u670D\u52A1\u540D -spring.application.name=eureka-server -#\u670D\u52A1\u7AEF\u53E3 -server.port=10001 - -eureka.instance.hostname=eureka-server -eureka.instance.prefer-ip-address=true -#\u662F\u5426\u5C06\u81EA\u5DF1\u6CE8\u518C\u5230eureka\u670D\u52A1\u4E2D\uFF0C\u9ED8\u8BA4\u662Ftrue -eureka.client.register-with-eureka=false -#\u662F\u5426\u4ECEEureka\u4E2D\u83B7\u53D6\u6CE8\u518C\u4FE1\u606F\uFF0C\u9ED8\u8BA4\u662Ftrue -eureka.client.fetch-registry=false - -#\u914D\u7F6EdefaultZone \u8986\u76D6\u5176\u9ED8\u8BA4\u503C\uFF0C\u907F\u514DConnect to localhost:8761 time out -eureka.client.serviceUrl.defaultZone=http://localhost:10001/eureka/ - -#\u5173\u95ED\u6CE8\u518C\u4E2D\u5FC3\u81EA\u6211\u4FDD\u62A4\u673A\u5236\u3002\u670D\u52A1\u5B9E\u4F8Bdown\u6389\uFF0C\u5219\u4E0B\u7EBF\u5B9E\u4F8B -eureka.server.enable-self-preservation = false -#\u6CE8\u518C\u4E2D\u5FC3\u6E05\u7406\u95F4\u9694\uFF08\u5355\u4F4D\u6BEB\u79D2\uFF0C\u9ED8\u8BA490*1000\uFF09\uFF0C\u4E0D\u8D77\u4F5C\u7528 -eureka.server.eviction-interval-timer-in-ms=20000 -# \u5BA2\u6237\u7AEF\u52A0\u5165\u6B64\u914D\u7F6E eureka.client.healthcheck.enabled=true \ No newline at end of file diff --git a/spring-cloud-2/eureka-server2/src/main/resources/logback.xml b/spring-cloud-2/eureka-server2/src/main/resources/logback.xml deleted file mode 100644 index 8832ac27..00000000 --- a/spring-cloud-2/eureka-server2/src/main/resources/logback.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git "a/spring-cloud-2/eureka-server2/\351\233\206\347\276\244\346\220\255\345\273\272.txt" "b/spring-cloud-2/eureka-server2/\351\233\206\347\276\244\346\220\255\345\273\272.txt" deleted file mode 100644 index 7319abbe..00000000 --- "a/spring-cloud-2/eureka-server2/\351\233\206\347\276\244\346\220\255\345\273\272.txt" +++ /dev/null @@ -1,8 +0,0 @@ -https://windmt.com/2018/04/15/spring-cloud-2-eureka/ - -hosts配置 -127.0.0.1 activate.navicat.com eureka-server-one eureka-server-two eureka-server-three -运行application-one.properties、application-two.properties、application-three.properties - - - diff --git a/spring-cloud-2/hystrix-dashboard/pom.xml b/spring-cloud-2/hystrix-dashboard/pom.xml deleted file mode 100644 index 896c3328..00000000 --- a/spring-cloud-2/hystrix-dashboard/pom.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.0.6.RELEASE - - - - com.sc2 - hystrix-dashboard - 0.0.1-SNAPSHOT - hystrix-dashboard - Demo project for Spring Boot - - - 1.8 - - - - - - org.springframework.cloud - spring-cloud-dependencies - Finchley.SR3 - pom - import - - - - - - org.springframework.cloud - spring-cloud-starter-web - - - - org.springframework.cloud - spring-cloud-starter-netflix-hystrix - - - org.springframework.cloud - spring-cloud-starter-netflix-hystrix-dashboard - - - org.springframework.cloud - spring-cloud-starter-actuator - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-cloud-2/hystrix-dashboard/src/main/java/com/sc2/hystrixdashboard/HystrixDashboardApplication.java b/spring-cloud-2/hystrix-dashboard/src/main/java/com/sc2/hystrixdashboard/HystrixDashboardApplication.java deleted file mode 100644 index 37d0e6f0..00000000 --- a/spring-cloud-2/hystrix-dashboard/src/main/java/com/sc2/hystrixdashboard/HystrixDashboardApplication.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.sc2.hystrixdashboard; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; - -@SpringBootApplication -@EnableHystrixDashboard -public class HystrixDashboardApplication { - - public static void main(String[] args) { - SpringApplication.run(HystrixDashboardApplication.class, args); - } - -} diff --git a/spring-cloud-2/hystrix-dashboard/src/main/resources/application.properties b/spring-cloud-2/hystrix-dashboard/src/main/resources/application.properties deleted file mode 100644 index 8da21756..00000000 --- a/spring-cloud-2/hystrix-dashboard/src/main/resources/application.properties +++ /dev/null @@ -1,4 +0,0 @@ -spring.application.name=hystrix-dashboard -server.port=10013 - - diff --git "a/spring-cloud-2/zipkin-server/\345\210\206\345\270\203\345\274\217\351\223\276\350\267\257\350\267\237\350\270\252 Sleuth \344\270\216 Zipkin.txt" "b/spring-cloud-2/zipkin-server/\345\210\206\345\270\203\345\274\217\351\223\276\350\267\257\350\267\237\350\270\252 Sleuth \344\270\216 Zipkin.txt" deleted file mode 100644 index 1d9e5a7e..00000000 --- "a/spring-cloud-2/zipkin-server/\345\210\206\345\270\203\345\274\217\351\223\276\350\267\257\350\267\237\350\270\252 Sleuth \344\270\216 Zipkin.txt" +++ /dev/null @@ -1,15 +0,0 @@ -教程 https://windmt.com/2018/04/24/spring-cloud-12-sleuth-zipkin/ -下载jar包 -https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec -java -jar zipkin.jar - - - - - - - - - - - diff --git a/spring-cloud-learn/api-gateway/.gitignore b/spring-cloud-learn/api-gateway/.gitignore deleted file mode 100644 index 82eca336..00000000 --- a/spring-cloud-learn/api-gateway/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/build/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ \ No newline at end of file diff --git a/spring-cloud-learn/api-gateway/pom.xml b/spring-cloud-learn/api-gateway/pom.xml deleted file mode 100644 index 7384750e..00000000 --- a/spring-cloud-learn/api-gateway/pom.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - 4.0.0 - - com.cpq - api-gateway - 0.0.1-SNAPSHOT - jar - - api-gateway - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.13.RELEASE - - - - - - org.springframework.cloud - spring-cloud-dependencies - Edgware.SR4 - pom - import - - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.cloud - spring-cloud-starter-eureka - - - org.springframework.cloud - spring-cloud-starter-zuul - - - org.springframework.boot - spring-boot-starter-actuator - - - - - - - - - - - com.alibaba - fastjson - 1.2.41 - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - true - - - - - - - diff --git a/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/ApiGatewayApplication.java b/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/ApiGatewayApplication.java deleted file mode 100644 index 08582b75..00000000 --- a/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/ApiGatewayApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.cpq.apigateway; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.cloud.netflix.zuul.EnableZuulProxy; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; - -@EnableZuulProxy -@SpringBootApplication -public class ApiGatewayApplication { - - public static void main(String[] args) { - SpringApplication.run(ApiGatewayApplication.class, args); - } - - - @Bean - @LoadBalanced - public RestTemplate restTemplateBalanced(){ - return new RestTemplate(); - } - /** - * 通过 http://localhost:1101/eureka-client-01/dc?token=false来测试过滤器 - */ - //@Bean - //public AccessFilter accessFilter(){ - // return new AccessFilter(); - //} -} diff --git a/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/filter/AccessFilter.java b/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/filter/AccessFilter.java deleted file mode 100644 index 18a16c37..00000000 --- a/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/filter/AccessFilter.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.cpq.apigateway.filter; - -import com.alibaba.fastjson.JSON; -import com.netflix.zuul.ZuulFilter; -import com.netflix.zuul.context.RequestContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.HashMap; -import java.util.Map; - -/** - * 是否登录过滤器 - */ - -@Component -public class AccessFilter extends ZuulFilter { - - private Logger logger = LoggerFactory.getLogger(AccessFilter.class); - - @Override - public String filterType() { - return FilterConstants.PRE_TYPE; - } - - @Override - public int filterOrder() { - return 0; - } - - @Override - public boolean shouldFilter() { - return true; - } - - @Override - public Object run() { - RequestContext ctx = RequestContext.getCurrentContext(); - HttpServletRequest request = ctx.getRequest(); - HttpServletResponse response = ctx.getResponse(); - ctx.addZuulRequestHeader("LOG_ID", "xxx"); - - try { - //HttpServletRequest request = ctx.getRequest(); - // - //Object accessToken = request.getParameter("token"); - //if ("false".equals(accessToken)){ - // ctx.setSendZuulResponse(false); - // ctx.setResponseStatusCode(401); - // return null; - //} - - }catch (Exception e){ - logger.error("网关捕获异常", e); - Map result = new HashMap(); - result.put("code", 500); - result.put("msg", "系统异常"); - result.put("exception",e.getMessage()); - ctx.setResponseBody(JSON.toJSONString(result)); - ctx.setSendZuulResponse(false); - response.setContentType(MediaType.APPLICATION_JSON_UTF8.toString()); - } - - return null; - } -} diff --git a/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/filter/GatewayFallback.java b/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/filter/GatewayFallback.java deleted file mode 100644 index eac9c097..00000000 --- a/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/filter/GatewayFallback.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.cpq.apigateway.filter; - -import com.alibaba.fastjson.JSON; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.stereotype.Component; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; - -// https://www.cnblogs.com/niechen/p/8856551.html ZuulFallbackProvider - -// https://www.cnblogs.com/niechen/p/8856551.html ZuulFallbackProvider - -// http://itmuch.com/spring-cloud/edgware-new-zuul-fallback/ - -//周立 http://itmuch.com/categories/Spring-Cloud/ - -@Component -public class GatewayFallback implements FallbackProvider { - - private Logger logger = LoggerFactory.getLogger(GatewayFallback.class); - - @Override - public String getRoute() { - return "*"; - } - - @Override - public ClientHttpResponse fallbackResponse() { - return createClientHttpResponse(null); - } - - @Override - public ClientHttpResponse fallbackResponse(Throwable throwable) { - return createClientHttpResponse(throwable); - } - - private ClientHttpResponse createClientHttpResponse(Throwable throwable){ - return new ClientHttpResponse() { - @Override - public HttpStatus getStatusCode() throws IOException { - return HttpStatus.OK; - } - - @Override - public int getRawStatusCode() throws IOException { - return HttpStatus.OK.value(); - } - - @Override - public String getStatusText() throws IOException { - return HttpStatus.OK.toString(); - } - - @Override - public HttpHeaders getHeaders() { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON_UTF8); - return headers; - } - - @Override - public void close() { - - } - - @Override - public InputStream getBody() throws IOException { - logger.error("网关抛异常", throwable); - Map result = new HashMap<>(); - result.put("code", 100000); - result.put("msg", "系统异常,请稍后重试"); - result.put("exception", throwable == null ? null:throwable); - return new ByteArrayInputStream(JSON.toJSONString(result).getBytes()); - } - - }; - } -} \ No newline at end of file diff --git a/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/filter/PostFilter.java b/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/filter/PostFilter.java deleted file mode 100644 index bd462644..00000000 --- a/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/filter/PostFilter.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.cpq.apigateway.filter; - -import com.netflix.zuul.ZuulFilter; -import com.netflix.zuul.context.RequestContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -@Component -public class PostFilter extends ZuulFilter { - - @Autowired - RestTemplate restTemplateBalanced; - - private Logger logger = LoggerFactory.getLogger(PostFilter.class); - - @Override - public String filterType() { - return FilterConstants.POST_TYPE; - } - - @Override - public int filterOrder() { - return 0; - } - - @Override - public boolean shouldFilter() { - return true; - } - - @Override - public Object run() { - RequestContext ctx = RequestContext.getCurrentContext(); - HttpServletResponse response = ctx.getResponse(); - HttpServletRequest request = ctx.getRequest(); - System.out.println("****request******" + request.getHeader("log-id")); - System.out.println("*****response*****" + response.getHeader("log-id")); - - Map map = ctx.getZuulRequestHeaders(); - System.out.println("****map******" + map.toString()); - System.out.println("****map******" + map.get("log-id")); - - - - //try { - // Object zuulResponse = RequestContext.getCurrentContext().get("zuulResponse"); - // if (zuulResponse != null) { - // RibbonHttpResponse resp = (RibbonHttpResponse) zuulResponse; - // String body = StreamUtils.copyToString(resp.getBody(), Charset.forName("UTF-8")); - // JSONObject jsonObject = JSONObject.parseObject(ctx.getResponseBody(), JSONObject.class); - // System.err.println(body); - // resp.close(); - // RequestContext.getCurrentContext().setResponseBody(body); - // } - //} catch (IOException e) { - // e.printStackTrace(); - //} - - - // - //InputStream stream = ctx.getResponseDataStream(); - //try { - // String body = StreamUtils.copyToString(stream, Charset.forName("UTF-8")); - // System.err.println(body); - // JSONObject jsonObject = JSONObject.parseObject(body, JSONObject.class); - // ctx.setResponseBody(body); - //} catch (IOException e) { - // e.printStackTrace(); - //}finally { - // //stream.close(); - //} - - Map logParams = new HashMap<>(); - logParams.put("userId", 111); - logParams.put("loginName", "loginNam11e"); - logParams.put("operateModule", "用户222"); - logParams.put("operateType", "增加"); - logParams.put("operateTime", new Date()); - logParams.put("result", true); - logParams.put("tenantCode", "tenantcode1"); - //restTemplateBalanced.postForObject("http://tenant-mq/log/producer/send", logParams, String.class); - //restTemplate.postForObject("http://localhost:8083/tenant-mq/log/producer/send", logParams, String.class); - //System.out.println("Services: " + discoveryClient.getServices()); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.parseMediaType("application/json; charset=UTF-8")); - headers.add("Accept", MediaType.APPLICATION_JSON.toString()); - HttpEntity httpEntity = new HttpEntity(logParams, headers); - restTemplateBalanced.postForObject("http://eureka-consumer-feign/p1", httpEntity, String.class); - return null; - } - } diff --git a/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/websocket/WebSocketFilter.java b/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/websocket/WebSocketFilter.java deleted file mode 100644 index 8bc3b558..00000000 --- a/spring-cloud-learn/api-gateway/src/main/java/com/cpq/apigateway/websocket/WebSocketFilter.java +++ /dev/null @@ -1,37 +0,0 @@ -//package com.cpq.apigateway.filter; -// -//import com.netflix.zuul.ZuulFilter; -//import com.netflix.zuul.context.RequestContext; -//import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; -//import org.springframework.stereotype.Component; -// -//import javax.servlet.http.HttpServletRequest; -// -//@Component -//public class WebSocketFilter extends ZuulFilter { -// @Override -// public String filterType() { -// return FilterConstants.PRE_TYPE; -// } -// @Override -// public int filterOrder() { -// return 0; -// } -// @Override -// public boolean shouldFilter() { -// return true; -// } -// @Override -// public Object run() { -// RequestContext context = RequestContext.getCurrentContext(); -// HttpServletRequest request = context.getRequest(); -// String upgradeHeader = request.getHeader("Upgrade"); -// if (null == upgradeHeader) { -// upgradeHeader = request.getHeader("upgrade"); -// } -// if (null != upgradeHeader && "websocket".equalsIgnoreCase(upgradeHeader)) { -// context.addZuulRequestHeader("connection", "Upgrade"); -// } -// return null; -// } -//} \ No newline at end of file diff --git a/spring-cloud-learn/api-gateway/src/main/resources/application-test.properties b/spring-cloud-learn/api-gateway/src/main/resources/application-test.properties deleted file mode 100644 index e29be0bf..00000000 --- a/spring-cloud-learn/api-gateway/src/main/resources/application-test.properties +++ /dev/null @@ -1,15 +0,0 @@ -server.port=1002 - - - - - - - - - - - - - - diff --git a/spring-cloud-learn/api-gateway/src/main/resources/application.properties b/spring-cloud-learn/api-gateway/src/main/resources/application.properties deleted file mode 100644 index 199a363b..00000000 --- a/spring-cloud-learn/api-gateway/src/main/resources/application.properties +++ /dev/null @@ -1,81 +0,0 @@ -spring.application.name=api-gateway -server.port=1001 -eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ - -eureka.client.healthcheck.enabled=true - -#\u4F7F\u7528ip\u66FF\u4EE3\u5B9E\u4F8B\u540D -eureka.instance.prefer-ip-address=true -#\u8BBE\u7F6E\u5B9E\u4F8B\u7684ID\u4E3A\uFF1A \u670D\u52A1\u540D:port\u3002 -eureka.instance.instance-id=${spring.application.name}:${server.port} -#\u8FD9\u4E24\u4E2A\u7ED3\u5408\u8D77\u6765\u7684\u7ED3\u679C\u662F\uFF0C\u5B9E\u4F8Bdown\u6389\u540E\uFF0C\u6B64\u5B9E\u4F8B\uFF08\u5305\u542B\u670D\u52A1\u540D:\u7AEF\u53E3\uFF09\u4F1A\u88AB\u6CE8\u518C\u4E2D\u5FC3\u5254\u9664 - -# eureka-client\u670D\u52A1\u6709\u591A\u4E2A\uFF0C\u4EE5/eureka-client/\u5F00\u5934\u7684\u8BF7\u6C42\u8F6C\u53D1\u5230\u670D\u52A1\u540D\u4E3Aeureka-client\u7684\u670D\u52A1\u4E2D\u3002 -# /eureka-client\u8BF7\u6C42\u524D\u7F00\u4F1A\u88AB\u53BB\u6389\uFF0C#\u5373 http://localhost:1001/eureka-client/dc \u8F6C\u53D1\u5230 http://localhost:2001/dc -zuul.routes.eureka-client.path=/eureka-client/** -#zuul.routes.eureka-consumer-feign.path=/eureka-consumer-feign/** - -zuul.routes.springcloud-mybatis-demo1.path=/springcloud/mybatis/demo1/** -zuul.routes.springcloud-mybatis-demo2.path=/springcloud/mybatis/demo2/** - -zuul.routes.tx-app01.path=/tx/app01/** -zuul.routes.tx-app02.path=/tx/app02/** - - -feign.hystrix.enabled=true -# \u5173\u4E8Espringcloud-hystrix\u673A\u5236 http://www.jianshu.com/p/b8d21248c9b1 -hystrix.command.default.execution.isolation.strategy= SEMAPHORE -hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=75000 - -# websocket\u5931\u8D25 -zuul.routes.api-websocket.path=/ws/** -zuul.routes.api-websocket.service-id=api-websocket - - - - -#************************\u8D85\u65F6\u91CD\u8BD5start************************ -#https://blog.csdn.net/xiao_jun_0820/article/details/79320352 -#issue -#https://github.com/spring-cloud/spring-cloud-netflix/issues/2667 -#\u91CD\u8BD5\u4F1A\u6613\u4EA7\u751F\u810F\u6570\u636E - -#spring.cloud.loadbalancer.retry.enabled=true -##zuul\u8D85\u65F6\u8BBE\u7F6E\u9ED8\u8BA41000\uFF0C\u5927\u4E8Ehystrix\u8D85\u65F6\u65F6\u95F4 -zuul.host.socket-timeout-millis=70000 -##\u9ED8\u8BA42000\uFF0C\u5927\u4E8Eribbon.ConnectTimeout -zuul.host.connect-timeout-millis=5000 -##\u5FC5\u987B\u8BBE\u7F6E\u4E3Atrue -#zuul.retryable=true - -##\u8BBE\u7F6E\u8DEF\u7531\u8F6C\u53D1\u8BF7\u6C42\u7684\u65F6\u5019\uFF0C\u521B\u5EFA\u8BF7\u6C42\u8FDE\u63A5\u7684\u8D85\u65F6\u65F6\u95F4 -ribbon.ConnectTimeout=5000 -##\u7528\u6765\u8BBE\u7F6E\u8DEF\u7531\u8F6C\u53D1\u8BF7\u6C42\u7684\u8D85\u65F6\u65F6\u95F4 -ribbon.ReadTimeout=60000 -##\u5BF9\u6240\u6709\u8BF7\u6C42\u90FD\u8FDB\u884C\u91CD\u8BD5 -#ribbon.OkToRetryOnAllOperations=true -##\u5207\u6362\u5B9E\u4F8B\u7684\u91CD\u8BD5\u6B21\u6570 -#ribbon.MaxAutoRetriesNextServer=3 -##\u5BF9\u5F53\u524D\u5B9E\u4F8B\u7684\u91CD\u8BD5\u6B21\u6570 -#ribbon.MaxAutoRetries=1 - -#feign.hystrix.enabled=true -##\u8BBE\u7F6EAPI\u7F51\u5173\u4E2D\u8DEF\u7531\u8F6C\u53D1\u8BF7\u6C42\u7684HystrixCommand\u6267\u884C\u8D85\u65F6\u65F6\u95F4\uFF0C\u5927\u4E8E\uFF08ReadTimeout + ConnectTimeout\uFF09*\uFF08MaxAutoRetries+1\uFF09*(MaxAutoRetriesNextServer+1) -#hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=264000 - -#\u524D\u53F0nginx\u94FE\u63A5\u8D85\u65F6\u65F6\u95F4\u9ED8\u8BA4\u662F65s\uFF0C\u53EF\u80FD\u8981\u8BBE\u7F6Eproxy_read_timeout 120; -#************************\u8D85\u65F6\u91CD\u8BD5end************************ - - - - - - - - - - - - - - diff --git a/spring-cloud-learn/api-gateway/src/main/resources/logback.xml b/spring-cloud-learn/api-gateway/src/main/resources/logback.xml deleted file mode 100644 index 8832ac27..00000000 --- a/spring-cloud-learn/api-gateway/src/main/resources/logback.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/spring-cloud-learn/api-gateway/src/main/resources/nginx.conf b/spring-cloud-learn/api-gateway/src/main/resources/nginx.conf deleted file mode 100644 index 06c4d037..00000000 --- a/spring-cloud-learn/api-gateway/src/main/resources/nginx.conf +++ /dev/null @@ -1,68 +0,0 @@ - -#user nobody; -worker_processes 1; - -#error_log logs/error.log; -#error_log logs/error.log notice; -#error_log logs/error.log info; - -#pid logs/nginx.pid; - - -events { - worker_connections 1024; -} - - -http { - include mime.types; - default_type application/octet-stream; - - #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - # '$status $body_bytes_sent "$http_referer" ' - # '"$http_user_agent" "$http_x_forwarded_for"'; - - #access_log logs/access.log main; - - sendfile on; - #tcp_nopush on; - - #整个请求保持65秒链接时间,即使收到服务端数据后,任然保持链接 - #keepalive_timeout 65; - - #发送请求 到 收到数据 之间的时间差 - #proxy_read_timeout 120; - - #重试次数,0无重试次数限制,1不重试,2重试一次 - #proxy_next_upstream_tries 1; - - #gzip on; - - - upstream www.twogateway.com { - server localhost:1001 weight=1; - server localhost:1002 weight=1; - } - - server { - listen 92; #远程授权 省平台 - server_name localhost; - - location / { - root html/gateway; - index index.html; - - #主页不缓存 - if ($request_uri ~* "^/$"){ - add_header Cache-Control no-store; - expires -1; - } - } - - location /eureka-client { - proxy_pass http://www.twogateway.com; - } - - } - -} diff --git a/spring-cloud-learn/config-client/.gitignore b/spring-cloud-learn/config-client/.gitignore deleted file mode 100644 index 82eca336..00000000 --- a/spring-cloud-learn/config-client/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/build/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ \ No newline at end of file diff --git a/spring-cloud-learn/config-client/pom.xml b/spring-cloud-learn/config-client/pom.xml deleted file mode 100644 index 9519e892..00000000 --- a/spring-cloud-learn/config-client/pom.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - 4.0.0 - - com.cpq - config-client - 0.0.1-SNAPSHOT - jar - - config-client - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.13.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.cloud - spring-cloud-starter-eureka - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.cloud - spring-cloud-starter-config - - - - - - - org.springframework.cloud - spring-cloud-dependencies - Edgware.SR4 - pom - import - - - - - diff --git a/spring-cloud-learn/config-client/src/main/java/com/cpq/configclient/controller/TestController.java b/spring-cloud-learn/config-client/src/main/java/com/cpq/configclient/controller/TestController.java deleted file mode 100644 index be08edd0..00000000 --- a/spring-cloud-learn/config-client/src/main/java/com/cpq/configclient/controller/TestController.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.cpq.configclient.controller; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletRequest; - -@RestController -@RequestMapping("/config-client") -public class TestController { - - @Value("${common.item}") - String commonItem; - @Value("${custom.param1}") - String customParam1; - //@Value("${common.eureka-consumer}") //无法读取eureka-consumer目录下的文件 - //String commonEurekaConsumer; - - @GetMapping("/dc") - public String dc(HttpServletRequest request) throws Exception{ - System.out.println("******* common.item: "+commonItem); - System.out.println("******* customParam1: "+customParam1); - return commonItem + " " + customParam1; - } - -} diff --git a/spring-cloud-learn/config-client/src/main/resources/bootstrap.yml b/spring-cloud-learn/config-client/src/main/resources/bootstrap.yml deleted file mode 100644 index 93333450..00000000 --- a/spring-cloud-learn/config-client/src/main/resources/bootstrap.yml +++ /dev/null @@ -1,9 +0,0 @@ -#从配置中心服务端获取git上的配置,必须使用bootstrap.yml -spring: - cloud: - config: - uri: http://localhost:1201/ - name: config-client #查看eureka-consumer笔记 - profile: test #配置文件的profile - label: master - diff --git a/spring-cloud-learn/config-server-git/.gitignore b/spring-cloud-learn/config-server-git/.gitignore deleted file mode 100644 index 82eca336..00000000 --- a/spring-cloud-learn/config-server-git/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/build/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ \ No newline at end of file diff --git a/spring-cloud-learn/config-server-git/pom.xml b/spring-cloud-learn/config-server-git/pom.xml deleted file mode 100644 index f392f1e2..00000000 --- a/spring-cloud-learn/config-server-git/pom.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - 4.0.0 - - com.cpq - config-server-git - 0.0.1-SNAPSHOT - jar - - config-server-git - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.13.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.cloud - spring-cloud-config-server - - - - - - - - org.springframework.cloud - spring-cloud-dependencies - Edgware.SR4 - pom - import - - - - - diff --git a/spring-cloud-learn/config-server-git/src/main/java/com/cpq/configservergit/ConfigServerGitApplication.java b/spring-cloud-learn/config-server-git/src/main/java/com/cpq/configservergit/ConfigServerGitApplication.java deleted file mode 100644 index e6f4b86c..00000000 --- a/spring-cloud-learn/config-server-git/src/main/java/com/cpq/configservergit/ConfigServerGitApplication.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.cpq.configservergit; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.cloud.config.server.EnableConfigServer; - -@EnableConfigServer -@EnableDiscoveryClient -@SpringBootApplication -public class ConfigServerGitApplication { - - public static void main(String[] args) { - SpringApplication.run(ConfigServerGitApplication.class, args); - } -} diff --git a/spring-cloud-learn/config-server-git/src/main/resources/application.yml b/spring-cloud-learn/config-server-git/src/main/resources/application.yml deleted file mode 100644 index 8e2a7515..00000000 --- a/spring-cloud-learn/config-server-git/src/main/resources/application.yml +++ /dev/null @@ -1,32 +0,0 @@ -spring: - application: - name: config-server - cloud: - config: - username: CodingSoldier - password: 密码 - label: master - server: - git: - uri: https://github.com/CodingSoldier/spring-cloud-config-git.git - # application作为一个占位符,获取服务名,用这个服务名匹配git仓库中的文件夹 - searchPaths: '{application}' - # 强制从远程git仓库获取配置 - force-pull: true - -# 浏览器打开 http://localhost:1201/config-client/dev/master -# 加载的是https://github.com/CodingSoldier/spring-cloud-config-git.git -# master分支中的config-client.yml、config-client-dev.yml配置文件 - -# 浏览器打开 http://localhost:1201/config-client/test/master -# 加载的是https://github.com/CodingSoldier/spring-cloud-config-git.git -# master分支中的config-client.yml、config-client-test.yml配置文件 - -# 若打开 http://localhost:1201/config-client/master 这url不规范,需要加上环境的profile -# 仅仅加载 https://github.com/CodingSoldier/spring-cloud-config-git.git -# master分支中的 config-client.yml配置文件 - -#*************其他工程获取git上的配置请查看:config-client、eureka-consumer*****************# - -server: - port: 1201 \ No newline at end of file diff --git a/spring-cloud-learn/consul-client/pom.xml b/spring-cloud-learn/consul-client/pom.xml deleted file mode 100644 index 36c69ae5..00000000 --- a/spring-cloud-learn/consul-client/pom.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - 4.0.0 - - com.cpq - consul-client - 0.0.1-SNAPSHOT - jar - - consul-client - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.4.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.cloud - spring-cloud-starter-consul-discovery - - - org.springframework.boot - spring-boot-starter-web - - - - - - - org.springframework.cloud - spring-cloud-dependencies - Dalston.SR1 - pom - import - - - - - diff --git a/spring-cloud-learn/consul-client/readme.txt b/spring-cloud-learn/consul-client/readme.txt deleted file mode 100644 index 254d46ad..00000000 --- a/spring-cloud-learn/consul-client/readme.txt +++ /dev/null @@ -1,2 +0,0 @@ -下载consul-server服务端 -在consul-server目录中执行 ./consul agent -dev diff --git a/spring-cloud-learn/consul-client/src/main/java/com/cpq/consulclient/CheckController.java b/spring-cloud-learn/consul-client/src/main/java/com/cpq/consulclient/CheckController.java deleted file mode 100644 index fb3fe57c..00000000 --- a/spring-cloud-learn/consul-client/src/main/java/com/cpq/consulclient/CheckController.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.cpq.consulclient; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -/** - * @Description - * @Author chenpiqian - * @Date: 2019-03-27 - */ -@RestController -public class CheckController { - - @PostMapping("/p1") - public String check(@RequestBody Log log){ - System.out.println(log.toString()); - return "ok1"; - } - - @GetMapping("check") - public String check(){ - System.out.println("**************ok1111"); - return "ok1"; - } - - @GetMapping("test01") - public String test01(){ - System.out.println("**************test01"); - return "test01"; - } - - @GetMapping("/api/consul/client/test001") - public String test011(){ - System.out.println("**************/api/consul/client/test001"); - return "/api/consul/client/test001"; - } - - @GetMapping("/consulclient/test001") - public String test01122(){ - System.out.println("*************consulclient/test001"); - return "consulclient/test001"; - } -} diff --git a/spring-cloud-learn/consul-client/src/main/java/com/cpq/consulclient/ConsulClientApplication.java b/spring-cloud-learn/consul-client/src/main/java/com/cpq/consulclient/ConsulClientApplication.java deleted file mode 100644 index 51720061..00000000 --- a/spring-cloud-learn/consul-client/src/main/java/com/cpq/consulclient/ConsulClientApplication.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.cpq.consulclient; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; - -@EnableDiscoveryClient -@SpringBootApplication -public class ConsulClientApplication { - - public static void main(String[] args) { - SpringApplication.run(ConsulClientApplication.class, args); - } - - -} diff --git a/spring-cloud-learn/consul-client/src/main/java/com/cpq/consulclient/Log.java b/spring-cloud-learn/consul-client/src/main/java/com/cpq/consulclient/Log.java deleted file mode 100644 index 420e64df..00000000 --- a/spring-cloud-learn/consul-client/src/main/java/com/cpq/consulclient/Log.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.cpq.consulclient; - -import java.io.Serializable; -import java.util.Date; - -/** - *

- * 日志表 - *

- * - * @author chenpiqian - * @since 2019-04-01 - */ -public class Log implements Serializable { - - private static final long serialVersionUID = 1L; - - private String id; - - /** - * 用户id - */ - private Long userId; - - /** - * 账号 - */ - private String loginName; - - /** - * 操作功能项 - */ - private String operateItem; - - /** - * 增删改查 - */ - private String operateType; - - /** - * 操作时间 - */ - private Date operateTime; - - /** - * 操作结果true成功,false失败 - */ - private Boolean result; - - /** - * 请求url - */ - private String url; - - /** - * 租户编码 - */ - private String tenantCode; - - /** - * 详情 - */ - private String detail; - - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public Long getUserId() { - return userId; - } - - public void setUserId(Long userId) { - this.userId = userId; - } - - public String getLoginName() { - return loginName; - } - - public void setLoginName(String loginName) { - this.loginName = loginName; - } - - public String getOperateItem() { - return operateItem; - } - - public void setOperateItem(String operateItem) { - this.operateItem = operateItem; - } - - public String getOperateType() { - return operateType; - } - - public void setOperateType(String operateType) { - this.operateType = operateType; - } - - public Date getOperateTime() { - return operateTime; - } - - public void setOperateTime(Date operateTime) { - this.operateTime = operateTime; - } - - public Boolean getResult() { - return result; - } - - public void setResult(Boolean result) { - this.result = result; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getTenantCode() { - return tenantCode; - } - - public void setTenantCode(String tenantCode) { - this.tenantCode = tenantCode; - } - - public String getDetail() { - return detail; - } - - public void setDetail(String detail) { - this.detail = detail; - } -} diff --git a/spring-cloud-learn/consul-client/src/main/java/com/cpq/consulclient/MyWebMvcConfiguration.java b/spring-cloud-learn/consul-client/src/main/java/com/cpq/consulclient/MyWebMvcConfiguration.java deleted file mode 100644 index 028abe0c..00000000 --- a/spring-cloud-learn/consul-client/src/main/java/com/cpq/consulclient/MyWebMvcConfiguration.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.cpq.consulclient; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; - -import java.text.SimpleDateFormat; -import java.util.List; - -/** - * @Description - * @Author chenpiqian - * @Date: 2019-04-01 - */ -@Configuration -public class MyWebMvcConfiguration extends WebMvcConfigurationSupport { - - //定义时间格式转换器 - @Bean - public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter1() { - MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); - converter.setObjectMapper(mapper); - return converter; - } - @Bean - public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter2() { - MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); - converter.setObjectMapper(mapper); - return converter; - } - - //添加转换器 - @Override - public void configureMessageConverters(List> converters) { - //将我们定义的时间格式转换器添加到转换器列表中, - //这样jackson格式化时候但凡遇到Date类型就会转换成我们定义的格式 - converters.add(jackson2HttpMessageConverter2()); - converters.add(jackson2HttpMessageConverter1()); - } - -} - diff --git a/spring-cloud-learn/consul-client/src/main/resources/application-test.properties b/spring-cloud-learn/consul-client/src/main/resources/application-test.properties deleted file mode 100644 index 2093ba3f..00000000 --- a/spring-cloud-learn/consul-client/src/main/resources/application-test.properties +++ /dev/null @@ -1,13 +0,0 @@ -spring.application.name=consul-client -server.port=8401 - -spring.cloud.consul.host=localhost -spring.cloud.consul.port=8500 -spring.cloud.consul.discovery.health-check-interval=60s -spring.cloud.consul.discovery.service-name=${spring.application.name} -spring.cloud.consul.discovery.instance-id=${spring.application.name}-${server.port} -spring.cloud.consul.discovery.health-check-url=http://localhost:8401/check -spring.cloud.consul.discovery.enabled=true -spring.cloud.consul.discovery.query-passing=true - -management.security.enabled=false \ No newline at end of file diff --git a/spring-cloud-learn/consul-client/src/main/resources/application.properties b/spring-cloud-learn/consul-client/src/main/resources/application.properties deleted file mode 100644 index 42403879..00000000 --- a/spring-cloud-learn/consul-client/src/main/resources/application.properties +++ /dev/null @@ -1,13 +0,0 @@ -spring.application.name=consul-client -server.port=8400 - -spring.cloud.consul.host=localhost -spring.cloud.consul.port=8500 -spring.cloud.consul.discovery.health-check-interval=60s -spring.cloud.consul.discovery.service-name=${spring.application.name} -spring.cloud.consul.discovery.instance-id=${spring.application.name}-${server.port} -spring.cloud.consul.discovery.health-check-url=http://localhost:8400/check -spring.cloud.consul.discovery.enabled=true -spring.cloud.consul.discovery.query-passing=true - -management.security.enabled=false \ No newline at end of file diff --git a/spring-cloud-learn/eureka-client/src/main/java/com/cpq/eurekaclient/EurekaClientApplication.java b/spring-cloud-learn/eureka-client/src/main/java/com/cpq/eurekaclient/EurekaClientApplication.java deleted file mode 100644 index 5a6950ed..00000000 --- a/spring-cloud-learn/eureka-client/src/main/java/com/cpq/eurekaclient/EurekaClientApplication.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.cpq.eurekaclient; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; - -@EnableDiscoveryClient -@SpringBootApplication -public class EurekaClientApplication { - - public static void main(String[] args) { - SpringApplication.run(EurekaClientApplication.class, args); - } -} diff --git a/spring-cloud-learn/eureka-client/src/main/java/com/cpq/eurekaclient/controller/DcController.java b/spring-cloud-learn/eureka-client/src/main/java/com/cpq/eurekaclient/controller/DcController.java deleted file mode 100644 index 1710468d..00000000 --- a/spring-cloud-learn/eureka-client/src/main/java/com/cpq/eurekaclient/controller/DcController.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.cpq.eurekaclient.controller; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletRequest; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -@RestController -public class DcController { - - @Autowired - DiscoveryClient discoveryClient; - @Value("${custom.time.sleep}") - long sleepTime; - - @GetMapping("/dc") - public String dc(HttpServletRequest request) throws Exception{ - - //request.getHeader("null point exception").length(); - - //Thread.sleep(5000L); //睡眠,相当于其他服务调用时弄个错误 - - //System.out.println(request.getCookies()); - System.out.println(request.getServerPort()); - String services = "Services: " + discoveryClient.getServices(); - //System.out.println(services); - return services; - } - - @GetMapping("/map") - public Map map(HttpServletRequest request) throws Exception{ - - Map map = new HashMap(); - map.put("status", 0); - map.put("data", 1234546); - return map; - } - - @PostMapping("/post/map") - public Map pmap(@RequestBody Map param) throws Exception{ - - Map map = new HashMap(); - map.put("status", 100); - map.put("data", "data"); - return map; - } - - @GetMapping("/dc2") - public String dc2(HttpServletRequest request) throws Exception{ - - //request.getHeader("null point exception").length(); - - //System.out.println(sleepTime); - //Thread.sleep(sleepTime); //睡眠,相当于其他服务调用时弄个错误 - - //System.out.println(request.getCookies()); - System.out.println(request.getServerPort()); - String services = "Services: " + discoveryClient.getServices(); - //System.out.println(services); - return services; - } - - @GetMapping("/dc3") - public String dc3(HttpServletRequest request) throws Exception{ - System.out.println(new Date() +" port: " +request.getServerPort()); - //TimeUnit.SECONDS.sleep(60000); - System.out.println(request.getServerPort()); - String services = "Services: " + discoveryClient.getServices(); - return services; - } - - @PostMapping("/p4") - public String p4(HttpServletRequest request) throws Exception{ - System.out.println(new Date() +" port: " +request.getServerPort()); - //TimeUnit.SECONDS.sleep(60000); - System.out.println(request.getServerPort()); - String services = "Services: " + discoveryClient.getServices(); - return services; - } - -} diff --git a/spring-cloud-learn/eureka-client/src/main/java/com/cpq/eurekaclient/controller/UploadController.java b/spring-cloud-learn/eureka-client/src/main/java/com/cpq/eurekaclient/controller/UploadController.java deleted file mode 100644 index 9f46be97..00000000 --- a/spring-cloud-learn/eureka-client/src/main/java/com/cpq/eurekaclient/controller/UploadController.java +++ /dev/null @@ -1,242 +0,0 @@ -//import -// com.alibaba.fastjson.JSONObject; -//import -// com.idoipo.infras.gateway.open.model.InvokeLogModel; -//import -// com.idoipo.infras.gateway.open.service.IInvokeLogService; -//import -// com.idoipo.infras.gateway.open.utils.MultiPartFormDateToJson; -//import -// com.netflix.zuul.ZuulFilter; -//import -// com.netflix.zuul.context.RequestContext; -//import -// org.slf4j.Logger; -//import -// org.slf4j.LoggerFactory; -//import -// org.springframework.beans.factory.annotation.Autowired; -//import -// org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; -//import -// org.springframework.stereotype.Component; -//import -// org.springframework.util.StreamUtils; -//import -// javax.servlet.http.HttpServletRequest; -//import -// java.io.IOException; -//import -// java.io.InputStream; -//import -// java.nio.charset.Charset; -//import -// java.util.Date; -//import -// java.util.Map; -///** -// * Create by liping on 2018/9/11 * 接口调用日志记录过滤器 -// */ -//@Component -//public -//class -//LogRecodePostFilter -// extends -// ZuulFilter { -// private -// static -// final -// Logger logger = LoggerFactory.getLogger(LogRecodePostFilter. -// class -// ); @Autowired IInvokeLogService invokeLogService; @Override -// public -// String filterType() { -// return -// FilterConstants.POST_TYPE; -//// -// 要打印返回信息,必须得用"post" -// } @Override -// public -// int -// filterOrder() { -// return -// FilterConstants.PRE_DECORATION_FILTER_ORDER + 2 -// ; } @Override -// public -// boolean -// shouldFilter() { RequestContext context -// RequestContext.getCurrentContext(); Boolean isSuccess -// = ( -// boolean -// ) context.get("isSuccess" -// ); -// return -// isSuccess; } @Override -// public -// Object run() { -// try -// { logger.info( -// "进入日志记录过滤器" -// ); RequestContext ctx -// RequestContext.getCurrentContext(); HttpServletRequest request -// ctx.getRequest(); InputStream in -// request.getInputStream(); String method -// request.getMethod(); String interfaceMethod -// request.getServletPath(); -//// -// logger.info("请求方法method={},url={}",method,interfaceMethod); -// String reqBody = StreamUtils.copyToString(in, Charset.forName("UTF-8" -// )); -// int -// user = 0 -// ; String invokeUser -// = "" -// if -// ("GET" -// .equals(method.toUpperCase())) { Map -// map = -// request.getParameterMap(); -//// -// 打印请求url参数 -// if -// (map != -// null -// ) { StringBuilder sb -// new -// StringBuilder(); sb.append( -// "{" -// ); -// for -// (Map.Entry -// entry : map.entrySet()) { String key -// entry.getKey(); String value -// printArray(entry.getValue()); sb.append( -// "[" + key + "=" + value + "]" -// ); -// if -// ("user" -// .equals(key)) { invokeUser -// value; } -// else -// if -// ("userFlag" -// .equals(key)) { user -// Integer.parseInt(value); } } sb.append( -// "}" -// ); reqBody -// sb.toString(); -//// -// logger.info("reqBody ={}" + reqBody); -// } } -// else -// if -// ("POST" -// .equals(method.toUpperCase())) { -//// -// 打印请求json参数 -// if -// (reqBody != -// null -// ) { String conType -// = request.getHeader("content-type" -// ); -//// -// post请求目前获取userFlag,user参数只支持multipart/form-data,application/json,对于其他方式不记录用户信息 -// if -// (conType.contains("multipart/form-data") || conType.contains("application/json" -// )) { -// if -// (conType.contains("multipart/form-data" -// )) { reqBody -// MultiPartFormDateToJson.formDateToJson(reqBody); } -//// -// 默认content-type传json-->application/json -// Object userObject; Object invokeUserObject; JSONObject jsonObject -// JSONObject.parseObject(reqBody); userObject -// = jsonObject.get("userFlag" -// ); -// if -// null -// != -// userObject) { user -// Integer.parseInt(userObject.toString()); } -//else -// { logger.warn( -// "当前请求缺少userFlag" -// ); } invokeUserObject -// = jsonObject.get("user" -// ); -// if -// null -// != -// userObject) { invokeUser -// invokeUserObject.toString(); } -//else -// { logger.warn( -// "当前请求缺少user" -// ); } -//// -// logger.info("reqBody:={}" + reqBody); -// } } } -//// -// 打印response -// InputStream out = -// ctx.getResponseDataStream(); String outBody -// = StreamUtils.copyToString(out, Charset.forName("UTF-8" -// )); -// boolean -// result = -// false -// if -// (outBody != -// null -// && "" != -// outBody) { JSONObject jsonObject -// JSONObject.parseObject(outBody); Object dataFlagObject -// = jsonObject.get("dataFlag" -// ); -// if -// null -// != -// dataFlagObject) { -// int -// flag = -// Integer.parseInt(dataFlagObject.toString()); -// if -// (flag == 1 -// ) { result -// true -// ; } } -//// -// logger.info("响应参数:={}" + outBody); -//// -// 必须重新写入流 -//// -// 重要!!! -// ctx.setResponseBody(outBody); InvokeLogModel logModel -// new -// InvokeLogModel(); logModel.setUid(user); logModel.setInvokeUser(invokeUser); logModel.setInterfaceName(interfaceMethod); logModel.setInterfaceMethod(method); logModel.setInvokeStartTime( -// new -// Date()); logModel.setInvokeEndTime( -// null -// ); logModel.setRequestParam(reqBody); logModel.setResponseResult(result); logModel.setResponseBody(outBody); invokeLogService.insertInvokerLog(logModel); } -//catch -// (IOException e) { logger.error( -// "LogRecode IO异常" -// , e); } -// return -// null -// ; } String printArray(String[] arr) { StringBuilder sb -// new -// StringBuilder(); -// for -// int -// i = 0; i < arr.length; i++ -//) { sb.append(arr[i]); -// if -// (i < arr.length - 1 -// ) { sb.append( -// "," -// ); } } -// return -// sb.toString(); } } \ No newline at end of file diff --git a/spring-cloud-learn/eureka-client/src/main/resources/application-dev.properties b/spring-cloud-learn/eureka-client/src/main/resources/application-dev.properties deleted file mode 100644 index 0ca6bddc..00000000 --- a/spring-cloud-learn/eureka-client/src/main/resources/application-dev.properties +++ /dev/null @@ -1,4 +0,0 @@ -server.port=2002 - -#\u7761\u7720\u65F6\u95F4 -custom.time.sleep=120000 \ No newline at end of file diff --git a/spring-cloud-learn/eureka-client/src/main/resources/application-prod.properties b/spring-cloud-learn/eureka-client/src/main/resources/application-prod.properties deleted file mode 100644 index 6e5e0332..00000000 --- a/spring-cloud-learn/eureka-client/src/main/resources/application-prod.properties +++ /dev/null @@ -1,7 +0,0 @@ -server.port=2004 - -#\u7761\u7720\u65F6\u95F4 -custom.time.sleep=1000 - - - diff --git a/spring-cloud-learn/eureka-client/src/main/resources/application-test.properties b/spring-cloud-learn/eureka-client/src/main/resources/application-test.properties deleted file mode 100644 index ba1d31a4..00000000 --- a/spring-cloud-learn/eureka-client/src/main/resources/application-test.properties +++ /dev/null @@ -1,4 +0,0 @@ -server.port=2003 - -#\u7761\u7720\u65F6\u95F4 -custom.time.sleep=120000 \ No newline at end of file diff --git a/spring-cloud-learn/eureka-client/src/main/resources/application.properties b/spring-cloud-learn/eureka-client/src/main/resources/application.properties deleted file mode 100644 index 5933ac9f..00000000 --- a/spring-cloud-learn/eureka-client/src/main/resources/application.properties +++ /dev/null @@ -1,19 +0,0 @@ -spring.application.name=eureka-client -server.port=2001 -#eureka.client.healthcheck.enabled=true -eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ - -#\u4F7F\u7528ip\u66FF\u4EE3\u5B9E\u4F8B\u540D -eureka.instance.prefer-ip-address=true -#\u8BBE\u7F6E\u5B9E\u4F8B\u7684ID\u4E3A\uFF1A \u670D\u52A1\u540D:port -eureka.instance.instance-id=${spring.application.name}:${server.port} - -#\u7761\u7720\u65F6\u95F4 -custom.time.sleep=1000 - -# \u628Aip\u6CE8\u518C\u5230eureka\u4E2D\uFF0C\u89E3\u51B3\u8F6C\u53D1\u7684\u65F6\u5019UnknownHostException -#eureka.instance.prefer-ip-address=true -#eureka.client.serviceUrl.defaultZone=http://192.168.1.102:1000/eureka/ - - - diff --git a/spring-cloud-learn/eureka-client/src/main/resources/logback.xml b/spring-cloud-learn/eureka-client/src/main/resources/logback.xml deleted file mode 100644 index 8832ac27..00000000 --- a/spring-cloud-learn/eureka-client/src/main/resources/logback.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/spring-cloud-learn/eureka-consumer-feign/pom.xml b/spring-cloud-learn/eureka-consumer-feign/pom.xml deleted file mode 100644 index adc915d1..00000000 --- a/spring-cloud-learn/eureka-consumer-feign/pom.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - 4.0.0 - - com.cpq - eureka-consumer-feign - 0.0.1-SNAPSHOT - jar - - eureka-consumer-feign - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.4.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.cloud - spring-cloud-starter-eureka - - - - org.springframework.cloud - spring-cloud-starter-feign - - - - org.springframework.boot - spring-boot-starter-test - - - org.springframework - spring-mock - 2.0.8 - - - - - io.github.openfeign.form - feign-form - 3.0.3 - - - io.github.openfeign.form - feign-form-spring - 3.0.3 - - - commons-fileupload - commons-fileupload - 1.3.3 - - - - - - - - - org.springframework.cloud - spring-cloud-dependencies - Dalston.SR1 - pom - import - - - - - diff --git a/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/EurekaConsumerFeignApplication.java b/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/EurekaConsumerFeignApplication.java deleted file mode 100644 index 2f7ff0fd..00000000 --- a/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/EurekaConsumerFeignApplication.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.cpq.eurekaconsumerfeign; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.cloud.netflix.feign.EnableFeignClients; - -@EnableFeignClients -@EnableDiscoveryClient -@SpringBootApplication -public class EurekaConsumerFeignApplication { - - public static void main(String[] args) { - SpringApplication.run(EurekaConsumerFeignApplication.class, args); - } - -} diff --git a/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/controller/DcController.java b/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/controller/DcController.java deleted file mode 100644 index 25497537..00000000 --- a/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/controller/DcController.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.cpq.eurekaconsumerfeign.controller; - -import com.cpq.eurekaconsumerfeign.service.DcClient; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -import java.util.Map; - -@RestController -public class DcController { - - @Autowired - DcClient dcClient; - - @GetMapping("/consumer") - public String dc(){ - return dcClient.consumer(); - } - - @PostMapping("/p1") - public Map p1(@RequestBody Map map){ - System.out.println(map.toString()); - return map; - } - -} diff --git a/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/service/DcClient.java b/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/service/DcClient.java deleted file mode 100644 index 10d8f5c7..00000000 --- a/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/service/DcClient.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.cpq.eurekaconsumerfeign.service; - -import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; - -@FeignClient("eureka-client") -public interface DcClient { - - @GetMapping("/dc") - String consumer(); - -} diff --git a/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/service/UploadService.java b/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/service/UploadService.java deleted file mode 100644 index 208e8554..00000000 --- a/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/service/UploadService.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.cpq.eurekaconsumerfeign.service; - -import feign.codec.Encoder; -import feign.form.spring.SpringFormEncoder; -import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.multipart.MultipartFile; - -@FeignClient(value = "eureka-client", configuration = UploadService.MultipartSupportConfig.class) -public interface UploadService { - - @PostMapping(value = "/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - String handleFileUpload(@RequestPart(value = "file")MultipartFile file); - - @Configuration - class MultipartSupportConfig { - @Bean - public Encoder feignFormEncoder() { - return new SpringFormEncoder(); - } - } - -} diff --git a/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/service/UploadServiceTest.java b/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/service/UploadServiceTest.java deleted file mode 100644 index 75a7a31c..00000000 --- a/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/service/UploadServiceTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.cpq.eurekaconsumerfeign.service; - - -import org.apache.commons.io.IOUtils; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.web.multipart.MultipartFile; - -import java.io.File; -import java.io.FileInputStream; - -@RunWith(SpringJUnit4ClassRunner.class) -@SpringBootTest -public class UploadServiceTest { - - @Autowired - private UploadService uploadService; - - @Test - public void handleFileUpload() throws Exception{ - String path = Thread.currentThread().getContextClassLoader().getResource("").getPath(); - File file = new File(path+"application.properties"); - FileInputStream input = new FileInputStream(file); - MultipartFile multipartFile = new MockMultipartFile("file-name", file.getName(), "text/plain", IOUtils.toByteArray(input)); - - System.out.println("**************"+uploadService.handleFileUpload(multipartFile)); - } -} diff --git a/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/service/UploadServiceTestFile.java b/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/service/UploadServiceTestFile.java deleted file mode 100644 index 491f654f..00000000 --- a/spring-cloud-learn/eureka-consumer-feign/src/main/java/com/cpq/eurekaconsumerfeign/service/UploadServiceTestFile.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.cpq.eurekaconsumerfeign.service; - - -import org.junit.Test; - -public class UploadServiceTestFile { - - @Test - public void handleFileUpload() throws Exception{ - System.out.println("**************************"); - } -} diff --git a/spring-cloud-learn/eureka-consumer-feign/src/main/resources/application.properties b/spring-cloud-learn/eureka-consumer-feign/src/main/resources/application.properties deleted file mode 100644 index d4983446..00000000 --- a/spring-cloud-learn/eureka-consumer-feign/src/main/resources/application.properties +++ /dev/null @@ -1,4 +0,0 @@ -spring.application.name=eureka-consumer-feign -server.port=2103 - -eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ \ No newline at end of file diff --git a/spring-cloud-learn/eureka-consumer-ribbon-hystrix/.gitignore b/spring-cloud-learn/eureka-consumer-ribbon-hystrix/.gitignore deleted file mode 100644 index 82eca336..00000000 --- a/spring-cloud-learn/eureka-consumer-ribbon-hystrix/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/build/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ \ No newline at end of file diff --git a/spring-cloud-learn/eureka-consumer-ribbon-hystrix/pom.xml b/spring-cloud-learn/eureka-consumer-ribbon-hystrix/pom.xml deleted file mode 100644 index 43bf9b60..00000000 --- a/spring-cloud-learn/eureka-consumer-ribbon-hystrix/pom.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - 4.0.0 - - com.cpq - eureka-consumer-ribbon-hystrix - 0.0.1-SNAPSHOT - jar - - eureka-consumer-ribbon-hystrix - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.4.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.cloud - spring-cloud-starter-eureka - - - - org.springframework.cloud - spring-cloud-starter-ribbon - - - - org.springframework.cloud - spring-cloud-starter-hystrix - - - - org.springframework.boot - spring-boot-starter-actuator - - - - - - - - org.springframework.cloud - spring-cloud-dependencies - Dalston.SR1 - pom - import - - - - - - diff --git a/spring-cloud-learn/eureka-consumer-ribbon-hystrix/src/main/java/com/cpq/eurekaconsumerribbonhystrix/EurekaConsumerRibbonHystrixApplication.java b/spring-cloud-learn/eureka-consumer-ribbon-hystrix/src/main/java/com/cpq/eurekaconsumerribbonhystrix/EurekaConsumerRibbonHystrixApplication.java deleted file mode 100644 index 2f3dab62..00000000 --- a/spring-cloud-learn/eureka-consumer-ribbon-hystrix/src/main/java/com/cpq/eurekaconsumerribbonhystrix/EurekaConsumerRibbonHystrixApplication.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.cpq.eurekaconsumerribbonhystrix; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; - -@EnableCircuitBreaker -@EnableDiscoveryClient -@SpringBootApplication -public class EurekaConsumerRibbonHystrixApplication { - - @Bean - @LoadBalanced - public RestTemplate restTemplate(){ - return new RestTemplate(); - } - - public static void main(String[] args) { - SpringApplication.run(EurekaConsumerRibbonHystrixApplication.class, args); - } -} diff --git a/spring-cloud-learn/eureka-consumer-ribbon-hystrix/src/main/java/com/cpq/eurekaconsumerribbonhystrix/controller/DcController.java b/spring-cloud-learn/eureka-consumer-ribbon-hystrix/src/main/java/com/cpq/eurekaconsumerribbonhystrix/controller/DcController.java deleted file mode 100644 index 2649351f..00000000 --- a/spring-cloud-learn/eureka-consumer-ribbon-hystrix/src/main/java/com/cpq/eurekaconsumerribbonhystrix/controller/DcController.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.cpq.eurekaconsumerribbonhystrix.controller; - -import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; - -@RestController -public class DcController { - - @Autowired - ConsumerService consumerService; - - @GetMapping("/consumer") - public String dc(){ - return consumerService.consumer(); - } - - @Component - class ConsumerService { - @Autowired - RestTemplate restTemplate; - - @HystrixCommand(fallbackMethod = "fallback") - public String consumer(){ - /** getForObject方法 - * T getForObject(URI url, Class responseType) - * T getForObject(String url, Class responseType, MapString< String, ?> urlVariables) - * T getForObject(String url, Class responseType, Object… urlVariables) - */ - return restTemplate.getForObject("http://eureka-client/dc", String.class); - } - - public String fallback() { - return "请求出错,调用降级处理函数"; - } - } - -} diff --git a/spring-cloud-learn/eureka-consumer-ribbon-hystrix/src/main/resources/application.properties b/spring-cloud-learn/eureka-consumer-ribbon-hystrix/src/main/resources/application.properties deleted file mode 100644 index 0452c469..00000000 --- a/spring-cloud-learn/eureka-consumer-ribbon-hystrix/src/main/resources/application.properties +++ /dev/null @@ -1,4 +0,0 @@ -spring.application.name=eureka-consumer-ribbon-hystrix -server.port=2102 - -eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ \ No newline at end of file diff --git a/spring-cloud-learn/eureka-consumer-ribbon/pom.xml b/spring-cloud-learn/eureka-consumer-ribbon/pom.xml deleted file mode 100644 index 4bca37ec..00000000 --- a/spring-cloud-learn/eureka-consumer-ribbon/pom.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - 4.0.0 - - com.cpq - eureka-consumer-ribbon - 0.0.1-SNAPSHOT - jar - - eureka-consumer-ribbon - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.4.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.cloud - spring-cloud-starter-eureka - - - - org.springframework.cloud - spring-cloud-starter-ribbon - - - - - - - - org.springframework.cloud - spring-cloud-dependencies - Dalston.SR1 - pom - import - - - - - - diff --git a/spring-cloud-learn/eureka-consumer-ribbon/src/main/java/com/cpq/eurekaconsumerribbon/EurekaConsumerRibbonApplication.java b/spring-cloud-learn/eureka-consumer-ribbon/src/main/java/com/cpq/eurekaconsumerribbon/EurekaConsumerRibbonApplication.java deleted file mode 100644 index f793ba54..00000000 --- a/spring-cloud-learn/eureka-consumer-ribbon/src/main/java/com/cpq/eurekaconsumerribbon/EurekaConsumerRibbonApplication.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.cpq.eurekaconsumerribbon; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; - -@EnableDiscoveryClient -@SpringBootApplication -public class EurekaConsumerRibbonApplication { - - @Bean - @LoadBalanced - public RestTemplate restTemplate() { - return new RestTemplate(); - } - - public static void main(String[] args) { - SpringApplication.run(EurekaConsumerRibbonApplication.class, args); - } -} diff --git a/spring-cloud-learn/eureka-consumer-ribbon/src/main/java/com/cpq/eurekaconsumerribbon/controller/DcController.java b/spring-cloud-learn/eureka-consumer-ribbon/src/main/java/com/cpq/eurekaconsumerribbon/controller/DcController.java deleted file mode 100644 index 3cc2b660..00000000 --- a/spring-cloud-learn/eureka-consumer-ribbon/src/main/java/com/cpq/eurekaconsumerribbon/controller/DcController.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.cpq.eurekaconsumerribbon.controller; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; - -@RestController -public class DcController { - - @Autowired - RestTemplate restTemplate; - - @GetMapping("/consumer") - public String dc(){ - return restTemplate.getForObject("http://eureka-client/dc", String.class); - } - -} diff --git a/spring-cloud-learn/eureka-consumer-ribbon/src/main/resources/application.properties b/spring-cloud-learn/eureka-consumer-ribbon/src/main/resources/application.properties deleted file mode 100644 index 1828c159..00000000 --- a/spring-cloud-learn/eureka-consumer-ribbon/src/main/resources/application.properties +++ /dev/null @@ -1,4 +0,0 @@ -spring.application.name=eureka-consumer-ribbon -server.port=2102 - -eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ \ No newline at end of file diff --git a/spring-cloud-learn/eureka-consumer/pom.xml b/spring-cloud-learn/eureka-consumer/pom.xml deleted file mode 100644 index 60010927..00000000 --- a/spring-cloud-learn/eureka-consumer/pom.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - 4.0.0 - - com.cpq - eureka-consumer - 0.0.1-SNAPSHOT - jar - - eureka-consumer - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.13.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.cloud - spring-cloud-starter-eureka - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.cloud - spring-cloud-starter-config - - - - - org.springframework.boot - spring-boot-starter-actuator - - - - - - - org.springframework.cloud - spring-cloud-dependencies - Edgware.SR4 - pom - import - - - - - - diff --git a/spring-cloud-learn/eureka-consumer/src/main/java/com/cpq/eurekaconsumer/EurekaConsumerApplication.java b/spring-cloud-learn/eureka-consumer/src/main/java/com/cpq/eurekaconsumer/EurekaConsumerApplication.java deleted file mode 100644 index 36817520..00000000 --- a/spring-cloud-learn/eureka-consumer/src/main/java/com/cpq/eurekaconsumer/EurekaConsumerApplication.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.cpq.eurekaconsumer; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; - -@EnableDiscoveryClient -@SpringBootApplication -public class EurekaConsumerApplication { - - @Bean - @LoadBalanced - public RestTemplate restTemplateBalanced(){ - return new RestTemplate(); - } - - @Bean - public RestTemplate restTemplate(){ - return new RestTemplate(); - } - - public static void main(String[] args) { - SpringApplication.run(EurekaConsumerApplication.class, args); - } -} diff --git a/spring-cloud-learn/eureka-consumer/src/main/java/com/cpq/eurekaconsumer/controller/DcController.java b/spring-cloud-learn/eureka-consumer/src/main/java/com/cpq/eurekaconsumer/controller/DcController.java deleted file mode 100644 index 9c9d7059..00000000 --- a/spring-cloud-learn/eureka-consumer/src/main/java/com/cpq/eurekaconsumer/controller/DcController.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.cpq.eurekaconsumer.controller; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; - -@RestController -public class DcController { - - @Autowired - LoadBalancerClient loadBalancerClient; - @Autowired - RestTemplate restTemplate; - @Autowired - RestTemplate restTemplateBalanced; - - // http://localhost:2101/consumer - @GetMapping("/consumer") - public String dc(){ - ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client"); - String url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/dc"; - System.out.println(url); - - //有负载的的RestTemplate,URL的http://被治理的服务名称/controller路径 - restTemplateBalanced.getForObject("http://eureka-client/dc",String.class); - - //没有负载的RestTemplate,URL的http://被治理的服务 或者 host:port /controller路径 - return restTemplate.getForObject(url, String.class); - } - -} diff --git a/spring-cloud-learn/eureka-consumer/src/main/java/com/cpq/eurekaconsumer/controller/TestController.java b/spring-cloud-learn/eureka-consumer/src/main/java/com/cpq/eurekaconsumer/controller/TestController.java deleted file mode 100644 index a4697f7f..00000000 --- a/spring-cloud-learn/eureka-consumer/src/main/java/com/cpq/eurekaconsumer/controller/TestController.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.cpq.eurekaconsumer.controller; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletRequest; - -@RestController -@RequestMapping("/eureka-consumer") -public class TestController { - - @Value("${common.item}") - String commonItem; - @Value("${common.eureka-consumer}") - String commonEurekaConsumer; - @Value("${custom.param1}") - String customParam1; - - @GetMapping("/dc") - public String dc(HttpServletRequest request) throws Exception{ - System.out.println("******* common.item: "+commonItem); - System.out.println("******* customParam1: "+customParam1); - System.out.println("******* commonEurekaConsumer: "+commonEurekaConsumer); - return commonItem + " " + customParam1 + " " + commonEurekaConsumer; - } - -} diff --git a/spring-cloud-learn/eureka-consumer/src/main/resources/bootstrap.properties b/spring-cloud-learn/eureka-consumer/src/main/resources/bootstrap.properties deleted file mode 100644 index 6c90c269..00000000 --- a/spring-cloud-learn/eureka-consumer/src/main/resources/bootstrap.properties +++ /dev/null @@ -1,13 +0,0 @@ -#\u4ECE\u914D\u7F6E\u4E2D\u5FC3\u670D\u52A1\u7AEF\u83B7\u53D6git\u4E0A\u7684\u914D\u7F6E\uFF0C\u5FC5\u987B\u4F7F\u7528bootstrap.yml - -spring.cloud.config.uri=http://localhost:1201/ -# \u83B7\u53D6git\u4ED3\u5E93\u4E2Deureka-consumer\u6587\u4EF6\u5939\u4E2D\u7684\u914D\u7F6E\u3002 -# \u914D\u7F6E\u4E86spring.cloud.config.name\uFF0C\u5C31\u8BFB\u53D6git\u4E2D\u7684spring.cloud.config.name\u6587\u4EF6\u5939 -# \u6CA1\u914D\u7F6Espring.cloud.config.name\uFF0C\u5C31\u9700\u8981\u914D\u7F6Espring.application.name=eureka-consumer\uFF0C\u7ED3\u679C\u4E5F\u662F\u8BFB\u53D6git\u4ED3\u5E93\u7684eureka-consumer\u6587\u4EF6\u5939\u4E2D\u7684\u914D\u7F6E -# \u5E94\u8BE5\u662F\u83B7\u53D6 http://localhost:1201/eureka-consumer/dev/master \u8FD9\u4E2Aurl\u7684\u7ED3\u679C -spring.cloud.config.name=eureka-consumer -#\u914D\u7F6E\u6587\u4EF6\u7684profile -spring.cloud.config.profile=dev -spring.cloud.config.label=master - - diff --git a/spring-cloud-learn/eureka-server/pom.xml b/spring-cloud-learn/eureka-server/pom.xml deleted file mode 100644 index cc5c4bca..00000000 --- a/spring-cloud-learn/eureka-server/pom.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - 4.0.0 - - com.cpq - eureka-server - 0.0.1-SNAPSHOT - jar - - eureka-server - Demo project for Spring Boot eurreka - - - org.springframework.boot - spring-boot-starter-parent - 1.5.13.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.cloud - spring-cloud-starter-eureka-server - - - - - - - org.springframework.cloud - spring-cloud-dependencies - Edgware.SR4 - pom - import - - - - - diff --git a/spring-cloud-learn/eureka-server/readme.txt b/spring-cloud-learn/eureka-server/readme.txt deleted file mode 100644 index 7cb7a852..00000000 --- a/spring-cloud-learn/eureka-server/readme.txt +++ /dev/null @@ -1 +0,0 @@ -https://github.com/dyc87112/SpringCloud-Learning \ No newline at end of file diff --git a/spring-cloud-learn/eureka-server/src/main/java/com/cpq/eureka/EurekaServerApplication.java b/spring-cloud-learn/eureka-server/src/main/java/com/cpq/eureka/EurekaServerApplication.java deleted file mode 100644 index 925d474b..00000000 --- a/spring-cloud-learn/eureka-server/src/main/java/com/cpq/eureka/EurekaServerApplication.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.cpq.eureka; - - -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; - -@EnableEurekaServer -@SpringBootApplication -public class EurekaServerApplication { - - public static void main(String[] args) { - new SpringApplicationBuilder(EurekaServerApplication.class).web(true).run(args); - } -} diff --git a/spring-cloud-learn/eureka-server/src/main/resources/application.properties b/spring-cloud-learn/eureka-server/src/main/resources/application.properties deleted file mode 100644 index e0554164..00000000 --- a/spring-cloud-learn/eureka-server/src/main/resources/application.properties +++ /dev/null @@ -1,19 +0,0 @@ -#\u670D\u52A1\u540D -spring.application.name=eureka-server -#\u670D\u52A1\u7AEF\u53E3 -server.port=1000 - -eureka.instance.hostname=localhost -#\u662F\u5426\u5C06\u81EA\u5DF1\u6CE8\u518C\u5230eureka\u670D\u52A1\u4E2D\uFF0C\u9ED8\u8BA4\u662Ftrue -eureka.client.register-with-eureka=false -#\u662F\u5426\u4ECEEureka\u4E2D\u83B7\u53D6\u6CE8\u518C\u4FE1\u606F\uFF0C\u9ED8\u8BA4\u662Ftrue -eureka.client.fetch-registry=false - -#\u914D\u7F6EdefaultZone \u8986\u76D6\u5176\u9ED8\u8BA4\u503C\uFF0C\u907F\u514DConnect to localhost:8761 time out -eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ - -#\u5173\u95ED\u6CE8\u518C\u4E2D\u5FC3\u81EA\u6211\u4FDD\u62A4\u673A\u5236\u3002\u670D\u52A1\u5B9E\u4F8Bdown\u6389\uFF0C\u5219\u4E0B\u7EBF\u5B9E\u4F8B -eureka.server.enable-self-preservation=false -#\u6CE8\u518C\u4E2D\u5FC3\u6E05\u7406\u95F4\u9694\uFF08\u5355\u4F4D\u6BEB\u79D2\uFF0C\u9ED8\u8BA460*1000\uFF09\uFF0C\u4E0D\u8D77\u4F5C\u7528 -eureka.server.eviction-interval-timer-in-ms=20000 -# \u5BA2\u6237\u7AEF\u52A0\u5165\u6B64\u914D\u7F6E eureka.client.healthcheck.enabled=true \ No newline at end of file diff --git a/spring-cloud-learn/eureka-server/src/main/resources/logback.xml b/spring-cloud-learn/eureka-server/src/main/resources/logback.xml deleted file mode 100644 index 8832ac27..00000000 --- a/spring-cloud-learn/eureka-server/src/main/resources/logback.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/spring-cloud-learn/hystrix-dashboard/.gitignore b/spring-cloud-learn/hystrix-dashboard/.gitignore deleted file mode 100644 index 82eca336..00000000 --- a/spring-cloud-learn/hystrix-dashboard/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/build/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ \ No newline at end of file diff --git a/spring-cloud-learn/hystrix-dashboard/pom.xml b/spring-cloud-learn/hystrix-dashboard/pom.xml deleted file mode 100644 index a356e717..00000000 --- a/spring-cloud-learn/hystrix-dashboard/pom.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - 4.0.0 - - com.cpq - hystrix-dashboard - 0.0.1-SNAPSHOT - jar - - hystrix-dashboard - Demo project for Spring Boot - - - UTF-8 - UTF-8 - 1.8 - - - - org.springframework.cloud - spring-cloud-starter-parent - Dalston.SR1 - - - - - org.springframework.cloud - spring-cloud-starter-hystrix - - - org.springframework.cloud - spring-cloud-starter-hystrix-dashboard - - - org.springframework.boot - spring-boot-starter-actuator - - - - diff --git a/spring-cloud-learn/hystrix-dashboard/src/main/java/com/cpq/hystrixdashboard/HystrixDashboardApplication.java b/spring-cloud-learn/hystrix-dashboard/src/main/java/com/cpq/hystrixdashboard/HystrixDashboardApplication.java deleted file mode 100644 index 27899254..00000000 --- a/spring-cloud-learn/hystrix-dashboard/src/main/java/com/cpq/hystrixdashboard/HystrixDashboardApplication.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.cpq.hystrixdashboard; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; - -@EnableHystrixDashboard -@SpringBootApplication -public class HystrixDashboardApplication { - - public static void main(String[] args) { - SpringApplication.run(HystrixDashboardApplication.class, args); - } -} diff --git a/spring-cloud-learn/hystrix-dashboard/src/main/resources/application.properties b/spring-cloud-learn/hystrix-dashboard/src/main/resources/application.properties deleted file mode 100644 index 1af11bd0..00000000 --- a/spring-cloud-learn/hystrix-dashboard/src/main/resources/application.properties +++ /dev/null @@ -1,2 +0,0 @@ -spring.application.name=hystrix-dashboard -server.port=1301 \ No newline at end of file diff --git a/spring-cloud-learn/rabbitmq-hello/.gitignore b/spring-cloud-learn/rabbitmq-hello/.gitignore deleted file mode 100644 index 82eca336..00000000 --- a/spring-cloud-learn/rabbitmq-hello/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/build/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ \ No newline at end of file diff --git a/spring-cloud-learn/rabbitmq-hello/readme.txt b/spring-cloud-learn/rabbitmq-hello/readme.txt deleted file mode 100644 index 205d39f1..00000000 --- a/spring-cloud-learn/rabbitmq-hello/readme.txt +++ /dev/null @@ -1,21 +0,0 @@ -直接安装Erland、RabbitMQ - - -安装界面管理插件 - 在开始菜单找到RabbitMQ Command Prompt - 输入:rabbitmq-plugins enable rabbitmq_management命令安装插件 - -启动Rabbitmq - 在菜单中找到 RabbitMQ Service - start 启动 - 在菜单中找到 RabbitMQ Service - stop 停止 - - -启动成功后,浏览器中输入http://localhost:15672,可以看到管理界面, -默认用户密码是:guest/guest - - - -window版本 -打开 RabbitMQ Command Prompt -执行 rabbitmq-server.bat -http://localhost:15672/#/ \ No newline at end of file diff --git a/spring-cloud-learn/rabbitmq-hello/src/main/java/com/cpq/rabbitmqhello/rabbitmq/RabbitConfig.java b/spring-cloud-learn/rabbitmq-hello/src/main/java/com/cpq/rabbitmqhello/rabbitmq/RabbitConfig.java deleted file mode 100644 index 12991406..00000000 --- a/spring-cloud-learn/rabbitmq-hello/src/main/java/com/cpq/rabbitmqhello/rabbitmq/RabbitConfig.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.cpq.rabbitmqhello.rabbitmq; - -import org.springframework.amqp.core.Queue; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class RabbitConfig { - - @Bean - public Queue helloQueue() { - return new Queue("hello"); - } - -} \ No newline at end of file diff --git a/spring-cloud-learn/rabbitmq-hello/src/main/java/com/cpq/rabbitmqhello/rabbitmq/Receiver.java b/spring-cloud-learn/rabbitmq-hello/src/main/java/com/cpq/rabbitmqhello/rabbitmq/Receiver.java deleted file mode 100644 index f18baf9e..00000000 --- a/spring-cloud-learn/rabbitmq-hello/src/main/java/com/cpq/rabbitmqhello/rabbitmq/Receiver.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.cpq.rabbitmqhello.rabbitmq; - -import org.springframework.amqp.rabbit.annotation.RabbitHandler; -import org.springframework.amqp.rabbit.annotation.RabbitListener; -import org.springframework.stereotype.Component; - -@Component -@RabbitListener(queues = "hello") -public class Receiver { - - @RabbitHandler - public void process(String hello) { - System.out.println("消息接收者Receiver(在主控制台查看) : " + hello); - } - -} diff --git a/spring-cloud-learn/rabbitmq-hello/src/main/java/com/cpq/rabbitmqhello/rabbitmq/Sender.java b/spring-cloud-learn/rabbitmq-hello/src/main/java/com/cpq/rabbitmqhello/rabbitmq/Sender.java deleted file mode 100644 index fd5411e0..00000000 --- a/spring-cloud-learn/rabbitmq-hello/src/main/java/com/cpq/rabbitmqhello/rabbitmq/Sender.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.cpq.rabbitmqhello.rabbitmq; - -import org.springframework.amqp.core.AmqpTemplate; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.Date; - -@Component -public class Sender { - - @Autowired - private AmqpTemplate rabbitTemplate; - - public void send() { - String context = "hello " + new Date(); - System.out.println("消息生产者Sender : " + context); - this.rabbitTemplate.convertAndSend("hello", context); - } - -} \ No newline at end of file diff --git a/spring-cloud-learn/rabbitmq-hello/src/main/resources/application.properties b/spring-cloud-learn/rabbitmq-hello/src/main/resources/application.properties deleted file mode 100644 index af8245a6..00000000 --- a/spring-cloud-learn/rabbitmq-hello/src/main/resources/application.properties +++ /dev/null @@ -1,6 +0,0 @@ -spring.application.name=rabbitmq-hello - -spring.rabbitmq.host=localhost -spring.rabbitmq.port=5672 -spring.rabbitmq.username=guest -spring.rabbitmq.password=guest \ No newline at end of file diff --git a/spring-cloud-learn/rabbitmq-hello/src/test/java/com/cpq/rabbitmqhello/RabbitmqHelloApplicationTests.java b/spring-cloud-learn/rabbitmq-hello/src/test/java/com/cpq/rabbitmqhello/RabbitmqHelloApplicationTests.java deleted file mode 100644 index 46dc0628..00000000 --- a/spring-cloud-learn/rabbitmq-hello/src/test/java/com/cpq/rabbitmqhello/RabbitmqHelloApplicationTests.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.cpq.rabbitmqhello; - -import com.cpq.rabbitmqhello.rabbitmq.Sender; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = RabbitmqHelloApplication.class) //@SpringBootTest为最新 -public class RabbitmqHelloApplicationTests { - - @Autowired - private Sender sender; - - @Test - public void contextLoads() { - sender.send(); - } - -} diff --git a/spring-cloud-learn/s2-api-gateway/pom.xml b/spring-cloud-learn/s2-api-gateway/pom.xml deleted file mode 100644 index 54344ddd..00000000 --- a/spring-cloud-learn/s2-api-gateway/pom.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.0.6.RELEASE - - - com.example - s2-api-gateway - 0.0.1-SNAPSHOT - s2-api-gateway - Demo project for Spring Boot - - - 1.8 - - - - - org.springframework.cloud - spring-cloud-starter-consul-discovery - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-webflux - - - org.springframework.cloud - spring-cloud-starter-gateway - - - org.springframework.cloud - spring-cloud-starter-netflix-ribbon - - - org.isomorphism - token-bucket - 1.7 - - - - - - - - org.springframework.cloud - spring-cloud-dependencies - Finchley.SR1 - pom - import - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-cloud-learn/s2-api-gateway/src/main/java/com/example/s2apigateway/AuthFilter.java b/spring-cloud-learn/s2-api-gateway/src/main/java/com/example/s2apigateway/AuthFilter.java deleted file mode 100644 index 4630f31c..00000000 --- a/spring-cloud-learn/s2-api-gateway/src/main/java/com/example/s2apigateway/AuthFilter.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.example.s2apigateway; - -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; - -/** - * @Description - * @Author chenpiqian - * @Date: 2019-03-27 - */ -@Component -public class AuthFilter implements GlobalFilter { - /** - * Process the Web request and (optionally) delegate to the next - * {@code WebFilter} through the given {@link GatewayFilterChain}. - * - * @param exchange the current server exchange - * @param chain provides a way to delegate to the next filter - * @return {@code Mono} to indicate when request processing is complete - */ - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - System.out.println("进入AuthFilter"); - return chain.filter(exchange); - - //boolean isAllow = true; - //if (isAllow) { - // return chain.filter(exchange); - // - //} else { - // //设置status和body - // return Mono.defer(() -> { - // //setResponseStatus(exchange, HttpStatus.UNAUTHORIZED); - // final ServerHttpResponse response = exchange.getResponse(); - // byte[] bytes = "Hello World".getBytes(StandardCharsets.UTF_8); - // DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes); - // response.getHeaders().set("aaa", "bbb"); - // return response.writeWith(Flux.just(buffer)); - // }); - //} - - } -} \ No newline at end of file diff --git a/spring-cloud-learn/s2-api-gateway/src/main/java/com/example/s2apigateway/CheckController.java b/spring-cloud-learn/s2-api-gateway/src/main/java/com/example/s2apigateway/CheckController.java deleted file mode 100644 index 7ddcdea8..00000000 --- a/spring-cloud-learn/s2-api-gateway/src/main/java/com/example/s2apigateway/CheckController.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.s2apigateway; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * @Description - * @Author chenpiqian - * @Date: 2019-03-27 - */ -@RestController -public class CheckController { - @GetMapping("check") - public String check(){ - System.out.println("**************ok1111"); - - return "ok1"; - } -} diff --git a/spring-cloud-learn/s2-api-gateway/src/main/java/com/example/s2apigateway/S2ApiGatewayApplication.java b/spring-cloud-learn/s2-api-gateway/src/main/java/com/example/s2apigateway/S2ApiGatewayApplication.java deleted file mode 100644 index 2bf636ce..00000000 --- a/spring-cloud-learn/s2-api-gateway/src/main/java/com/example/s2apigateway/S2ApiGatewayApplication.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.s2apigateway; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; - - -@SpringBootApplication -@EnableDiscoveryClient -public class S2ApiGatewayApplication { - - public static void main(String[] args) { - SpringApplication.run(S2ApiGatewayApplication.class, args); - } - - - - //@Bean - //public RouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient) { - // return new DiscoveryClientRouteDefinitionLocator(discoveryClient); - //} - -} diff --git a/spring-cloud-learn/s2-api-gateway/src/main/java/com/example/s2apigateway/ThrottleGatewayFilter.java b/spring-cloud-learn/s2-api-gateway/src/main/java/com/example/s2apigateway/ThrottleGatewayFilter.java deleted file mode 100644 index e3ba8f33..00000000 --- a/spring-cloud-learn/s2-api-gateway/src/main/java/com/example/s2apigateway/ThrottleGatewayFilter.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2013-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.s2apigateway; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.isomorphism.util.TokenBucket; -import org.isomorphism.util.TokenBuckets; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; - -import java.util.concurrent.TimeUnit; - -/** - * Sample throttling filter. See https://github.com/bbeck/token-bucket - */ -@Component -public class ThrottleGatewayFilter implements GatewayFilter { - - private static final Log log = LogFactory.getLog(ThrottleGatewayFilter.class); - - int capacity; - - int refillTokens; - - int refillPeriod; - - TimeUnit refillUnit; - - public int getCapacity() { - return capacity; - } - - public ThrottleGatewayFilter setCapacity(int capacity) { - this.capacity = capacity; - return this; - } - - public int getRefillTokens() { - return refillTokens; - } - - public ThrottleGatewayFilter setRefillTokens(int refillTokens) { - this.refillTokens = refillTokens; - return this; - } - - public int getRefillPeriod() { - return refillPeriod; - } - - public ThrottleGatewayFilter setRefillPeriod(int refillPeriod) { - this.refillPeriod = refillPeriod; - return this; - } - - public TimeUnit getRefillUnit() { - return refillUnit; - } - - public ThrottleGatewayFilter setRefillUnit(TimeUnit refillUnit) { - this.refillUnit = refillUnit; - return this; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - - TokenBucket tokenBucket = TokenBuckets.builder().withCapacity(capacity) - .withFixedIntervalRefillStrategy(refillTokens, refillPeriod, refillUnit) - .build(); - - // TODO: get a token bucket for a key - log.debug("TokenBucket capacity: " + tokenBucket.getCapacity()); - boolean consumed = tokenBucket.tryConsume(); - if (consumed) { - return chain.filter(exchange); - } - exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); - return exchange.getResponse().setComplete(); - } - -} diff --git a/spring-cloud-learn/s2-api-gateway/src/main/resources/application.yml b/spring-cloud-learn/s2-api-gateway/src/main/resources/application.yml deleted file mode 100644 index 612350f2..00000000 --- a/spring-cloud-learn/s2-api-gateway/src/main/resources/application.yml +++ /dev/null @@ -1,32 +0,0 @@ -server: - port: 9999 - -spring: - application: - name: s2-api-gateway - cloud: - consul: - host: localhost - port: 8500 - discovery: - health-check-interval: 60s - service-name: ${spring.application.name} - instance-id: ${spring.application.name}-${server.port} - health-check-url: http://localhost:9999/check - enabled: true - query-passing: true - gateway: - discovery: - locator: - enabled: true - routes: - - id: TO_CONSUL_CLIENT - uri: lb://consul-client - predicates: - - Path=/consulclient/** - filters: - - StripPrefix=1 - -management: - security: - enabled: false diff --git a/spring-cloud-learn/springcloud-mybatis-demo1/pom.xml b/spring-cloud-learn/springcloud-mybatis-demo1/pom.xml deleted file mode 100644 index f5446f5a..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo1/pom.xml +++ /dev/null @@ -1,161 +0,0 @@ - - - 4.0.0 - - com.example - springcloud-mybatis-demo1 - 0.0.1-SNAPSHOT - jar - - springcloud-mybatis-demo1 - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.9.RELEASE - - - - - org.springframework.cloud - spring-cloud-dependencies - Edgware.SR2 - pom - import - - - - - - UTF-8 - UTF-8 - - - - - - org.springframework.cloud - spring-cloud-starter-eureka - - - org.mybatis.spring.boot - mybatis-spring-boot-starter - 1.1.1 - - - org.springframework.cloud - spring-cloud-starter-feign - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.springframework.cloud - spring-cloud-starter-ribbon - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.codingapi - transaction-springcloud - 4.1.0 - - - org.slf4j - * - - - - - com.codingapi - tx-plugins-db - 4.1.0 - - - org.slf4j - * - - - - - - com.alibaba - druid - 1.0.19 - - - mysql - mysql-connector-java - 5.1.43 - - - - - - springcloud-mybatis-demo1 - - - - src/main/java - - **/*.xml - - false - - - - src/main/resources - - **/*.* - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - org.springframework.boot - spring-boot-maven-plugin - - - true - - - - - org.mybatis.generator - mybatis-generator-maven-plugin - 1.3.6 - - false - true - ${basedir}/src/main/resources/generatorConfig.xml - - - - mysql - mysql-connector-java - - 5.1.44 - - - - - - - diff --git a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/MybatisDemo1Application.java b/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/MybatisDemo1Application.java deleted file mode 100644 index b974faed..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/MybatisDemo1Application.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.example.demo; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; -import org.springframework.cloud.netflix.feign.EnableFeignClients; - -@EnableAutoConfiguration -@SpringBootApplication -@EnableEurekaClient -@MapperScan("com.example.demo.*.mapper") -@EnableFeignClients -public class MybatisDemo1Application { - - public static void main(String[] args) { - SpringApplication.run(MybatisDemo1Application.class, args); - } - -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/comm/Config.java b/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/comm/Config.java deleted file mode 100644 index 4c13d12d..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/comm/Config.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.example.demo.comm; - -import com.codingapi.tx.springcloud.http.TransactionHttpRequestInterceptor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.http.converter.StringHttpMessageConverter; -import org.springframework.web.client.RestTemplate; - -import java.nio.charset.StandardCharsets; - -@Configuration -public class Config { - - @Autowired - private RestTemplateBuilder builder; - - @Bean - @LoadBalanced - public RestTemplate restTemplate(){ - HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(); - httpRequestFactory.setConnectionRequestTimeout(2000); - httpRequestFactory.setConnectTimeout(5000); - httpRequestFactory.setReadTimeout(60000); - RestTemplate restTemplate = builder.interceptors(new TransactionHttpRequestInterceptor()).build(); - restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8)); - restTemplate.setRequestFactory(httpRequestFactory); - return restTemplate; - } - -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/client/Demo2Client.java b/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/client/Demo2Client.java deleted file mode 100644 index ad5bc693..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/client/Demo2Client.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.demo.test01.client; - -import com.example.demo.test01.entity.Test; -import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - -import java.util.List; - -/** - * Created by lorne on 2017/6/27. - */ -@FeignClient(value = "springcloud-mybatis-demo2",fallback = Demo2ClientHystric.class) -public interface Demo2Client { - - - @RequestMapping(value = "/test02/list",method = RequestMethod.GET) - List list(); - - - @RequestMapping(value = "/test02/save",method = RequestMethod.GET) - int save(); -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/client/Demo2ClientHystric.java b/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/client/Demo2ClientHystric.java deleted file mode 100644 index d1df3693..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/client/Demo2ClientHystric.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.demo.test01.client; - -import com.example.demo.test01.entity.Test; -import org.springframework.stereotype.Component; - -import java.util.List; - - -@Component -public class Demo2ClientHystric implements Demo2Client { - - - @Override - public List list() { - System.out.println("进入断路器-list。。。"); - throw new RuntimeException("list 保存失败."); - } - - @Override - public int save() { - System.out.println("进入断路器-save。。。"); - throw new RuntimeException("save 保存失败."); - } -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/controller/Test01Controller.java b/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/controller/Test01Controller.java deleted file mode 100644 index 6c531d06..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/controller/Test01Controller.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.example.demo.test01.controller; - - -import com.example.demo.test01.service.DemoService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -/** - * Created by lorne on 2017/6/26. - */ -@RestController -@RequestMapping("/test01") -public class Test01Controller { - - @Autowired - private DemoService demoService; - - @RequestMapping("/save") - @ResponseBody - public int save(){ - return demoService.save(); - } - - @RequestMapping("/save2") - @ResponseBody - public int save2(){ - return demoService.save2(); - } - - @RequestMapping("/restSave") - @ResponseBody - public int restSave(){ - return demoService.restSave(); - } - - -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/entity/Test.java b/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/entity/Test.java deleted file mode 100644 index 7d5bbbbb..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/entity/Test.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.example.demo.test01.entity; - -/** - * Created by lorne on 2017/6/26. - */ - -public class Test { - - - private Integer id; - - private String name; - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/mapper/TestMapper.java b/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/mapper/TestMapper.java deleted file mode 100644 index d17a408c..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/mapper/TestMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.demo.test01.mapper; - -import com.example.demo.test01.entity.Test; -import org.apache.ibatis.annotations.Insert; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; - -import java.util.List; - -/** - * Created by lorne on 2017/6/28. - */ -@Mapper -public interface TestMapper { - - - @Select("SELECT * FROM T_TEST") - List findAll(); - - @Insert("INSERT INTO T_TEST(NAME) VALUES(#{name})") - int save(@Param("name") String name); - -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/service/DemoService.java b/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/service/DemoService.java deleted file mode 100644 index e17b72ef..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/service/DemoService.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.demo.test01.service; - -/** - * Created by lorne on 2017/6/26. - */ -public interface DemoService { - - int save(); - - int save2(); - - int restSave(); - -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/service/DemoServiceImpl.java b/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/service/DemoServiceImpl.java deleted file mode 100644 index 59b5b71f..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/test01/service/DemoServiceImpl.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.example.demo.test01.service; - -import com.codingapi.tx.annotation.TxTransaction; -import com.example.demo.test01.client.Demo2Client; -import com.example.demo.test01.mapper.TestMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.client.RestTemplate; - -/** - * Created by lorne on 2017/6/26. - */ -@Service -public class DemoServiceImpl implements DemoService { - - private Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class); - - @Autowired - private Demo2Client demo2Client; - @Autowired - private RestTemplate restTemplate; - @Autowired - private TestMapper testMapper; - - @Override - @TxTransaction(isStart = true) - @Transactional - public int save() { - int rs1 = testMapper.save("mybatis-hello-1"); - int rs2 = demo2Client.save(); - int v = 100/0; - return rs1+rs2; - } - - @Override - @TxTransaction(isStart = true) - @Transactional - public int save2() { - int rs1 = testMapper.save("222222222"); - System.out.println("执行----com.example.demo.test01.service.DemoServiceImpl.save2"); - int rs2 = demo2Client.save(); - return rs1+rs2; - } - - @Override - @TxTransaction(isStart = true) - @Transactional - public int restSave() { - int rs1 = testMapper.save("rrrrrrrrrrrr"); - System.out.println("执行----com.example.demo.test01.service.DemoServiceImpl.restSave"); - restTemplate.getForObject("http://springcloud-mybatis-demo2//test02/save",String.class); - return rs1; - } -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/txlcn/TxManagerHttpRequestServiceImpl.java b/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/txlcn/TxManagerHttpRequestServiceImpl.java deleted file mode 100644 index de389f0c..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/txlcn/TxManagerHttpRequestServiceImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.demo.txlcn; - -import com.codingapi.tx.netty.service.TxManagerHttpRequestService; -import com.lorne.core.framework.utils.http.HttpUtils; -import org.springframework.stereotype.Service; - -@Service -public class TxManagerHttpRequestServiceImpl implements TxManagerHttpRequestService{ - - @Override - public String httpGet(String url) { - return HttpUtils.get(url); - } - - @Override - public String httpPost(String url, String params) { - return HttpUtils.post(url,params); - } -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/txlcn/TxManagerTxUrlServiceImpl.java b/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/txlcn/TxManagerTxUrlServiceImpl.java deleted file mode 100644 index a1bdc87c..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/java/com/example/demo/txlcn/TxManagerTxUrlServiceImpl.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.demo.txlcn; - -import com.codingapi.tx.config.service.TxManagerTxUrlService; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -@Service -public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService{ - @Value("${tm.manager.url}") - private String url; - - @Override - public String getTxUrl() { - return url; - } -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/resources/application.properties b/spring-cloud-learn/springcloud-mybatis-demo1/src/main/resources/application.properties deleted file mode 100644 index a8b152d3..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo1/src/main/resources/application.properties +++ /dev/null @@ -1,37 +0,0 @@ -spring.application.name = springcloud-mybatis-demo1 -server.port = 8081 - -logging.level.com.codingapi=debug - -eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ -#\u4F7F\u7528ip\u66FF\u4EE3\u5B9E\u4F8B\u540D -eureka.instance.prefer-ip-address=true -#\u8BBE\u7F6E\u5B9E\u4F8B\u7684ID\u4E3A\uFF1A \u670D\u52A1\u540D:port -eureka.instance.instance-id=${spring.application.name}:${server.port} - -#feign.hystrix.enabled=true -## \u5173\u4E8Espringcloud-hystrix\u673A\u5236 http://www.jianshu.com/p/b8d21248c9b1 -#hystrix.command.default.execution.isolation.strategy= SEMAPHORE -#hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000 - -#Ribbon\u7684\u8D1F\u8F7D\u5747\u8861\u7B56\u7565 -ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule -ribbon.MaxAutoRetriesNextServer=0 - -#txmanager\u5730\u5740 -tm.manager.url=http://127.0.0.1:7000/tx/manager/ - -spring.datasource.type=com.alibaba.druid.pool.DruidDataSource -spring.datasource.driver-class-name = com.mysql.jdbc.Driver -spring.datasource.url= jdbc:mysql://localhost:3306/cpq -spring.datasource.username= root -spring.datasource.password=cpq..123 -spring.datasource.initialSize=10 -spring.datasource.maxActive=50 -spring.datasource.minIdle=0 -spring.datasource.maxWait=60000 -spring.datasource.testWhileIdle=true -spring.datasource.testOnBorrow=false -spring.datasource.poolPreparedStatements=false - - diff --git a/spring-cloud-learn/springcloud-mybatis-demo2/pom.xml b/spring-cloud-learn/springcloud-mybatis-demo2/pom.xml deleted file mode 100644 index 216eca08..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo2/pom.xml +++ /dev/null @@ -1,156 +0,0 @@ - - - 4.0.0 - - com.example - springcloud-mybatis-demo2 - 0.0.1-SNAPSHOT - jar - - springcloud-mybatis-demo2 - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.9.RELEASE - - - - - org.springframework.cloud - spring-cloud-dependencies - Edgware.SR2 - pom - import - - - - - - UTF-8 - UTF-8 - - - - - - org.springframework.cloud - spring-cloud-starter-eureka - - - org.mybatis.spring.boot - mybatis-spring-boot-starter - 1.1.1 - - - org.springframework.cloud - spring-cloud-starter-feign - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.codingapi - transaction-springcloud - 4.1.0 - - - org.slf4j - * - - - - - com.codingapi - tx-plugins-db - 4.1.0 - - - org.slf4j - * - - - - - - com.alibaba - druid - 1.0.19 - - - mysql - mysql-connector-java - 5.1.43 - - - - - - springcloud-mybatis-demo2 - - - - src/main/java - - **/*.xml - - false - - - - src/main/resources - - **/*.* - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - org.springframework.boot - spring-boot-maven-plugin - - - true - - - - - org.mybatis.generator - mybatis-generator-maven-plugin - 1.3.6 - - false - true - ${basedir}/src/main/resources/generatorConfig.xml - - - - mysql - mysql-connector-java - - 5.1.44 - - - - - - - diff --git a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/MybatisDemo2Application.java b/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/MybatisDemo2Application.java deleted file mode 100644 index 2885d08c..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/MybatisDemo2Application.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.example.demo; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; -import org.springframework.cloud.netflix.feign.EnableFeignClients; - -@SpringBootApplication -@EnableEurekaClient -@EnableAutoConfiguration -@MapperScan("com.example.demo.*.mapper") -@EnableFeignClients -public class MybatisDemo2Application { - - public static void main(String[] args) { - SpringApplication.run(MybatisDemo2Application.class, args); - } - -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/controller/Test02Controller.java b/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/controller/Test02Controller.java deleted file mode 100644 index a01bd242..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/controller/Test02Controller.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.example.demo.test02.controller; - - -import com.example.demo.test02.entity.Test; -import com.example.demo.test02.service.DemoService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * Created by lorne on 2017/6/26. - */ -@RestController -@RequestMapping("/test02") -public class Test02Controller { - - @Autowired - private DemoService demoService; - - - @RequestMapping("/list") - @ResponseBody - public List list(){ - return demoService.list(); - } - - - @RequestMapping("/save") - @ResponseBody - public int save(){ - int num = demoService.save(); - System.out.println("执行----com.example.demo.test02.controller.Test02Controller.save"); - return num; - } -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/entity/Test.java b/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/entity/Test.java deleted file mode 100644 index ab554834..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/entity/Test.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.example.demo.test02.entity; - -/** - * Created by lorne on 2017/6/26. - */ - -public class Test { - - private Integer id; - - private String name; - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/mapper/TestMapper.java b/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/mapper/TestMapper.java deleted file mode 100644 index 061a3718..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/mapper/TestMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.demo.test02.mapper; - -import com.example.demo.test02.entity.Test; -import org.apache.ibatis.annotations.Insert; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; - -import java.util.List; - -/** - * Created by lorne on 2017/6/28. - */ -@Mapper -public interface TestMapper { - - - @Select("SELECT * FROM T_TEST2") - List findAll(); - - @Insert("INSERT INTO T_TEST2(id, NAME) VALUES(#{id},#{name})") - int save(@Param("id") String id, @Param("name") String name); - -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/service/DemoService.java b/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/service/DemoService.java deleted file mode 100644 index 7cc2e360..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/service/DemoService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.demo.test02.service; - -import com.example.demo.test02.entity.Test; - -import java.util.List; - -/** - * Created by lorne on 2017/6/26. - */ -public interface DemoService { - - List list(); - - int save(); - -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/service/DemoServiceImpl.java b/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/service/DemoServiceImpl.java deleted file mode 100644 index 8c3b44b7..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/test02/service/DemoServiceImpl.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.example.demo.test02.service; - -import com.codingapi.tx.annotation.TxTransaction; -import com.example.demo.test02.entity.Test; -import com.example.demo.test02.mapper.TestMapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.UUID; - -/** - * Created by lorne on 2017/6/26. - */ -@Service -public class DemoServiceImpl implements DemoService{ - - @Autowired - private TestMapper testMapper; - - - - @Override - public List list() { - return testMapper.findAll(); - } - - - @Override - @Transactional - @TxTransaction - public int save() { - - int rs = testMapper.save(UUID.randomUUID().toString(),"mybatis-hello-2"); - - return rs; - } -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/txlcn/TxManagerHttpRequestServiceImpl.java b/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/txlcn/TxManagerHttpRequestServiceImpl.java deleted file mode 100644 index de389f0c..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/txlcn/TxManagerHttpRequestServiceImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.demo.txlcn; - -import com.codingapi.tx.netty.service.TxManagerHttpRequestService; -import com.lorne.core.framework.utils.http.HttpUtils; -import org.springframework.stereotype.Service; - -@Service -public class TxManagerHttpRequestServiceImpl implements TxManagerHttpRequestService{ - - @Override - public String httpGet(String url) { - return HttpUtils.get(url); - } - - @Override - public String httpPost(String url, String params) { - return HttpUtils.post(url,params); - } -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/txlcn/TxManagerTxUrlServiceImpl.java b/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/txlcn/TxManagerTxUrlServiceImpl.java deleted file mode 100644 index a1bdc87c..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/java/com/example/demo/txlcn/TxManagerTxUrlServiceImpl.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.demo.txlcn; - -import com.codingapi.tx.config.service.TxManagerTxUrlService; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -@Service -public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService{ - @Value("${tm.manager.url}") - private String url; - - @Override - public String getTxUrl() { - return url; - } -} diff --git a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/resources/application.properties b/spring-cloud-learn/springcloud-mybatis-demo2/src/main/resources/application.properties deleted file mode 100644 index bc2232c0..00000000 --- a/spring-cloud-learn/springcloud-mybatis-demo2/src/main/resources/application.properties +++ /dev/null @@ -1,34 +0,0 @@ -spring.application.name = springcloud-mybatis-demo2 -server.port = 8082 -logging.level.com.codingapi=debug - -eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ -#\u4F7F\u7528ip\u66FF\u4EE3\u5B9E\u4F8B\u540D -eureka.instance.prefer-ip-address=true -#\u8BBE\u7F6E\u5B9E\u4F8B\u7684ID\u4E3A\uFF1A \u670D\u52A1\u540D:port -eureka.instance.instance-id=${spring.application.name}:${server.port} - -#feign.hystrix.enabled=true -## \u5173\u4E8Espringcloud-hystrix\u673A\u5236 http://www.jianshu.com/p/b8d21248c9b1 -#hystrix.command.default.execution.isolation.strategy= SEMAPHORE -#hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000 - -#Ribbon\u7684\u8D1F\u8F7D\u5747\u8861\u7B56\u7565 -ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule -ribbon.MaxAutoRetriesNextServer=0 - -#txmanager\u5730\u5740 -tm.manager.url=http://127.0.0.1:7000/tx/manager/ - -spring.datasource.type=com.alibaba.druid.pool.DruidDataSource -spring.datasource.driver-class-name = com.mysql.jdbc.Driver -spring.datasource.url= jdbc:mysql://localhost:3306/cpq -spring.datasource.username= root -spring.datasource.password=cpq..123 -spring.datasource.initialSize=10 -spring.datasource.maxActive=50 -spring.datasource.minIdle=0 -spring.datasource.maxWait=60000 -spring.datasource.testWhileIdle=true -spring.datasource.testOnBorrow=false -spring.datasource.poolPreparedStatements=false diff --git a/spring-cloud-learn/stream-group/.gitignore b/spring-cloud-learn/stream-group/.gitignore deleted file mode 100644 index 82eca336..00000000 --- a/spring-cloud-learn/stream-group/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/build/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ \ No newline at end of file diff --git a/spring-cloud-learn/stream-group/pom.xml b/spring-cloud-learn/stream-group/pom.xml deleted file mode 100644 index 045d11be..00000000 --- a/spring-cloud-learn/stream-group/pom.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - 4.0.0 - - com.cpq - stream-group - 0.0.1-SNAPSHOT - jar - - stream-group - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.4.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.cloud - spring-cloud-starter-stream-rabbit - - - - - - - org.springframework.cloud - spring-cloud-dependencies - Dalston.SR1 - pom - import - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/spring-cloud-learn/stream-group/src/main/java/com/cpq/streamgroup/skin/SinkReceiver.java b/spring-cloud-learn/stream-group/src/main/java/com/cpq/streamgroup/skin/SinkReceiver.java deleted file mode 100644 index a3f769c4..00000000 --- a/spring-cloud-learn/stream-group/src/main/java/com/cpq/streamgroup/skin/SinkReceiver.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.cpq.streamgroup.skin; - -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.StreamListener; -import org.springframework.cloud.stream.messaging.Sink; - -@EnableBinding(value = {Sink.class}) -public class SinkReceiver { - - //定时发送消息给group01 - @StreamListener(Sink.INPUT) - public void reverve(User user){ - System.out.println("streamgroup.skin.SinkReceiver接收: "+user.toString()); - } - -} diff --git a/spring-cloud-learn/stream-group/src/main/java/com/cpq/streamgroup/skin/User.java b/spring-cloud-learn/stream-group/src/main/java/com/cpq/streamgroup/skin/User.java deleted file mode 100644 index 3e1b1075..00000000 --- a/spring-cloud-learn/stream-group/src/main/java/com/cpq/streamgroup/skin/User.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.cpq.streamgroup.skin; - -public class User { - private String name; - private Integer age; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Integer getAge() { - return age; - } - - public void setAge(Integer age) { - this.age = age; - } - - @Override - public String toString() { - return "User{" + - "name='" + name + '\'' + - ", age=" + age + - '}'; - } -} diff --git a/spring-cloud-learn/stream-group/src/main/resources/application.properties b/spring-cloud-learn/stream-group/src/main/resources/application.properties deleted file mode 100644 index 1b2ba5cf..00000000 --- a/spring-cloud-learn/stream-group/src/main/resources/application.properties +++ /dev/null @@ -1,14 +0,0 @@ -spring.application.name=stream-group - -#\u914D\u5408spring-boot-maven-plugin\u7684\u542F\u52A8\u6B65\u9AA4 -#\u65B0\u5EFAmaven\u542F\u52A8\u914D\u7F6E -# Working directory \u4E3A\u672C\u9879\u76EE \u6839\u76EE\u5F55 -# Command line spring-boot:run -Dport=7200 -Dindex=1 -server.port=${port} - -spring.cloud.stream.bindings.input.group=I-Stream-Group-01 -spring.cloud.stream.bindings.input.destination=group01 - -spring.cloud.stream.bindings.input.consumer.partitioned=true -spring.cloud.stream.instanceCount=2 -spring.cloud.stream.instanceIndex=${index} \ No newline at end of file diff --git a/spring-cloud-learn/stream-group/src/test/java/com/cpq/streamgroup/StreamGroupApplicationTests.java b/spring-cloud-learn/stream-group/src/test/java/com/cpq/streamgroup/StreamGroupApplicationTests.java deleted file mode 100644 index 2b9a4a8b..00000000 --- a/spring-cloud-learn/stream-group/src/test/java/com/cpq/streamgroup/StreamGroupApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.cpq.streamgroup; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class StreamGroupApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-cloud-learn/stream-hello/.gitignore b/spring-cloud-learn/stream-hello/.gitignore deleted file mode 100644 index 82eca336..00000000 --- a/spring-cloud-learn/stream-hello/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/build/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ \ No newline at end of file diff --git a/spring-cloud-learn/stream-hello/pom.xml b/spring-cloud-learn/stream-hello/pom.xml deleted file mode 100644 index a6c3e36a..00000000 --- a/spring-cloud-learn/stream-hello/pom.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - 4.0.0 - - com.cpq - stream-hello - 0.0.1-SNAPSHOT - jar - - stream-hello - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.4.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.cloud - spring-cloud-starter-stream-rabbit - - - - - - - org.springframework.cloud - spring-cloud-dependencies - Dalston.SR1 - pom - import - - - - - - diff --git a/spring-cloud-learn/stream-hello/src/main/java/com/cpq/streamhello/sink/GroupSinkSender.java b/spring-cloud-learn/stream-hello/src/main/java/com/cpq/streamhello/sink/GroupSinkSender.java deleted file mode 100644 index bbc7d15c..00000000 --- a/spring-cloud-learn/stream-hello/src/main/java/com/cpq/streamhello/sink/GroupSinkSender.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.cpq.streamhello.sink; - -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.messaging.Source; -import org.springframework.context.annotation.Bean; -import org.springframework.integration.annotation.InboundChannelAdapter; -import org.springframework.integration.annotation.Poller; -import org.springframework.integration.core.MessageSource; -import org.springframework.messaging.support.GenericMessage; - - -@EnableBinding(value = {Source.class}) -public class GroupSinkSender { - - @Bean - @InboundChannelAdapter(value = Source.OUTPUT, poller = @Poller(fixedDelay = "2000")) - public MessageSource timerMessageSource(){ - return () -> new GenericMessage<>("{\"name\":\"姓名\", \"age\": 10}"); - } - -} diff --git a/spring-cloud-learn/stream-hello/src/main/java/com/cpq/streamhello/sink/SinkReceiver.java b/spring-cloud-learn/stream-hello/src/main/java/com/cpq/streamhello/sink/SinkReceiver.java deleted file mode 100644 index 86c89026..00000000 --- a/spring-cloud-learn/stream-hello/src/main/java/com/cpq/streamhello/sink/SinkReceiver.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.cpq.streamhello.sink; - -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.StreamListener; -import org.springframework.cloud.stream.messaging.Sink; -import org.springframework.stereotype.Component; - -@Component -@EnableBinding(Sink.class) -public class SinkReceiver { - - @StreamListener(Sink.INPUT) - public void receive(Object payload){ - System.out.println("streamhello.sink.SinkReceiver接收: "+payload.toString()); - } - -} diff --git a/spring-cloud-learn/stream-hello/src/main/resources/application.properties b/spring-cloud-learn/stream-hello/src/main/resources/application.properties deleted file mode 100644 index 4b682b79..00000000 --- a/spring-cloud-learn/stream-hello/src/main/resources/application.properties +++ /dev/null @@ -1,16 +0,0 @@ -spring.application.name=stream-hello -server.port=7001 - -#\u914D\u7F6E\u9879\u76EEinput\u548Coutput\u4E3A\u540C\u4E00\u4E2A\u4E3B\uFF0C\u672C\u9879\u76EE\u4E5F\u53EF\u4EE5\u63A5\u6536\u672C\u9879\u76EE\u7684\u53D1\u51FA\u7684\u4FE1\u606F -spring.cloud.stream.bindings.input.group=I-Stream-Group-01 -spring.cloud.stream.bindings.input.destination=group01 - -#stream-group\u7684application.properties\u5B9A\u4E49\u4E86 -#spring.cloud.stream.bindings.input.destination=group01 -#\u7528\u4E8E\u63A5\u6536\u914D\u7F6E\u4E86spring.cloud.stream.bindings.output.destination=group01\u7684\u9879\u76EE\u7684\u4FE1\u606F -spring.cloud.stream.bindings.output.group=O-Stream-Group-01 -spring.cloud.stream.bindings.output.destination=group01 - -spring.cloud.stream.bindings.output.producer.partitionKeyExpression=payload -spring.cloud.stream.bindings.output.producer.partitionCount=2 - diff --git a/spring-cloud-learn/stream-hello/src/test/java/com/cpq/streamhello/SinkApplicationTests.java b/spring-cloud-learn/stream-hello/src/test/java/com/cpq/streamhello/SinkApplicationTests.java deleted file mode 100644 index 83e1d0bf..00000000 --- a/spring-cloud-learn/stream-hello/src/test/java/com/cpq/streamhello/SinkApplicationTests.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.cpq.streamhello; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.Output; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@EnableBinding(value = {SinkApplicationTests.SinkSender.class}) -public class SinkApplicationTests { - - @Autowired - private SinkSender sinkSender; - - @Test - public void sinkSenderTester() { - String str = "********生产消息******"; - sinkSender.output().send(MessageBuilder.withPayload(str).build()); - } - - public interface SinkSender{ - String OUTPUT = "input"; - - @Output(SinkSender.OUTPUT) - MessageChannel output(); - } - -} diff --git a/spring-cloud-learn/trace-1/.gitignore b/spring-cloud-learn/trace-1/.gitignore deleted file mode 100644 index 82eca336..00000000 --- a/spring-cloud-learn/trace-1/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/build/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ \ No newline at end of file diff --git a/spring-cloud-learn/trace-1/pom.xml b/spring-cloud-learn/trace-1/pom.xml deleted file mode 100644 index 02b5e042..00000000 --- a/spring-cloud-learn/trace-1/pom.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - 4.0.0 - - com.cpq - trace-1 - 0.0.1-SNAPSHOT - jar - - trace-1 - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.4.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.cloud - spring-cloud-starter-eureka - - - org.springframework.cloud - spring-cloud-starter-ribbon - - - org.springframework.cloud - spring-cloud-starter-sleuth - - - net.logstash.logback - logstash-logback-encoder - 4.6 - - - org.springframework.cloud - spring-cloud-sleuth-zipkin - - - - - - - - org.springframework.cloud - spring-cloud-dependencies - - Dalston.SR1 - pom - import - - - - - diff --git a/spring-cloud-learn/trace-1/readme.txt b/spring-cloud-learn/trace-1/readme.txt deleted file mode 100644 index b86d1203..00000000 --- a/spring-cloud-learn/trace-1/readme.txt +++ /dev/null @@ -1 +0,0 @@ -日志文件是在java-learn根目录生成 \ No newline at end of file diff --git a/spring-cloud-learn/trace-1/src/main/java/com/cpq/trace1/Trace1Application.java b/spring-cloud-learn/trace-1/src/main/java/com/cpq/trace1/Trace1Application.java deleted file mode 100644 index 24c038e2..00000000 --- a/spring-cloud-learn/trace-1/src/main/java/com/cpq/trace1/Trace1Application.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.cpq.trace1; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.context.annotation.Bean; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; - -@RestController -@EnableDiscoveryClient -@SpringBootApplication -public class Trace1Application { - - public static void main(String[] args) { - SpringApplication.run(Trace1Application.class, args); - } - - @Bean - @LoadBalanced - RestTemplate restTemplate(){ - return new RestTemplate(); - } - - @RequestMapping(value = "/trace-1", method = RequestMethod.GET) - public String trace(){ - return restTemplate().getForEntity("http://trace-2/trace-2", String.class).getBody(); - } -} diff --git a/spring-cloud-learn/trace-1/src/main/resources/application.properties b/spring-cloud-learn/trace-1/src/main/resources/application.properties deleted file mode 100644 index 4c2ce7e7..00000000 --- a/spring-cloud-learn/trace-1/src/main/resources/application.properties +++ /dev/null @@ -1,5 +0,0 @@ -server.port=9101 - -spring.zipkin.base-url=http://localhost:9411 - -eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ \ No newline at end of file diff --git a/spring-cloud-learn/trace-1/src/main/resources/bootstrap.properties b/spring-cloud-learn/trace-1/src/main/resources/bootstrap.properties deleted file mode 100644 index 01993a03..00000000 --- a/spring-cloud-learn/trace-1/src/main/resources/bootstrap.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=trace-1 diff --git a/spring-cloud-learn/trace-1/src/main/resources/logback-spring.xml b/spring-cloud-learn/trace-1/src/main/resources/logback-spring.xml deleted file mode 100644 index c044fb9f..00000000 --- a/spring-cloud-learn/trace-1/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - INFO - - - ${CONSOLE_LOG_PATTERN} - utf8 - - - - - - ${LOG_FILE}.json - - ${LOG_FILE}.json.%d{yyyy-MM-dd}.gz - 7 - - - - - UTC - - - - { - "severity": "%level", - "service": "${springAppName:-}", - "trace": "%X{X-B3-TraceId:-}", - "span": "%X{X-B3-SpanId:-}", - "exportable": "%X{X-Span-Export:-}", - "pid": "${PID:-}", - "thread": "%thread", - "class": "%logger{40}", - "rest": "%message" - } - - - - - - - - - - - \ No newline at end of file diff --git a/spring-cloud-learn/trace-2/.gitignore b/spring-cloud-learn/trace-2/.gitignore deleted file mode 100644 index 82eca336..00000000 --- a/spring-cloud-learn/trace-2/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/build/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ \ No newline at end of file diff --git a/spring-cloud-learn/trace-2/pom.xml b/spring-cloud-learn/trace-2/pom.xml deleted file mode 100644 index 675dc99b..00000000 --- a/spring-cloud-learn/trace-2/pom.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - 4.0.0 - - com.cpq - trace-2 - 0.0.1-SNAPSHOT - jar - - trace-2 - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.4.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.cloud - spring-cloud-starter-eureka - - - org.springframework.cloud - spring-cloud-starter-ribbon - - - org.springframework.cloud - spring-cloud-starter-sleuth - - - net.logstash.logback - logstash-logback-encoder - 4.6 - - - org.springframework.cloud - spring-cloud-sleuth-zipkin - - - - - - - org.springframework.cloud - spring-cloud-dependencies - - Dalston.SR1 - pom - import - - - - diff --git a/spring-cloud-learn/trace-2/src/main/java/com/cpq/trace2/Trace2Application.java b/spring-cloud-learn/trace-2/src/main/java/com/cpq/trace2/Trace2Application.java deleted file mode 100644 index 3069e628..00000000 --- a/spring-cloud-learn/trace-2/src/main/java/com/cpq/trace2/Trace2Application.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.cpq.trace2; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletRequest; - -@RestController -@EnableDiscoveryClient -@SpringBootApplication -public class Trace2Application { - - public static void main(String[] args) { - SpringApplication.run(Trace2Application.class, args); - } - - @RequestMapping(value = "/trace-2", method = RequestMethod.GET) - public String trace(HttpServletRequest request) { - System.out.println("*********"+request.getHeader("X-B3-TraceId")+"*********"); - System.out.println("*********"+request.getHeader("X-B3-SpanId")+"*********"); - - return "Trace"; - } -} diff --git a/spring-cloud-learn/trace-2/src/main/resources/application.properties b/spring-cloud-learn/trace-2/src/main/resources/application.properties deleted file mode 100644 index 2d777b6f..00000000 --- a/spring-cloud-learn/trace-2/src/main/resources/application.properties +++ /dev/null @@ -1,5 +0,0 @@ -server.port=9102 - -spring.zipkin.base-url=http://localhost:9411 - -eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ \ No newline at end of file diff --git a/spring-cloud-learn/trace-2/src/main/resources/bootstrap.properties b/spring-cloud-learn/trace-2/src/main/resources/bootstrap.properties deleted file mode 100644 index 3984d155..00000000 --- a/spring-cloud-learn/trace-2/src/main/resources/bootstrap.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=trace-2 \ No newline at end of file diff --git a/spring-cloud-learn/trace-2/src/main/resources/logback-spring.xml b/spring-cloud-learn/trace-2/src/main/resources/logback-spring.xml deleted file mode 100644 index c044fb9f..00000000 --- a/spring-cloud-learn/trace-2/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - INFO - - - ${CONSOLE_LOG_PATTERN} - utf8 - - - - - - ${LOG_FILE}.json - - ${LOG_FILE}.json.%d{yyyy-MM-dd}.gz - 7 - - - - - UTC - - - - { - "severity": "%level", - "service": "${springAppName:-}", - "trace": "%X{X-B3-TraceId:-}", - "span": "%X{X-B3-SpanId:-}", - "exportable": "%X{X-Span-Export:-}", - "pid": "${PID:-}", - "thread": "%thread", - "class": "%logger{40}", - "rest": "%message" - } - - - - - - - - - - - \ No newline at end of file diff --git a/spring-cloud-learn/turbine/.gitignore b/spring-cloud-learn/turbine/.gitignore deleted file mode 100644 index 82eca336..00000000 --- a/spring-cloud-learn/turbine/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/build/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ \ No newline at end of file diff --git a/spring-cloud-learn/turbine/pom.xml b/spring-cloud-learn/turbine/pom.xml deleted file mode 100644 index 8b725728..00000000 --- a/spring-cloud-learn/turbine/pom.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - 4.0.0 - - com.cpq - turbine - 0.0.1-SNAPSHOT - jar - - turbine - Demo project for Spring Boot - - - org.springframework.cloud - spring-cloud-starter-parent - Dalston.SR1 - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.cloud - spring-cloud-starter-turbine - - - org.springframework.boot - spring-boot-starter-actuator - - - - diff --git a/spring-cloud-learn/turbine/src/main/java/com/cpq/turbine/TurbineApplication.java b/spring-cloud-learn/turbine/src/main/java/com/cpq/turbine/TurbineApplication.java deleted file mode 100644 index c375b164..00000000 --- a/spring-cloud-learn/turbine/src/main/java/com/cpq/turbine/TurbineApplication.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.cpq.turbine; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.turbine.EnableTurbine; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableAutoConfiguration -@EnableTurbine -@SpringBootApplication -public class TurbineApplication { - - public static void main(String[] args) { - SpringApplication.run(TurbineApplication.class, args); - } -} diff --git a/spring-cloud-learn/turbine/src/main/resources/application.properties b/spring-cloud-learn/turbine/src/main/resources/application.properties deleted file mode 100644 index e62042f6..00000000 --- a/spring-cloud-learn/turbine/src/main/resources/application.properties +++ /dev/null @@ -1,11 +0,0 @@ -spring.application.name=turbine -# \u670D\u52A1\u7AEF\u53E3 -server.port=8989 -#\u7BA1\u7406\u7AEF\u53E3 -management.port=8990 - -eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ - -turbine.app-config=eureka-consumer-ribbon-hystrix -turbine.cluster-name-expression="default" -turbine.combine-host-port=true \ No newline at end of file diff --git a/spring-cloud-learn/tx-app01/pom.xml b/spring-cloud-learn/tx-app01/pom.xml deleted file mode 100644 index 79fc8124..00000000 --- a/spring-cloud-learn/tx-app01/pom.xml +++ /dev/null @@ -1,177 +0,0 @@ - - - 4.0.0 - - com.cpq - tx-app01 - 0.0.1-SNAPSHOT - jar - - tx-app01 - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.9.RELEASE - - - - - org.springframework.cloud - spring-cloud-dependencies - Edgware.SR2 - pom - import - - - - - - UTF-8 - UTF-8 - - - - - - org.springframework.cloud - spring-cloud-starter-eureka - - - org.springframework.cloud - spring-cloud-starter-feign - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.springframework.cloud - spring-cloud-starter-ribbon - - - - - org.mybatis.spring.boot - mybatis-spring-boot-starter - 1.3.0 - - - com.alibaba - druid - 1.0.19 - - - mysql - mysql-connector-java - 5.1.43 - - - - - com.codingapi - transaction-springcloud - 4.1.0 - - - org.slf4j - * - - - - - com.codingapi - tx-plugins-db - 4.1.0 - - - org.slf4j - * - - - - - - - com.alibaba - fastjson - 1.2.41 - - - - - io.springfox - springfox-swagger2 - 2.2.2 - - - io.springfox - springfox-swagger-ui - 2.2.2 - - - - - - tx-app01 - - - - src/main/java - - **/*.xml - - false - - - - src/main/resources - - **/*.* - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - org.springframework.boot - spring-boot-maven-plugin - - - true - - - - - org.mybatis.generator - mybatis-generator-maven-plugin - 1.3.6 - - false - true - ${basedir}/src/main/resources/generatorConfig.xml - - - - mysql - mysql-connector-java - - 5.1.44 - - - - - - - diff --git a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/Application.java b/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/Application.java deleted file mode 100644 index d5cc0d5e..00000000 --- a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/Application.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.cpq.txapp01; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; -import org.springframework.cloud.netflix.feign.EnableFeignClients; - -@SpringBootApplication -@EnableEurekaClient -@EnableAutoConfiguration -@MapperScan("com.cpq.txapp01.*.mapper") -@EnableFeignClients //启动feign -public class Application { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } -} diff --git a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/comm/Config.java b/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/comm/Config.java deleted file mode 100644 index c67c10d9..00000000 --- a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/comm/Config.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.cpq.txapp01.comm; - -import com.codingapi.tx.springcloud.http.TransactionHttpRequestInterceptor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.http.converter.StringHttpMessageConverter; -import org.springframework.web.client.RestTemplate; - -import java.nio.charset.StandardCharsets; - -@Configuration -public class Config { - - @Autowired - private RestTemplateBuilder builder; - - @Bean - @LoadBalanced - public RestTemplate restTemplate(){ - HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(); - httpRequestFactory.setConnectionRequestTimeout(2000); - httpRequestFactory.setConnectTimeout(5000); - httpRequestFactory.setReadTimeout(60000); - RestTemplate restTemplate = builder.interceptors(new TransactionHttpRequestInterceptor()).build(); - restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8)); - restTemplate.setRequestFactory(httpRequestFactory); - return restTemplate; - } - -} diff --git a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/client/Demo1Client.java b/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/client/Demo1Client.java deleted file mode 100644 index cc4a8ef3..00000000 --- a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/client/Demo1Client.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.cpq.txapp01.sysuser.client; - -import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - -/** - * Created by lorne on 2017/6/27. - */ -@FeignClient(value = "springcloud-mybatis-demo1") -public interface Demo1Client { - @RequestMapping(value = "/test01/save2",method = RequestMethod.GET) - int save(); -} diff --git a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/client/TxApp02Client.java b/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/client/TxApp02Client.java deleted file mode 100644 index a7769f67..00000000 --- a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/client/TxApp02Client.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.cpq.txapp01.sysuser.client; - -import com.alibaba.fastjson.JSONObject; -import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - -@FeignClient(name = "tx-app002") -public interface TxApp02Client { - @RequestMapping(value = "/sys/role/add",method = RequestMethod.POST) - JSONObject add(); -} diff --git a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/controller/SysUserController.java b/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/controller/SysUserController.java deleted file mode 100644 index 09985a86..00000000 --- a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/controller/SysUserController.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.cpq.txapp01.sysuser.controller; - -import com.alibaba.fastjson.JSONObject; -import com.cpq.txapp01.sysuser.service.SysUserService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletRequest; - -@RestController -@RequestMapping("/sys/user") -public class SysUserController { - - @Autowired - SysUserService sysUserService; - - @PostMapping("/add") - public JSONObject add(HttpServletRequest request, @RequestBody JSONObject jsonObject) throws Exception{ - - sysUserService.add(); - - JSONObject json = new JSONObject(); - json.put("code", 0); - - return json; - } - - @PostMapping("/app001/app002/demo1/demo2") - public JSONObject app01app02demo1demo2(HttpServletRequest request, @RequestBody JSONObject jsonObject) throws Exception{ - sysUserService.app01app02demo1demo2(); - JSONObject json = new JSONObject(); - json.put("code", 0); - - return json; - } - - @PostMapping("/restsave") - public JSONObject restsave(HttpServletRequest request, @RequestBody JSONObject jsonObject) throws Exception{ - sysUserService.restsave(); - JSONObject json = new JSONObject(); - json.put("code", 0); - - return json; - } - -} diff --git a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/mapper/SysUserMapper.java b/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/mapper/SysUserMapper.java deleted file mode 100644 index 09902d21..00000000 --- a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/mapper/SysUserMapper.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.cpq.txapp01.sysuser.mapper; - -import com.cpq.txapp01.sysuser.model.SysUser; -import com.cpq.txapp01.sysuser.model.SysUserExample; -import java.util.List; -import org.apache.ibatis.annotations.Param; - -public interface SysUserMapper { - long countByExample(SysUserExample example); - - int deleteByExample(SysUserExample example); - - int deleteByPrimaryKey(String id); - - int insert(SysUser record); - - int insertSelective(SysUser record); - - List selectByExample(SysUserExample example); - - SysUser selectByPrimaryKey(String id); - - int updateByExampleSelective(@Param("record") SysUser record, @Param("example") SysUserExample example); - - int updateByExample(@Param("record") SysUser record, @Param("example") SysUserExample example); - - int updateByPrimaryKeySelective(SysUser record); - - int updateByPrimaryKey(SysUser record); -} \ No newline at end of file diff --git a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/mapper/SysUserMapper.xml b/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/mapper/SysUserMapper.xml deleted file mode 100644 index 0b906d5c..00000000 --- a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/mapper/SysUserMapper.xml +++ /dev/null @@ -1,433 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - and ${criterion.condition} - - - and ${criterion.condition} #{criterion.value} - - - and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} - - - and ${criterion.condition} - - #{listItem} - - - - - - - - - - - - - - - - - - and ${criterion.condition} - - - and ${criterion.condition} #{criterion.value} - - - and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} - - - and ${criterion.condition} - - #{listItem} - - - - - - - - - - - id, login_id, name, password, key_sign, area_code, approve_code, department, status, - role_id, sys_type, cooperation_mode, email, phone, deleted, create_user_id, create_time, - update_user_id, update_time - - - - - delete from sys_user - where id = #{id,jdbcType=VARCHAR} - - - delete from sys_user - - - - - - insert into sys_user (id, login_id, name, - password, key_sign, area_code, - approve_code, department, status, - role_id, sys_type, cooperation_mode, - email, phone, deleted, - create_user_id, create_time, update_user_id, - update_time) - values (#{id,jdbcType=VARCHAR}, #{loginId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, - #{password,jdbcType=VARCHAR}, #{keySign,jdbcType=VARCHAR}, #{areaCode,jdbcType=VARCHAR}, - #{approveCode,jdbcType=VARCHAR}, #{department,jdbcType=VARCHAR}, #{status,jdbcType=INTEGER}, - #{roleId,jdbcType=VARCHAR}, #{sysType,jdbcType=INTEGER}, #{cooperationMode,jdbcType=VARCHAR}, - #{email,jdbcType=VARCHAR}, #{phone,jdbcType=VARCHAR}, #{deleted,jdbcType=BIT}, - #{createUserId,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateUserId,jdbcType=VARCHAR}, - #{updateTime,jdbcType=TIMESTAMP}) - - - insert into sys_user - - - id, - - - login_id, - - - name, - - - password, - - - key_sign, - - - area_code, - - - approve_code, - - - department, - - - status, - - - role_id, - - - sys_type, - - - cooperation_mode, - - - email, - - - phone, - - - deleted, - - - create_user_id, - - - create_time, - - - update_user_id, - - - update_time, - - - - - #{id,jdbcType=VARCHAR}, - - - #{loginId,jdbcType=VARCHAR}, - - - #{name,jdbcType=VARCHAR}, - - - #{password,jdbcType=VARCHAR}, - - - #{keySign,jdbcType=VARCHAR}, - - - #{areaCode,jdbcType=VARCHAR}, - - - #{approveCode,jdbcType=VARCHAR}, - - - #{department,jdbcType=VARCHAR}, - - - #{status,jdbcType=INTEGER}, - - - #{roleId,jdbcType=VARCHAR}, - - - #{sysType,jdbcType=INTEGER}, - - - #{cooperationMode,jdbcType=VARCHAR}, - - - #{email,jdbcType=VARCHAR}, - - - #{phone,jdbcType=VARCHAR}, - - - #{deleted,jdbcType=BIT}, - - - #{createUserId,jdbcType=VARCHAR}, - - - #{createTime,jdbcType=TIMESTAMP}, - - - #{updateUserId,jdbcType=VARCHAR}, - - - #{updateTime,jdbcType=TIMESTAMP}, - - - - - - update sys_user - - - id = #{record.id,jdbcType=VARCHAR}, - - - login_id = #{record.loginId,jdbcType=VARCHAR}, - - - name = #{record.name,jdbcType=VARCHAR}, - - - password = #{record.password,jdbcType=VARCHAR}, - - - key_sign = #{record.keySign,jdbcType=VARCHAR}, - - - area_code = #{record.areaCode,jdbcType=VARCHAR}, - - - approve_code = #{record.approveCode,jdbcType=VARCHAR}, - - - department = #{record.department,jdbcType=VARCHAR}, - - - status = #{record.status,jdbcType=INTEGER}, - - - role_id = #{record.roleId,jdbcType=VARCHAR}, - - - sys_type = #{record.sysType,jdbcType=INTEGER}, - - - cooperation_mode = #{record.cooperationMode,jdbcType=VARCHAR}, - - - email = #{record.email,jdbcType=VARCHAR}, - - - phone = #{record.phone,jdbcType=VARCHAR}, - - - deleted = #{record.deleted,jdbcType=BIT}, - - - create_user_id = #{record.createUserId,jdbcType=VARCHAR}, - - - create_time = #{record.createTime,jdbcType=TIMESTAMP}, - - - update_user_id = #{record.updateUserId,jdbcType=VARCHAR}, - - - update_time = #{record.updateTime,jdbcType=TIMESTAMP}, - - - - - - - - update sys_user - set id = #{record.id,jdbcType=VARCHAR}, - login_id = #{record.loginId,jdbcType=VARCHAR}, - name = #{record.name,jdbcType=VARCHAR}, - password = #{record.password,jdbcType=VARCHAR}, - key_sign = #{record.keySign,jdbcType=VARCHAR}, - area_code = #{record.areaCode,jdbcType=VARCHAR}, - approve_code = #{record.approveCode,jdbcType=VARCHAR}, - department = #{record.department,jdbcType=VARCHAR}, - status = #{record.status,jdbcType=INTEGER}, - role_id = #{record.roleId,jdbcType=VARCHAR}, - sys_type = #{record.sysType,jdbcType=INTEGER}, - cooperation_mode = #{record.cooperationMode,jdbcType=VARCHAR}, - email = #{record.email,jdbcType=VARCHAR}, - phone = #{record.phone,jdbcType=VARCHAR}, - deleted = #{record.deleted,jdbcType=BIT}, - create_user_id = #{record.createUserId,jdbcType=VARCHAR}, - create_time = #{record.createTime,jdbcType=TIMESTAMP}, - update_user_id = #{record.updateUserId,jdbcType=VARCHAR}, - update_time = #{record.updateTime,jdbcType=TIMESTAMP} - - - - - - update sys_user - - - login_id = #{loginId,jdbcType=VARCHAR}, - - - name = #{name,jdbcType=VARCHAR}, - - - password = #{password,jdbcType=VARCHAR}, - - - key_sign = #{keySign,jdbcType=VARCHAR}, - - - area_code = #{areaCode,jdbcType=VARCHAR}, - - - approve_code = #{approveCode,jdbcType=VARCHAR}, - - - department = #{department,jdbcType=VARCHAR}, - - - status = #{status,jdbcType=INTEGER}, - - - role_id = #{roleId,jdbcType=VARCHAR}, - - - sys_type = #{sysType,jdbcType=INTEGER}, - - - cooperation_mode = #{cooperationMode,jdbcType=VARCHAR}, - - - email = #{email,jdbcType=VARCHAR}, - - - phone = #{phone,jdbcType=VARCHAR}, - - - deleted = #{deleted,jdbcType=BIT}, - - - create_user_id = #{createUserId,jdbcType=VARCHAR}, - - - create_time = #{createTime,jdbcType=TIMESTAMP}, - - - update_user_id = #{updateUserId,jdbcType=VARCHAR}, - - - update_time = #{updateTime,jdbcType=TIMESTAMP}, - - - where id = #{id,jdbcType=VARCHAR} - - - update sys_user - set login_id = #{loginId,jdbcType=VARCHAR}, - name = #{name,jdbcType=VARCHAR}, - password = #{password,jdbcType=VARCHAR}, - key_sign = #{keySign,jdbcType=VARCHAR}, - area_code = #{areaCode,jdbcType=VARCHAR}, - approve_code = #{approveCode,jdbcType=VARCHAR}, - department = #{department,jdbcType=VARCHAR}, - status = #{status,jdbcType=INTEGER}, - role_id = #{roleId,jdbcType=VARCHAR}, - sys_type = #{sysType,jdbcType=INTEGER}, - cooperation_mode = #{cooperationMode,jdbcType=VARCHAR}, - email = #{email,jdbcType=VARCHAR}, - phone = #{phone,jdbcType=VARCHAR}, - deleted = #{deleted,jdbcType=BIT}, - create_user_id = #{createUserId,jdbcType=VARCHAR}, - create_time = #{createTime,jdbcType=TIMESTAMP}, - update_user_id = #{updateUserId,jdbcType=VARCHAR}, - update_time = #{updateTime,jdbcType=TIMESTAMP} - where id = #{id,jdbcType=VARCHAR} - - \ No newline at end of file diff --git a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/model/SysUser.java b/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/model/SysUser.java deleted file mode 100644 index f8dc4e8c..00000000 --- a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/model/SysUser.java +++ /dev/null @@ -1,198 +0,0 @@ -package com.cpq.txapp01.sysuser.model; - -import java.io.Serializable; -import java.util.Date; - -public class SysUser implements Serializable { - private String id; - - private String loginId; - - private String name; - - private String password; - - private String keySign; - - private String areaCode; - - private String approveCode; - - private String department; - - private Integer status; - - private String roleId; - - private Integer sysType; - - private String cooperationMode; - - private String email; - - private String phone; - - private Boolean deleted; - - private String createUserId; - - private Date createTime; - - private String updateUserId; - - private Date updateTime; - - private static final long serialVersionUID = 1L; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id == null ? null : id.trim(); - } - - public String getLoginId() { - return loginId; - } - - public void setLoginId(String loginId) { - this.loginId = loginId == null ? null : loginId.trim(); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name == null ? null : name.trim(); - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password == null ? null : password.trim(); - } - - public String getKeySign() { - return keySign; - } - - public void setKeySign(String keySign) { - this.keySign = keySign == null ? null : keySign.trim(); - } - - public String getAreaCode() { - return areaCode; - } - - public void setAreaCode(String areaCode) { - this.areaCode = areaCode == null ? null : areaCode.trim(); - } - - public String getApproveCode() { - return approveCode; - } - - public void setApproveCode(String approveCode) { - this.approveCode = approveCode == null ? null : approveCode.trim(); - } - - public String getDepartment() { - return department; - } - - public void setDepartment(String department) { - this.department = department == null ? null : department.trim(); - } - - public Integer getStatus() { - return status; - } - - public void setStatus(Integer status) { - this.status = status; - } - - public String getRoleId() { - return roleId; - } - - public void setRoleId(String roleId) { - this.roleId = roleId == null ? null : roleId.trim(); - } - - public Integer getSysType() { - return sysType; - } - - public void setSysType(Integer sysType) { - this.sysType = sysType; - } - - public String getCooperationMode() { - return cooperationMode; - } - - public void setCooperationMode(String cooperationMode) { - this.cooperationMode = cooperationMode == null ? null : cooperationMode.trim(); - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email == null ? null : email.trim(); - } - - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone == null ? null : phone.trim(); - } - - public Boolean getDeleted() { - return deleted; - } - - public void setDeleted(Boolean deleted) { - this.deleted = deleted; - } - - public String getCreateUserId() { - return createUserId; - } - - public void setCreateUserId(String createUserId) { - this.createUserId = createUserId == null ? null : createUserId.trim(); - } - - public Date getCreateTime() { - return createTime; - } - - public void setCreateTime(Date createTime) { - this.createTime = createTime; - } - - public String getUpdateUserId() { - return updateUserId; - } - - public void setUpdateUserId(String updateUserId) { - this.updateUserId = updateUserId == null ? null : updateUserId.trim(); - } - - public Date getUpdateTime() { - return updateTime; - } - - public void setUpdateTime(Date updateTime) { - this.updateTime = updateTime; - } -} \ No newline at end of file diff --git a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/model/SysUserExample.java b/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/model/SysUserExample.java deleted file mode 100644 index c97f25f6..00000000 --- a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/model/SysUserExample.java +++ /dev/null @@ -1,1481 +0,0 @@ -package com.cpq.txapp01.sysuser.model; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -public class SysUserExample { - protected String orderByClause; - - protected boolean distinct; - - protected List oredCriteria; - - public SysUserExample() { - oredCriteria = new ArrayList(); - } - - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - - public String getOrderByClause() { - return orderByClause; - } - - public void setDistinct(boolean distinct) { - this.distinct = distinct; - } - - public boolean isDistinct() { - return distinct; - } - - public List getOredCriteria() { - return oredCriteria; - } - - public void or(Criteria criteria) { - oredCriteria.add(criteria); - } - - public Criteria or() { - Criteria criteria = createCriteriaInternal(); - oredCriteria.add(criteria); - return criteria; - } - - public Criteria createCriteria() { - Criteria criteria = createCriteriaInternal(); - if (oredCriteria.size() == 0) { - oredCriteria.add(criteria); - } - return criteria; - } - - protected Criteria createCriteriaInternal() { - Criteria criteria = new Criteria(); - return criteria; - } - - public void clear() { - oredCriteria.clear(); - orderByClause = null; - distinct = false; - } - - protected abstract static class GeneratedCriteria { - protected List criteria; - - protected GeneratedCriteria() { - super(); - criteria = new ArrayList(); - } - - public boolean isValid() { - return criteria.size() > 0; - } - - public List getAllCriteria() { - return criteria; - } - - public List getCriteria() { - return criteria; - } - - protected void addCriterion(String condition) { - if (condition == null) { - throw new RuntimeException("Value for condition cannot be null"); - } - criteria.add(new Criterion(condition)); - } - - protected void addCriterion(String condition, Object value, String property) { - if (value == null) { - throw new RuntimeException("Value for " + property + " cannot be null"); - } - criteria.add(new Criterion(condition, value)); - } - - protected void addCriterion(String condition, Object value1, Object value2, String property) { - if (value1 == null || value2 == null) { - throw new RuntimeException("Between values for " + property + " cannot be null"); - } - criteria.add(new Criterion(condition, value1, value2)); - } - - public Criteria andIdIsNull() { - addCriterion("id is null"); - return (Criteria) this; - } - - public Criteria andIdIsNotNull() { - addCriterion("id is not null"); - return (Criteria) this; - } - - public Criteria andIdEqualTo(String value) { - addCriterion("id =", value, "id"); - return (Criteria) this; - } - - public Criteria andIdNotEqualTo(String value) { - addCriterion("id <>", value, "id"); - return (Criteria) this; - } - - public Criteria andIdGreaterThan(String value) { - addCriterion("id >", value, "id"); - return (Criteria) this; - } - - public Criteria andIdGreaterThanOrEqualTo(String value) { - addCriterion("id >=", value, "id"); - return (Criteria) this; - } - - public Criteria andIdLessThan(String value) { - addCriterion("id <", value, "id"); - return (Criteria) this; - } - - public Criteria andIdLessThanOrEqualTo(String value) { - addCriterion("id <=", value, "id"); - return (Criteria) this; - } - - public Criteria andIdLike(String value) { - addCriterion("id like", value, "id"); - return (Criteria) this; - } - - public Criteria andIdNotLike(String value) { - addCriterion("id not like", value, "id"); - return (Criteria) this; - } - - public Criteria andIdIn(List values) { - addCriterion("id in", values, "id"); - return (Criteria) this; - } - - public Criteria andIdNotIn(List values) { - addCriterion("id not in", values, "id"); - return (Criteria) this; - } - - public Criteria andIdBetween(String value1, String value2) { - addCriterion("id between", value1, value2, "id"); - return (Criteria) this; - } - - public Criteria andIdNotBetween(String value1, String value2) { - addCriterion("id not between", value1, value2, "id"); - return (Criteria) this; - } - - public Criteria andLoginIdIsNull() { - addCriterion("login_id is null"); - return (Criteria) this; - } - - public Criteria andLoginIdIsNotNull() { - addCriterion("login_id is not null"); - return (Criteria) this; - } - - public Criteria andLoginIdEqualTo(String value) { - addCriterion("login_id =", value, "loginId"); - return (Criteria) this; - } - - public Criteria andLoginIdNotEqualTo(String value) { - addCriterion("login_id <>", value, "loginId"); - return (Criteria) this; - } - - public Criteria andLoginIdGreaterThan(String value) { - addCriterion("login_id >", value, "loginId"); - return (Criteria) this; - } - - public Criteria andLoginIdGreaterThanOrEqualTo(String value) { - addCriterion("login_id >=", value, "loginId"); - return (Criteria) this; - } - - public Criteria andLoginIdLessThan(String value) { - addCriterion("login_id <", value, "loginId"); - return (Criteria) this; - } - - public Criteria andLoginIdLessThanOrEqualTo(String value) { - addCriterion("login_id <=", value, "loginId"); - return (Criteria) this; - } - - public Criteria andLoginIdLike(String value) { - addCriterion("login_id like", value, "loginId"); - return (Criteria) this; - } - - public Criteria andLoginIdNotLike(String value) { - addCriterion("login_id not like", value, "loginId"); - return (Criteria) this; - } - - public Criteria andLoginIdIn(List values) { - addCriterion("login_id in", values, "loginId"); - return (Criteria) this; - } - - public Criteria andLoginIdNotIn(List values) { - addCriterion("login_id not in", values, "loginId"); - return (Criteria) this; - } - - public Criteria andLoginIdBetween(String value1, String value2) { - addCriterion("login_id between", value1, value2, "loginId"); - return (Criteria) this; - } - - public Criteria andLoginIdNotBetween(String value1, String value2) { - addCriterion("login_id not between", value1, value2, "loginId"); - return (Criteria) this; - } - - public Criteria andNameIsNull() { - addCriterion("name is null"); - return (Criteria) this; - } - - public Criteria andNameIsNotNull() { - addCriterion("name is not null"); - return (Criteria) this; - } - - public Criteria andNameEqualTo(String value) { - addCriterion("name =", value, "name"); - return (Criteria) this; - } - - public Criteria andNameNotEqualTo(String value) { - addCriterion("name <>", value, "name"); - return (Criteria) this; - } - - public Criteria andNameGreaterThan(String value) { - addCriterion("name >", value, "name"); - return (Criteria) this; - } - - public Criteria andNameGreaterThanOrEqualTo(String value) { - addCriterion("name >=", value, "name"); - return (Criteria) this; - } - - public Criteria andNameLessThan(String value) { - addCriterion("name <", value, "name"); - return (Criteria) this; - } - - public Criteria andNameLessThanOrEqualTo(String value) { - addCriterion("name <=", value, "name"); - return (Criteria) this; - } - - public Criteria andNameLike(String value) { - addCriterion("name like", value, "name"); - return (Criteria) this; - } - - public Criteria andNameNotLike(String value) { - addCriterion("name not like", value, "name"); - return (Criteria) this; - } - - public Criteria andNameIn(List values) { - addCriterion("name in", values, "name"); - return (Criteria) this; - } - - public Criteria andNameNotIn(List values) { - addCriterion("name not in", values, "name"); - return (Criteria) this; - } - - public Criteria andNameBetween(String value1, String value2) { - addCriterion("name between", value1, value2, "name"); - return (Criteria) this; - } - - public Criteria andNameNotBetween(String value1, String value2) { - addCriterion("name not between", value1, value2, "name"); - return (Criteria) this; - } - - public Criteria andPasswordIsNull() { - addCriterion("password is null"); - return (Criteria) this; - } - - public Criteria andPasswordIsNotNull() { - addCriterion("password is not null"); - return (Criteria) this; - } - - public Criteria andPasswordEqualTo(String value) { - addCriterion("password =", value, "password"); - return (Criteria) this; - } - - public Criteria andPasswordNotEqualTo(String value) { - addCriterion("password <>", value, "password"); - return (Criteria) this; - } - - public Criteria andPasswordGreaterThan(String value) { - addCriterion("password >", value, "password"); - return (Criteria) this; - } - - public Criteria andPasswordGreaterThanOrEqualTo(String value) { - addCriterion("password >=", value, "password"); - return (Criteria) this; - } - - public Criteria andPasswordLessThan(String value) { - addCriterion("password <", value, "password"); - return (Criteria) this; - } - - public Criteria andPasswordLessThanOrEqualTo(String value) { - addCriterion("password <=", value, "password"); - return (Criteria) this; - } - - public Criteria andPasswordLike(String value) { - addCriterion("password like", value, "password"); - return (Criteria) this; - } - - public Criteria andPasswordNotLike(String value) { - addCriterion("password not like", value, "password"); - return (Criteria) this; - } - - public Criteria andPasswordIn(List values) { - addCriterion("password in", values, "password"); - return (Criteria) this; - } - - public Criteria andPasswordNotIn(List values) { - addCriterion("password not in", values, "password"); - return (Criteria) this; - } - - public Criteria andPasswordBetween(String value1, String value2) { - addCriterion("password between", value1, value2, "password"); - return (Criteria) this; - } - - public Criteria andPasswordNotBetween(String value1, String value2) { - addCriterion("password not between", value1, value2, "password"); - return (Criteria) this; - } - - public Criteria andKeySignIsNull() { - addCriterion("key_sign is null"); - return (Criteria) this; - } - - public Criteria andKeySignIsNotNull() { - addCriterion("key_sign is not null"); - return (Criteria) this; - } - - public Criteria andKeySignEqualTo(String value) { - addCriterion("key_sign =", value, "keySign"); - return (Criteria) this; - } - - public Criteria andKeySignNotEqualTo(String value) { - addCriterion("key_sign <>", value, "keySign"); - return (Criteria) this; - } - - public Criteria andKeySignGreaterThan(String value) { - addCriterion("key_sign >", value, "keySign"); - return (Criteria) this; - } - - public Criteria andKeySignGreaterThanOrEqualTo(String value) { - addCriterion("key_sign >=", value, "keySign"); - return (Criteria) this; - } - - public Criteria andKeySignLessThan(String value) { - addCriterion("key_sign <", value, "keySign"); - return (Criteria) this; - } - - public Criteria andKeySignLessThanOrEqualTo(String value) { - addCriterion("key_sign <=", value, "keySign"); - return (Criteria) this; - } - - public Criteria andKeySignLike(String value) { - addCriterion("key_sign like", value, "keySign"); - return (Criteria) this; - } - - public Criteria andKeySignNotLike(String value) { - addCriterion("key_sign not like", value, "keySign"); - return (Criteria) this; - } - - public Criteria andKeySignIn(List values) { - addCriterion("key_sign in", values, "keySign"); - return (Criteria) this; - } - - public Criteria andKeySignNotIn(List values) { - addCriterion("key_sign not in", values, "keySign"); - return (Criteria) this; - } - - public Criteria andKeySignBetween(String value1, String value2) { - addCriterion("key_sign between", value1, value2, "keySign"); - return (Criteria) this; - } - - public Criteria andKeySignNotBetween(String value1, String value2) { - addCriterion("key_sign not between", value1, value2, "keySign"); - return (Criteria) this; - } - - public Criteria andAreaCodeIsNull() { - addCriterion("area_code is null"); - return (Criteria) this; - } - - public Criteria andAreaCodeIsNotNull() { - addCriterion("area_code is not null"); - return (Criteria) this; - } - - public Criteria andAreaCodeEqualTo(String value) { - addCriterion("area_code =", value, "areaCode"); - return (Criteria) this; - } - - public Criteria andAreaCodeNotEqualTo(String value) { - addCriterion("area_code <>", value, "areaCode"); - return (Criteria) this; - } - - public Criteria andAreaCodeGreaterThan(String value) { - addCriterion("area_code >", value, "areaCode"); - return (Criteria) this; - } - - public Criteria andAreaCodeGreaterThanOrEqualTo(String value) { - addCriterion("area_code >=", value, "areaCode"); - return (Criteria) this; - } - - public Criteria andAreaCodeLessThan(String value) { - addCriterion("area_code <", value, "areaCode"); - return (Criteria) this; - } - - public Criteria andAreaCodeLessThanOrEqualTo(String value) { - addCriterion("area_code <=", value, "areaCode"); - return (Criteria) this; - } - - public Criteria andAreaCodeLike(String value) { - addCriterion("area_code like", value, "areaCode"); - return (Criteria) this; - } - - public Criteria andAreaCodeNotLike(String value) { - addCriterion("area_code not like", value, "areaCode"); - return (Criteria) this; - } - - public Criteria andAreaCodeIn(List values) { - addCriterion("area_code in", values, "areaCode"); - return (Criteria) this; - } - - public Criteria andAreaCodeNotIn(List values) { - addCriterion("area_code not in", values, "areaCode"); - return (Criteria) this; - } - - public Criteria andAreaCodeBetween(String value1, String value2) { - addCriterion("area_code between", value1, value2, "areaCode"); - return (Criteria) this; - } - - public Criteria andAreaCodeNotBetween(String value1, String value2) { - addCriterion("area_code not between", value1, value2, "areaCode"); - return (Criteria) this; - } - - public Criteria andApproveCodeIsNull() { - addCriterion("approve_code is null"); - return (Criteria) this; - } - - public Criteria andApproveCodeIsNotNull() { - addCriterion("approve_code is not null"); - return (Criteria) this; - } - - public Criteria andApproveCodeEqualTo(String value) { - addCriterion("approve_code =", value, "approveCode"); - return (Criteria) this; - } - - public Criteria andApproveCodeNotEqualTo(String value) { - addCriterion("approve_code <>", value, "approveCode"); - return (Criteria) this; - } - - public Criteria andApproveCodeGreaterThan(String value) { - addCriterion("approve_code >", value, "approveCode"); - return (Criteria) this; - } - - public Criteria andApproveCodeGreaterThanOrEqualTo(String value) { - addCriterion("approve_code >=", value, "approveCode"); - return (Criteria) this; - } - - public Criteria andApproveCodeLessThan(String value) { - addCriterion("approve_code <", value, "approveCode"); - return (Criteria) this; - } - - public Criteria andApproveCodeLessThanOrEqualTo(String value) { - addCriterion("approve_code <=", value, "approveCode"); - return (Criteria) this; - } - - public Criteria andApproveCodeLike(String value) { - addCriterion("approve_code like", value, "approveCode"); - return (Criteria) this; - } - - public Criteria andApproveCodeNotLike(String value) { - addCriterion("approve_code not like", value, "approveCode"); - return (Criteria) this; - } - - public Criteria andApproveCodeIn(List values) { - addCriterion("approve_code in", values, "approveCode"); - return (Criteria) this; - } - - public Criteria andApproveCodeNotIn(List values) { - addCriterion("approve_code not in", values, "approveCode"); - return (Criteria) this; - } - - public Criteria andApproveCodeBetween(String value1, String value2) { - addCriterion("approve_code between", value1, value2, "approveCode"); - return (Criteria) this; - } - - public Criteria andApproveCodeNotBetween(String value1, String value2) { - addCriterion("approve_code not between", value1, value2, "approveCode"); - return (Criteria) this; - } - - public Criteria andDepartmentIsNull() { - addCriterion("department is null"); - return (Criteria) this; - } - - public Criteria andDepartmentIsNotNull() { - addCriterion("department is not null"); - return (Criteria) this; - } - - public Criteria andDepartmentEqualTo(String value) { - addCriterion("department =", value, "department"); - return (Criteria) this; - } - - public Criteria andDepartmentNotEqualTo(String value) { - addCriterion("department <>", value, "department"); - return (Criteria) this; - } - - public Criteria andDepartmentGreaterThan(String value) { - addCriterion("department >", value, "department"); - return (Criteria) this; - } - - public Criteria andDepartmentGreaterThanOrEqualTo(String value) { - addCriterion("department >=", value, "department"); - return (Criteria) this; - } - - public Criteria andDepartmentLessThan(String value) { - addCriterion("department <", value, "department"); - return (Criteria) this; - } - - public Criteria andDepartmentLessThanOrEqualTo(String value) { - addCriterion("department <=", value, "department"); - return (Criteria) this; - } - - public Criteria andDepartmentLike(String value) { - addCriterion("department like", value, "department"); - return (Criteria) this; - } - - public Criteria andDepartmentNotLike(String value) { - addCriterion("department not like", value, "department"); - return (Criteria) this; - } - - public Criteria andDepartmentIn(List values) { - addCriterion("department in", values, "department"); - return (Criteria) this; - } - - public Criteria andDepartmentNotIn(List values) { - addCriterion("department not in", values, "department"); - return (Criteria) this; - } - - public Criteria andDepartmentBetween(String value1, String value2) { - addCriterion("department between", value1, value2, "department"); - return (Criteria) this; - } - - public Criteria andDepartmentNotBetween(String value1, String value2) { - addCriterion("department not between", value1, value2, "department"); - return (Criteria) this; - } - - public Criteria andStatusIsNull() { - addCriterion("status is null"); - return (Criteria) this; - } - - public Criteria andStatusIsNotNull() { - addCriterion("status is not null"); - return (Criteria) this; - } - - public Criteria andStatusEqualTo(Integer value) { - addCriterion("status =", value, "status"); - return (Criteria) this; - } - - public Criteria andStatusNotEqualTo(Integer value) { - addCriterion("status <>", value, "status"); - return (Criteria) this; - } - - public Criteria andStatusGreaterThan(Integer value) { - addCriterion("status >", value, "status"); - return (Criteria) this; - } - - public Criteria andStatusGreaterThanOrEqualTo(Integer value) { - addCriterion("status >=", value, "status"); - return (Criteria) this; - } - - public Criteria andStatusLessThan(Integer value) { - addCriterion("status <", value, "status"); - return (Criteria) this; - } - - public Criteria andStatusLessThanOrEqualTo(Integer value) { - addCriterion("status <=", value, "status"); - return (Criteria) this; - } - - public Criteria andStatusIn(List values) { - addCriterion("status in", values, "status"); - return (Criteria) this; - } - - public Criteria andStatusNotIn(List values) { - addCriterion("status not in", values, "status"); - return (Criteria) this; - } - - public Criteria andStatusBetween(Integer value1, Integer value2) { - addCriterion("status between", value1, value2, "status"); - return (Criteria) this; - } - - public Criteria andStatusNotBetween(Integer value1, Integer value2) { - addCriterion("status not between", value1, value2, "status"); - return (Criteria) this; - } - - public Criteria andRoleIdIsNull() { - addCriterion("role_id is null"); - return (Criteria) this; - } - - public Criteria andRoleIdIsNotNull() { - addCriterion("role_id is not null"); - return (Criteria) this; - } - - public Criteria andRoleIdEqualTo(String value) { - addCriterion("role_id =", value, "roleId"); - return (Criteria) this; - } - - public Criteria andRoleIdNotEqualTo(String value) { - addCriterion("role_id <>", value, "roleId"); - return (Criteria) this; - } - - public Criteria andRoleIdGreaterThan(String value) { - addCriterion("role_id >", value, "roleId"); - return (Criteria) this; - } - - public Criteria andRoleIdGreaterThanOrEqualTo(String value) { - addCriterion("role_id >=", value, "roleId"); - return (Criteria) this; - } - - public Criteria andRoleIdLessThan(String value) { - addCriterion("role_id <", value, "roleId"); - return (Criteria) this; - } - - public Criteria andRoleIdLessThanOrEqualTo(String value) { - addCriterion("role_id <=", value, "roleId"); - return (Criteria) this; - } - - public Criteria andRoleIdLike(String value) { - addCriterion("role_id like", value, "roleId"); - return (Criteria) this; - } - - public Criteria andRoleIdNotLike(String value) { - addCriterion("role_id not like", value, "roleId"); - return (Criteria) this; - } - - public Criteria andRoleIdIn(List values) { - addCriterion("role_id in", values, "roleId"); - return (Criteria) this; - } - - public Criteria andRoleIdNotIn(List values) { - addCriterion("role_id not in", values, "roleId"); - return (Criteria) this; - } - - public Criteria andRoleIdBetween(String value1, String value2) { - addCriterion("role_id between", value1, value2, "roleId"); - return (Criteria) this; - } - - public Criteria andRoleIdNotBetween(String value1, String value2) { - addCriterion("role_id not between", value1, value2, "roleId"); - return (Criteria) this; - } - - public Criteria andSysTypeIsNull() { - addCriterion("sys_type is null"); - return (Criteria) this; - } - - public Criteria andSysTypeIsNotNull() { - addCriterion("sys_type is not null"); - return (Criteria) this; - } - - public Criteria andSysTypeEqualTo(Integer value) { - addCriterion("sys_type =", value, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeNotEqualTo(Integer value) { - addCriterion("sys_type <>", value, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeGreaterThan(Integer value) { - addCriterion("sys_type >", value, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeGreaterThanOrEqualTo(Integer value) { - addCriterion("sys_type >=", value, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeLessThan(Integer value) { - addCriterion("sys_type <", value, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeLessThanOrEqualTo(Integer value) { - addCriterion("sys_type <=", value, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeIn(List values) { - addCriterion("sys_type in", values, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeNotIn(List values) { - addCriterion("sys_type not in", values, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeBetween(Integer value1, Integer value2) { - addCriterion("sys_type between", value1, value2, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeNotBetween(Integer value1, Integer value2) { - addCriterion("sys_type not between", value1, value2, "sysType"); - return (Criteria) this; - } - - public Criteria andCooperationModeIsNull() { - addCriterion("cooperation_mode is null"); - return (Criteria) this; - } - - public Criteria andCooperationModeIsNotNull() { - addCriterion("cooperation_mode is not null"); - return (Criteria) this; - } - - public Criteria andCooperationModeEqualTo(String value) { - addCriterion("cooperation_mode =", value, "cooperationMode"); - return (Criteria) this; - } - - public Criteria andCooperationModeNotEqualTo(String value) { - addCriterion("cooperation_mode <>", value, "cooperationMode"); - return (Criteria) this; - } - - public Criteria andCooperationModeGreaterThan(String value) { - addCriterion("cooperation_mode >", value, "cooperationMode"); - return (Criteria) this; - } - - public Criteria andCooperationModeGreaterThanOrEqualTo(String value) { - addCriterion("cooperation_mode >=", value, "cooperationMode"); - return (Criteria) this; - } - - public Criteria andCooperationModeLessThan(String value) { - addCriterion("cooperation_mode <", value, "cooperationMode"); - return (Criteria) this; - } - - public Criteria andCooperationModeLessThanOrEqualTo(String value) { - addCriterion("cooperation_mode <=", value, "cooperationMode"); - return (Criteria) this; - } - - public Criteria andCooperationModeLike(String value) { - addCriterion("cooperation_mode like", value, "cooperationMode"); - return (Criteria) this; - } - - public Criteria andCooperationModeNotLike(String value) { - addCriterion("cooperation_mode not like", value, "cooperationMode"); - return (Criteria) this; - } - - public Criteria andCooperationModeIn(List values) { - addCriterion("cooperation_mode in", values, "cooperationMode"); - return (Criteria) this; - } - - public Criteria andCooperationModeNotIn(List values) { - addCriterion("cooperation_mode not in", values, "cooperationMode"); - return (Criteria) this; - } - - public Criteria andCooperationModeBetween(String value1, String value2) { - addCriterion("cooperation_mode between", value1, value2, "cooperationMode"); - return (Criteria) this; - } - - public Criteria andCooperationModeNotBetween(String value1, String value2) { - addCriterion("cooperation_mode not between", value1, value2, "cooperationMode"); - return (Criteria) this; - } - - public Criteria andEmailIsNull() { - addCriterion("email is null"); - return (Criteria) this; - } - - public Criteria andEmailIsNotNull() { - addCriterion("email is not null"); - return (Criteria) this; - } - - public Criteria andEmailEqualTo(String value) { - addCriterion("email =", value, "email"); - return (Criteria) this; - } - - public Criteria andEmailNotEqualTo(String value) { - addCriterion("email <>", value, "email"); - return (Criteria) this; - } - - public Criteria andEmailGreaterThan(String value) { - addCriterion("email >", value, "email"); - return (Criteria) this; - } - - public Criteria andEmailGreaterThanOrEqualTo(String value) { - addCriterion("email >=", value, "email"); - return (Criteria) this; - } - - public Criteria andEmailLessThan(String value) { - addCriterion("email <", value, "email"); - return (Criteria) this; - } - - public Criteria andEmailLessThanOrEqualTo(String value) { - addCriterion("email <=", value, "email"); - return (Criteria) this; - } - - public Criteria andEmailLike(String value) { - addCriterion("email like", value, "email"); - return (Criteria) this; - } - - public Criteria andEmailNotLike(String value) { - addCriterion("email not like", value, "email"); - return (Criteria) this; - } - - public Criteria andEmailIn(List values) { - addCriterion("email in", values, "email"); - return (Criteria) this; - } - - public Criteria andEmailNotIn(List values) { - addCriterion("email not in", values, "email"); - return (Criteria) this; - } - - public Criteria andEmailBetween(String value1, String value2) { - addCriterion("email between", value1, value2, "email"); - return (Criteria) this; - } - - public Criteria andEmailNotBetween(String value1, String value2) { - addCriterion("email not between", value1, value2, "email"); - return (Criteria) this; - } - - public Criteria andPhoneIsNull() { - addCriterion("phone is null"); - return (Criteria) this; - } - - public Criteria andPhoneIsNotNull() { - addCriterion("phone is not null"); - return (Criteria) this; - } - - public Criteria andPhoneEqualTo(String value) { - addCriterion("phone =", value, "phone"); - return (Criteria) this; - } - - public Criteria andPhoneNotEqualTo(String value) { - addCriterion("phone <>", value, "phone"); - return (Criteria) this; - } - - public Criteria andPhoneGreaterThan(String value) { - addCriterion("phone >", value, "phone"); - return (Criteria) this; - } - - public Criteria andPhoneGreaterThanOrEqualTo(String value) { - addCriterion("phone >=", value, "phone"); - return (Criteria) this; - } - - public Criteria andPhoneLessThan(String value) { - addCriterion("phone <", value, "phone"); - return (Criteria) this; - } - - public Criteria andPhoneLessThanOrEqualTo(String value) { - addCriterion("phone <=", value, "phone"); - return (Criteria) this; - } - - public Criteria andPhoneLike(String value) { - addCriterion("phone like", value, "phone"); - return (Criteria) this; - } - - public Criteria andPhoneNotLike(String value) { - addCriterion("phone not like", value, "phone"); - return (Criteria) this; - } - - public Criteria andPhoneIn(List values) { - addCriterion("phone in", values, "phone"); - return (Criteria) this; - } - - public Criteria andPhoneNotIn(List values) { - addCriterion("phone not in", values, "phone"); - return (Criteria) this; - } - - public Criteria andPhoneBetween(String value1, String value2) { - addCriterion("phone between", value1, value2, "phone"); - return (Criteria) this; - } - - public Criteria andPhoneNotBetween(String value1, String value2) { - addCriterion("phone not between", value1, value2, "phone"); - return (Criteria) this; - } - - public Criteria andDeletedIsNull() { - addCriterion("deleted is null"); - return (Criteria) this; - } - - public Criteria andDeletedIsNotNull() { - addCriterion("deleted is not null"); - return (Criteria) this; - } - - public Criteria andDeletedEqualTo(Boolean value) { - addCriterion("deleted =", value, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedNotEqualTo(Boolean value) { - addCriterion("deleted <>", value, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedGreaterThan(Boolean value) { - addCriterion("deleted >", value, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedGreaterThanOrEqualTo(Boolean value) { - addCriterion("deleted >=", value, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedLessThan(Boolean value) { - addCriterion("deleted <", value, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedLessThanOrEqualTo(Boolean value) { - addCriterion("deleted <=", value, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedIn(List values) { - addCriterion("deleted in", values, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedNotIn(List values) { - addCriterion("deleted not in", values, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedBetween(Boolean value1, Boolean value2) { - addCriterion("deleted between", value1, value2, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedNotBetween(Boolean value1, Boolean value2) { - addCriterion("deleted not between", value1, value2, "deleted"); - return (Criteria) this; - } - - public Criteria andCreateUserIdIsNull() { - addCriterion("create_user_id is null"); - return (Criteria) this; - } - - public Criteria andCreateUserIdIsNotNull() { - addCriterion("create_user_id is not null"); - return (Criteria) this; - } - - public Criteria andCreateUserIdEqualTo(String value) { - addCriterion("create_user_id =", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdNotEqualTo(String value) { - addCriterion("create_user_id <>", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdGreaterThan(String value) { - addCriterion("create_user_id >", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdGreaterThanOrEqualTo(String value) { - addCriterion("create_user_id >=", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdLessThan(String value) { - addCriterion("create_user_id <", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdLessThanOrEqualTo(String value) { - addCriterion("create_user_id <=", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdLike(String value) { - addCriterion("create_user_id like", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdNotLike(String value) { - addCriterion("create_user_id not like", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdIn(List values) { - addCriterion("create_user_id in", values, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdNotIn(List values) { - addCriterion("create_user_id not in", values, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdBetween(String value1, String value2) { - addCriterion("create_user_id between", value1, value2, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdNotBetween(String value1, String value2) { - addCriterion("create_user_id not between", value1, value2, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateTimeIsNull() { - addCriterion("create_time is null"); - return (Criteria) this; - } - - public Criteria andCreateTimeIsNotNull() { - addCriterion("create_time is not null"); - return (Criteria) this; - } - - public Criteria andCreateTimeEqualTo(Date value) { - addCriterion("create_time =", value, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeNotEqualTo(Date value) { - addCriterion("create_time <>", value, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeGreaterThan(Date value) { - addCriterion("create_time >", value, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeGreaterThanOrEqualTo(Date value) { - addCriterion("create_time >=", value, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeLessThan(Date value) { - addCriterion("create_time <", value, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeLessThanOrEqualTo(Date value) { - addCriterion("create_time <=", value, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeIn(List values) { - addCriterion("create_time in", values, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeNotIn(List values) { - addCriterion("create_time not in", values, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeBetween(Date value1, Date value2) { - addCriterion("create_time between", value1, value2, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeNotBetween(Date value1, Date value2) { - addCriterion("create_time not between", value1, value2, "createTime"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdIsNull() { - addCriterion("update_user_id is null"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdIsNotNull() { - addCriterion("update_user_id is not null"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdEqualTo(String value) { - addCriterion("update_user_id =", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdNotEqualTo(String value) { - addCriterion("update_user_id <>", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdGreaterThan(String value) { - addCriterion("update_user_id >", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdGreaterThanOrEqualTo(String value) { - addCriterion("update_user_id >=", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdLessThan(String value) { - addCriterion("update_user_id <", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdLessThanOrEqualTo(String value) { - addCriterion("update_user_id <=", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdLike(String value) { - addCriterion("update_user_id like", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdNotLike(String value) { - addCriterion("update_user_id not like", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdIn(List values) { - addCriterion("update_user_id in", values, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdNotIn(List values) { - addCriterion("update_user_id not in", values, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdBetween(String value1, String value2) { - addCriterion("update_user_id between", value1, value2, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdNotBetween(String value1, String value2) { - addCriterion("update_user_id not between", value1, value2, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateTimeIsNull() { - addCriterion("update_time is null"); - return (Criteria) this; - } - - public Criteria andUpdateTimeIsNotNull() { - addCriterion("update_time is not null"); - return (Criteria) this; - } - - public Criteria andUpdateTimeEqualTo(Date value) { - addCriterion("update_time =", value, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeNotEqualTo(Date value) { - addCriterion("update_time <>", value, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeGreaterThan(Date value) { - addCriterion("update_time >", value, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeGreaterThanOrEqualTo(Date value) { - addCriterion("update_time >=", value, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeLessThan(Date value) { - addCriterion("update_time <", value, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeLessThanOrEqualTo(Date value) { - addCriterion("update_time <=", value, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeIn(List values) { - addCriterion("update_time in", values, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeNotIn(List values) { - addCriterion("update_time not in", values, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeBetween(Date value1, Date value2) { - addCriterion("update_time between", value1, value2, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeNotBetween(Date value1, Date value2) { - addCriterion("update_time not between", value1, value2, "updateTime"); - return (Criteria) this; - } - } - - public static class Criteria extends GeneratedCriteria { - - protected Criteria() { - super(); - } - } - - public static class Criterion { - private String condition; - - private Object value; - - private Object secondValue; - - private boolean noValue; - - private boolean singleValue; - - private boolean betweenValue; - - private boolean listValue; - - private String typeHandler; - - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - - protected Criterion(String condition) { - super(); - this.condition = condition; - this.typeHandler = null; - this.noValue = true; - } - - protected Criterion(String condition, Object value, String typeHandler) { - super(); - this.condition = condition; - this.value = value; - this.typeHandler = typeHandler; - if (value instanceof List) { - this.listValue = true; - } else { - this.singleValue = true; - } - } - - protected Criterion(String condition, Object value) { - this(condition, value, null); - } - - protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { - super(); - this.condition = condition; - this.value = value; - this.secondValue = secondValue; - this.typeHandler = typeHandler; - this.betweenValue = true; - } - - protected Criterion(String condition, Object value, Object secondValue) { - this(condition, value, secondValue, null); - } - } -} \ No newline at end of file diff --git a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/service/SysUserService.java b/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/service/SysUserService.java deleted file mode 100644 index c4b090be..00000000 --- a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/service/SysUserService.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cpq.txapp01.sysuser.service; - -import com.alibaba.fastjson.JSONObject; - -public interface SysUserService{ - - JSONObject add(); - - JSONObject app01app02demo1demo2(); - - JSONObject restsave(); - -} diff --git a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/service/SysUserServiceImpl.java b/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/service/SysUserServiceImpl.java deleted file mode 100644 index fad914b3..00000000 --- a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/sysuser/service/SysUserServiceImpl.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.cpq.txapp01.sysuser.service; - -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tx.annotation.ITxTransaction; -import com.codingapi.tx.annotation.TxTransaction; -import com.cpq.txapp01.sysuser.client.Demo1Client; -import com.cpq.txapp01.sysuser.client.TxApp02Client; -import com.cpq.txapp01.sysuser.mapper.SysUserMapper; -import com.cpq.txapp01.sysuser.model.SysUser; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.client.RestTemplate; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -@Service -public class SysUserServiceImpl implements SysUserService, ITxTransaction { - - @Autowired - private TxApp02Client txApp02Client; - @Autowired - private Demo1Client demo1Client; - @Autowired - private RestTemplate restTemplate; - @Autowired - SysUserMapper sysUserMapper; - - @Override - @TxTransaction(isStart = true) - @Transactional - public JSONObject add() { - - SysUser user = new SysUser(); - user.setId(UUID.randomUUID().toString().replace("-","")); - user.setName("name01"); - int num = sysUserMapper.insert(user); - - Map map = new HashMap<>(); - Object jsonObject = txApp02Client.add(); - - int v = 100/0; - - return new JSONObject(); - } - - @Override - @TxTransaction(isStart = true) - @Transactional - public JSONObject app01app02demo1demo2() { - - SysUser user = new SysUser(); - user.setId(UUID.randomUUID().toString().replace("-","")); - user.setName("app01app02demo1demo2"); - int num = sysUserMapper.insert(user); - System.out.println("执行----com.cpq.txapp01.sysuser.service.SysUserServiceImpl.app01app02demo1demo2"); - - demo1Client.save(); - - txApp02Client.add(); - - //这里抛异常,前面的代码都回滚 - //int v = 100/0; - - return new JSONObject(); - } - - @Override - @TxTransaction(isStart = true) - @Transactional - public JSONObject restsave() { - - SysUser user = new SysUser(); - user.setId(UUID.randomUUID().toString().replace("-","")); - user.setName("rrrrrrr"); - int num = sysUserMapper.insert(user); - System.out.println("执行----com.cpq.txapp01.sysuser.service.SysUserServiceImpl.restsave"); - - restTemplate.getForObject("http://springcloud-mybatis-demo1/test01/restSave", String.class); - restTemplate.postForObject("http://tx-app002/sys/role/add", new HashMap<>(), String.class); - - //这里抛异常,前面的代码都回滚 - int v = 100/0; - - return new JSONObject(); - } - -} diff --git a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/txlcn/TxManagerHttpRequestServiceImpl.java b/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/txlcn/TxManagerHttpRequestServiceImpl.java deleted file mode 100644 index ed304f71..00000000 --- a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/txlcn/TxManagerHttpRequestServiceImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.cpq.txapp01.txlcn; - -import com.codingapi.tx.netty.service.TxManagerHttpRequestService; -import com.lorne.core.framework.utils.http.HttpUtils; -import org.springframework.stereotype.Service; - -@Service -public class TxManagerHttpRequestServiceImpl implements TxManagerHttpRequestService{ - - @Override - public String httpGet(String url) { - return HttpUtils.get(url); - } - - @Override - public String httpPost(String url, String params) { - return HttpUtils.post(url,params); - } -} diff --git a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/txlcn/TxManagerTxUrlServiceImpl.java b/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/txlcn/TxManagerTxUrlServiceImpl.java deleted file mode 100644 index 865f2668..00000000 --- a/spring-cloud-learn/tx-app01/src/main/java/com/cpq/txapp01/txlcn/TxManagerTxUrlServiceImpl.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.cpq.txapp01.txlcn; - -import com.codingapi.tx.config.service.TxManagerTxUrlService; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -@Service -public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService{ - @Value("${tm.manager.url}") - private String url; - - @Override - public String getTxUrl() { - return url; - } -} diff --git a/spring-cloud-learn/tx-app01/src/main/resources/application.properties b/spring-cloud-learn/tx-app01/src/main/resources/application.properties deleted file mode 100644 index b88fd162..00000000 --- a/spring-cloud-learn/tx-app01/src/main/resources/application.properties +++ /dev/null @@ -1,39 +0,0 @@ -spring.application.name=tx-app01 -server.port=7001 - -logging.level.com.cpq.txapp01=debug - -eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ -#\u4F7F\u7528ip\u66FF\u4EE3\u5B9E\u4F8B\u540D -eureka.instance.prefer-ip-address=true -#\u8BBE\u7F6E\u5B9E\u4F8B\u7684ID\u4E3A\uFF1A \u670D\u52A1\u540D:port -eureka.instance.instance-id=${spring.application.name}:${server.port} - -#feign.hystrix.enabled=true -## \u5173\u4E8Espringcloud-hystrix\u673A\u5236 http://www.jianshu.com/p/b8d21248c9b1 -#hystrix.command.default.execution.isolation.strategy= SEMAPHORE -#hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000 - -#Ribbon\u7684\u8D1F\u8F7D\u5747\u8861\u7B56\u7565 -ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule -ribbon.MaxAutoRetriesNextServer=0 - -#txmanager\u5730\u5740 -tm.manager.url=http://127.0.0.1:7000/tx/manager/ - -spring.datasource.type=com.alibaba.druid.pool.DruidDataSource -spring.datasource.driver-class-name = com.mysql.jdbc.Driver -spring.datasource.url= jdbc:mysql://localhost:3306/cpq -spring.datasource.username= root -spring.datasource.password=cpq..123 -spring.datasource.initialSize=10 -spring.datasource.maxActive=50 -spring.datasource.minIdle=0 -spring.datasource.maxWait=60000 -spring.datasource.testWhileIdle=true -spring.datasource.testOnBorrow=false -spring.datasource.poolPreparedStatements=false - - - - diff --git a/spring-cloud-learn/tx-app01/src/main/resources/generatorConfig.xml b/spring-cloud-learn/tx-app01/src/main/resources/generatorConfig.xml deleted file mode 100644 index 5176bbee..00000000 --- a/spring-cloud-learn/tx-app01/src/main/resources/generatorConfig.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-cloud-learn/tx-app02/pom.xml b/spring-cloud-learn/tx-app02/pom.xml deleted file mode 100644 index 1f4ec429..00000000 --- a/spring-cloud-learn/tx-app02/pom.xml +++ /dev/null @@ -1,172 +0,0 @@ - - - 4.0.0 - - com.cpq - tx-app02 - 0.0.1-SNAPSHOT - jar - - tx-app02 - Demo project for Spring Boot - - - org.springframework.boot - spring-boot-starter-parent - 1.5.9.RELEASE - - - - - org.springframework.cloud - spring-cloud-dependencies - Edgware.SR2 - pom - import - - - - - - UTF-8 - UTF-8 - - - - - - org.springframework.cloud - spring-cloud-starter-eureka - - - org.springframework.cloud - spring-cloud-starter-feign - - - org.springframework.boot - spring-boot-starter-actuator - - - - - org.mybatis.spring.boot - mybatis-spring-boot-starter - 1.1.1 - - - com.alibaba - druid - 1.0.19 - - - mysql - mysql-connector-java - 5.1.43 - - - - - com.codingapi - transaction-springcloud - 4.1.0 - - - org.slf4j - * - - - - - com.codingapi - tx-plugins-db - 4.1.0 - - - org.slf4j - * - - - - - - - com.alibaba - fastjson - 1.2.41 - - - - - io.springfox - springfox-swagger2 - 2.2.2 - - - io.springfox - springfox-swagger-ui - 2.2.2 - - - - - - tx-app02 - - - - src/main/java - - **/*.xml - - false - - - - src/main/resources - - **/*.* - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - org.springframework.boot - spring-boot-maven-plugin - - - true - - - - - org.mybatis.generator - mybatis-generator-maven-plugin - 1.3.6 - - false - true - ${basedir}/src/main/resources/generatorConfig.xml - - - - mysql - mysql-connector-java - - 5.1.44 - - - - - - - diff --git a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/Application.java b/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/Application.java deleted file mode 100644 index 33a4f610..00000000 --- a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/Application.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.cpq.txapp02; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; -import org.springframework.cloud.netflix.feign.EnableFeignClients; - -@EnableAutoConfiguration -@SpringBootApplication -@EnableEurekaClient -@EnableFeignClients -@MapperScan("com.cpq.txapp02.*.mapper") -public class Application { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } -} diff --git a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/client/TxApp01.java b/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/client/TxApp01.java deleted file mode 100644 index 5a07d729..00000000 --- a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/client/TxApp01.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.cpq.txapp02.sysrole.client; - -import com.alibaba.fastjson.JSONObject; -import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - -@FeignClient(value = "tx-app001") -public interface TxApp01 { - - @RequestMapping(value = "/sys/user/add",method = RequestMethod.POST) - JSONObject save(); - -} diff --git a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/controller/SysRoleController.java b/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/controller/SysRoleController.java deleted file mode 100644 index 6a97f2f6..00000000 --- a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/controller/SysRoleController.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.cpq.txapp02.sysrole.controller; - -import com.alibaba.fastjson.JSONObject; -import com.cpq.txapp02.sysrole.client.TxApp01; -import com.cpq.txapp02.sysrole.service.SysRoleServiceImpl; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletRequest; -import java.util.concurrent.TimeUnit; - -@RestController -@RequestMapping("/sys/role") -public class SysRoleController { - @Autowired - TxApp01 txApp01; - - @Autowired - SysRoleServiceImpl sysRoleService; - - @PostMapping("/add") - public JSONObject add(HttpServletRequest request) throws Exception{ - sysRoleService.add(); - System.out.println("tx-app02已经执行"); - JSONObject json = new JSONObject(); - json.put("code", 0); - return json; - } - -} diff --git a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/mapper/SysRoleMapper.java b/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/mapper/SysRoleMapper.java deleted file mode 100644 index 4dd24531..00000000 --- a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/mapper/SysRoleMapper.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.cpq.txapp02.sysrole.mapper; - -import com.cpq.txapp02.sysrole.model.SysRole; -import com.cpq.txapp02.sysrole.model.SysRoleExample; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -@Mapper -public interface SysRoleMapper { - long countByExample(SysRoleExample example); - - int deleteByExample(SysRoleExample example); - - int deleteByPrimaryKey(String id); - - int insert(SysRole record); - - int insertSelective(SysRole record); - - List selectByExample(SysRoleExample example); - - SysRole selectByPrimaryKey(String id); - - int updateByExampleSelective(@Param("record") SysRole record, @Param("example") SysRoleExample example); - - int updateByExample(@Param("record") SysRole record, @Param("example") SysRoleExample example); - - int updateByPrimaryKeySelective(SysRole record); - - int updateByPrimaryKey(SysRole record); -} \ No newline at end of file diff --git a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/mapper/SysRoleMapper.xml b/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/mapper/SysRoleMapper.xml deleted file mode 100644 index a40d7eb3..00000000 --- a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/mapper/SysRoleMapper.xml +++ /dev/null @@ -1,291 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - and ${criterion.condition} - - - and ${criterion.condition} #{criterion.value} - - - and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} - - - and ${criterion.condition} - - #{listItem} - - - - - - - - - - - - - - - - - - and ${criterion.condition} - - - and ${criterion.condition} #{criterion.value} - - - and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} - - - and ${criterion.condition} - - #{listItem} - - - - - - - - - - - id, name, sys_type, remark, status, create_time, create_user_id, update_time, update_user_id, - resources - - - - - delete from sys_role - where id = #{id,jdbcType=VARCHAR} - - - delete from sys_role - - - - - - insert into sys_role (id, name, sys_type, - remark, status, create_time, - create_user_id, update_time, update_user_id, - resources) - values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{sysType,jdbcType=INTEGER}, - #{remark,jdbcType=VARCHAR}, #{status,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP}, - #{createUserId,jdbcType=VARCHAR}, #{updateTime,jdbcType=TIMESTAMP}, #{updateUserId,jdbcType=VARCHAR}, - #{resources,jdbcType=VARCHAR}) - - - insert into sys_role - - - id, - - - name, - - - sys_type, - - - remark, - - - status, - - - create_time, - - - create_user_id, - - - update_time, - - - update_user_id, - - - resources, - - - - - #{id,jdbcType=VARCHAR}, - - - #{name,jdbcType=VARCHAR}, - - - #{sysType,jdbcType=INTEGER}, - - - #{remark,jdbcType=VARCHAR}, - - - #{status,jdbcType=INTEGER}, - - - #{createTime,jdbcType=TIMESTAMP}, - - - #{createUserId,jdbcType=VARCHAR}, - - - #{updateTime,jdbcType=TIMESTAMP}, - - - #{updateUserId,jdbcType=VARCHAR}, - - - #{resources,jdbcType=VARCHAR}, - - - - - - update sys_role - - - id = #{record.id,jdbcType=VARCHAR}, - - - name = #{record.name,jdbcType=VARCHAR}, - - - sys_type = #{record.sysType,jdbcType=INTEGER}, - - - remark = #{record.remark,jdbcType=VARCHAR}, - - - status = #{record.status,jdbcType=INTEGER}, - - - create_time = #{record.createTime,jdbcType=TIMESTAMP}, - - - create_user_id = #{record.createUserId,jdbcType=VARCHAR}, - - - update_time = #{record.updateTime,jdbcType=TIMESTAMP}, - - - update_user_id = #{record.updateUserId,jdbcType=VARCHAR}, - - - resources = #{record.resources,jdbcType=VARCHAR}, - - - - - - - - update sys_role - set id = #{record.id,jdbcType=VARCHAR}, - name = #{record.name,jdbcType=VARCHAR}, - sys_type = #{record.sysType,jdbcType=INTEGER}, - remark = #{record.remark,jdbcType=VARCHAR}, - status = #{record.status,jdbcType=INTEGER}, - create_time = #{record.createTime,jdbcType=TIMESTAMP}, - create_user_id = #{record.createUserId,jdbcType=VARCHAR}, - update_time = #{record.updateTime,jdbcType=TIMESTAMP}, - update_user_id = #{record.updateUserId,jdbcType=VARCHAR}, - resources = #{record.resources,jdbcType=VARCHAR} - - - - - - update sys_role - - - name = #{name,jdbcType=VARCHAR}, - - - sys_type = #{sysType,jdbcType=INTEGER}, - - - remark = #{remark,jdbcType=VARCHAR}, - - - status = #{status,jdbcType=INTEGER}, - - - create_time = #{createTime,jdbcType=TIMESTAMP}, - - - create_user_id = #{createUserId,jdbcType=VARCHAR}, - - - update_time = #{updateTime,jdbcType=TIMESTAMP}, - - - update_user_id = #{updateUserId,jdbcType=VARCHAR}, - - - resources = #{resources,jdbcType=VARCHAR}, - - - where id = #{id,jdbcType=VARCHAR} - - - update sys_role - set name = #{name,jdbcType=VARCHAR}, - sys_type = #{sysType,jdbcType=INTEGER}, - remark = #{remark,jdbcType=VARCHAR}, - status = #{status,jdbcType=INTEGER}, - create_time = #{createTime,jdbcType=TIMESTAMP}, - create_user_id = #{createUserId,jdbcType=VARCHAR}, - update_time = #{updateTime,jdbcType=TIMESTAMP}, - update_user_id = #{updateUserId,jdbcType=VARCHAR}, - resources = #{resources,jdbcType=VARCHAR} - where id = #{id,jdbcType=VARCHAR} - - \ No newline at end of file diff --git a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/model/SysRole.java b/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/model/SysRole.java deleted file mode 100644 index 0645a3cc..00000000 --- a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/model/SysRole.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.cpq.txapp02.sysrole.model; - -import java.io.Serializable; -import java.util.Date; - -public class SysRole implements Serializable { - private String id; - - private String name; - - private Integer sysType; - - private String remark; - - private Integer status; - - private Date createTime; - - private String createUserId; - - private Date updateTime; - - private String updateUserId; - - private String resources; - - private static final long serialVersionUID = 1L; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id == null ? null : id.trim(); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name == null ? null : name.trim(); - } - - public Integer getSysType() { - return sysType; - } - - public void setSysType(Integer sysType) { - this.sysType = sysType; - } - - public String getRemark() { - return remark; - } - - public void setRemark(String remark) { - this.remark = remark == null ? null : remark.trim(); - } - - public Integer getStatus() { - return status; - } - - public void setStatus(Integer status) { - this.status = status; - } - - public Date getCreateTime() { - return createTime; - } - - public void setCreateTime(Date createTime) { - this.createTime = createTime; - } - - public String getCreateUserId() { - return createUserId; - } - - public void setCreateUserId(String createUserId) { - this.createUserId = createUserId == null ? null : createUserId.trim(); - } - - public Date getUpdateTime() { - return updateTime; - } - - public void setUpdateTime(Date updateTime) { - this.updateTime = updateTime; - } - - public String getUpdateUserId() { - return updateUserId; - } - - public void setUpdateUserId(String updateUserId) { - this.updateUserId = updateUserId == null ? null : updateUserId.trim(); - } - - public String getResources() { - return resources; - } - - public void setResources(String resources) { - this.resources = resources == null ? null : resources.trim(); - } -} \ No newline at end of file diff --git a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/model/SysRoleExample.java b/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/model/SysRoleExample.java deleted file mode 100644 index 101ee2a1..00000000 --- a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/model/SysRoleExample.java +++ /dev/null @@ -1,861 +0,0 @@ -package com.cpq.txapp02.sysrole.model; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -public class SysRoleExample { - protected String orderByClause; - - protected boolean distinct; - - protected List oredCriteria; - - public SysRoleExample() { - oredCriteria = new ArrayList(); - } - - public void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - - public String getOrderByClause() { - return orderByClause; - } - - public void setDistinct(boolean distinct) { - this.distinct = distinct; - } - - public boolean isDistinct() { - return distinct; - } - - public List getOredCriteria() { - return oredCriteria; - } - - public void or(Criteria criteria) { - oredCriteria.add(criteria); - } - - public Criteria or() { - Criteria criteria = createCriteriaInternal(); - oredCriteria.add(criteria); - return criteria; - } - - public Criteria createCriteria() { - Criteria criteria = createCriteriaInternal(); - if (oredCriteria.size() == 0) { - oredCriteria.add(criteria); - } - return criteria; - } - - protected Criteria createCriteriaInternal() { - Criteria criteria = new Criteria(); - return criteria; - } - - public void clear() { - oredCriteria.clear(); - orderByClause = null; - distinct = false; - } - - protected abstract static class GeneratedCriteria { - protected List criteria; - - protected GeneratedCriteria() { - super(); - criteria = new ArrayList(); - } - - public boolean isValid() { - return criteria.size() > 0; - } - - public List getAllCriteria() { - return criteria; - } - - public List getCriteria() { - return criteria; - } - - protected void addCriterion(String condition) { - if (condition == null) { - throw new RuntimeException("Value for condition cannot be null"); - } - criteria.add(new Criterion(condition)); - } - - protected void addCriterion(String condition, Object value, String property) { - if (value == null) { - throw new RuntimeException("Value for " + property + " cannot be null"); - } - criteria.add(new Criterion(condition, value)); - } - - protected void addCriterion(String condition, Object value1, Object value2, String property) { - if (value1 == null || value2 == null) { - throw new RuntimeException("Between values for " + property + " cannot be null"); - } - criteria.add(new Criterion(condition, value1, value2)); - } - - public Criteria andIdIsNull() { - addCriterion("id is null"); - return (Criteria) this; - } - - public Criteria andIdIsNotNull() { - addCriterion("id is not null"); - return (Criteria) this; - } - - public Criteria andIdEqualTo(String value) { - addCriterion("id =", value, "id"); - return (Criteria) this; - } - - public Criteria andIdNotEqualTo(String value) { - addCriterion("id <>", value, "id"); - return (Criteria) this; - } - - public Criteria andIdGreaterThan(String value) { - addCriterion("id >", value, "id"); - return (Criteria) this; - } - - public Criteria andIdGreaterThanOrEqualTo(String value) { - addCriterion("id >=", value, "id"); - return (Criteria) this; - } - - public Criteria andIdLessThan(String value) { - addCriterion("id <", value, "id"); - return (Criteria) this; - } - - public Criteria andIdLessThanOrEqualTo(String value) { - addCriterion("id <=", value, "id"); - return (Criteria) this; - } - - public Criteria andIdLike(String value) { - addCriterion("id like", value, "id"); - return (Criteria) this; - } - - public Criteria andIdNotLike(String value) { - addCriterion("id not like", value, "id"); - return (Criteria) this; - } - - public Criteria andIdIn(List values) { - addCriterion("id in", values, "id"); - return (Criteria) this; - } - - public Criteria andIdNotIn(List values) { - addCriterion("id not in", values, "id"); - return (Criteria) this; - } - - public Criteria andIdBetween(String value1, String value2) { - addCriterion("id between", value1, value2, "id"); - return (Criteria) this; - } - - public Criteria andIdNotBetween(String value1, String value2) { - addCriterion("id not between", value1, value2, "id"); - return (Criteria) this; - } - - public Criteria andNameIsNull() { - addCriterion("name is null"); - return (Criteria) this; - } - - public Criteria andNameIsNotNull() { - addCriterion("name is not null"); - return (Criteria) this; - } - - public Criteria andNameEqualTo(String value) { - addCriterion("name =", value, "name"); - return (Criteria) this; - } - - public Criteria andNameNotEqualTo(String value) { - addCriterion("name <>", value, "name"); - return (Criteria) this; - } - - public Criteria andNameGreaterThan(String value) { - addCriterion("name >", value, "name"); - return (Criteria) this; - } - - public Criteria andNameGreaterThanOrEqualTo(String value) { - addCriterion("name >=", value, "name"); - return (Criteria) this; - } - - public Criteria andNameLessThan(String value) { - addCriterion("name <", value, "name"); - return (Criteria) this; - } - - public Criteria andNameLessThanOrEqualTo(String value) { - addCriterion("name <=", value, "name"); - return (Criteria) this; - } - - public Criteria andNameLike(String value) { - addCriterion("name like", value, "name"); - return (Criteria) this; - } - - public Criteria andNameNotLike(String value) { - addCriterion("name not like", value, "name"); - return (Criteria) this; - } - - public Criteria andNameIn(List values) { - addCriterion("name in", values, "name"); - return (Criteria) this; - } - - public Criteria andNameNotIn(List values) { - addCriterion("name not in", values, "name"); - return (Criteria) this; - } - - public Criteria andNameBetween(String value1, String value2) { - addCriterion("name between", value1, value2, "name"); - return (Criteria) this; - } - - public Criteria andNameNotBetween(String value1, String value2) { - addCriterion("name not between", value1, value2, "name"); - return (Criteria) this; - } - - public Criteria andSysTypeIsNull() { - addCriterion("sys_type is null"); - return (Criteria) this; - } - - public Criteria andSysTypeIsNotNull() { - addCriterion("sys_type is not null"); - return (Criteria) this; - } - - public Criteria andSysTypeEqualTo(Integer value) { - addCriterion("sys_type =", value, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeNotEqualTo(Integer value) { - addCriterion("sys_type <>", value, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeGreaterThan(Integer value) { - addCriterion("sys_type >", value, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeGreaterThanOrEqualTo(Integer value) { - addCriterion("sys_type >=", value, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeLessThan(Integer value) { - addCriterion("sys_type <", value, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeLessThanOrEqualTo(Integer value) { - addCriterion("sys_type <=", value, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeIn(List values) { - addCriterion("sys_type in", values, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeNotIn(List values) { - addCriterion("sys_type not in", values, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeBetween(Integer value1, Integer value2) { - addCriterion("sys_type between", value1, value2, "sysType"); - return (Criteria) this; - } - - public Criteria andSysTypeNotBetween(Integer value1, Integer value2) { - addCriterion("sys_type not between", value1, value2, "sysType"); - return (Criteria) this; - } - - public Criteria andRemarkIsNull() { - addCriterion("remark is null"); - return (Criteria) this; - } - - public Criteria andRemarkIsNotNull() { - addCriterion("remark is not null"); - return (Criteria) this; - } - - public Criteria andRemarkEqualTo(String value) { - addCriterion("remark =", value, "remark"); - return (Criteria) this; - } - - public Criteria andRemarkNotEqualTo(String value) { - addCriterion("remark <>", value, "remark"); - return (Criteria) this; - } - - public Criteria andRemarkGreaterThan(String value) { - addCriterion("remark >", value, "remark"); - return (Criteria) this; - } - - public Criteria andRemarkGreaterThanOrEqualTo(String value) { - addCriterion("remark >=", value, "remark"); - return (Criteria) this; - } - - public Criteria andRemarkLessThan(String value) { - addCriterion("remark <", value, "remark"); - return (Criteria) this; - } - - public Criteria andRemarkLessThanOrEqualTo(String value) { - addCriterion("remark <=", value, "remark"); - return (Criteria) this; - } - - public Criteria andRemarkLike(String value) { - addCriterion("remark like", value, "remark"); - return (Criteria) this; - } - - public Criteria andRemarkNotLike(String value) { - addCriterion("remark not like", value, "remark"); - return (Criteria) this; - } - - public Criteria andRemarkIn(List values) { - addCriterion("remark in", values, "remark"); - return (Criteria) this; - } - - public Criteria andRemarkNotIn(List values) { - addCriterion("remark not in", values, "remark"); - return (Criteria) this; - } - - public Criteria andRemarkBetween(String value1, String value2) { - addCriterion("remark between", value1, value2, "remark"); - return (Criteria) this; - } - - public Criteria andRemarkNotBetween(String value1, String value2) { - addCriterion("remark not between", value1, value2, "remark"); - return (Criteria) this; - } - - public Criteria andStatusIsNull() { - addCriterion("status is null"); - return (Criteria) this; - } - - public Criteria andStatusIsNotNull() { - addCriterion("status is not null"); - return (Criteria) this; - } - - public Criteria andStatusEqualTo(Integer value) { - addCriterion("status =", value, "status"); - return (Criteria) this; - } - - public Criteria andStatusNotEqualTo(Integer value) { - addCriterion("status <>", value, "status"); - return (Criteria) this; - } - - public Criteria andStatusGreaterThan(Integer value) { - addCriterion("status >", value, "status"); - return (Criteria) this; - } - - public Criteria andStatusGreaterThanOrEqualTo(Integer value) { - addCriterion("status >=", value, "status"); - return (Criteria) this; - } - - public Criteria andStatusLessThan(Integer value) { - addCriterion("status <", value, "status"); - return (Criteria) this; - } - - public Criteria andStatusLessThanOrEqualTo(Integer value) { - addCriterion("status <=", value, "status"); - return (Criteria) this; - } - - public Criteria andStatusIn(List values) { - addCriterion("status in", values, "status"); - return (Criteria) this; - } - - public Criteria andStatusNotIn(List values) { - addCriterion("status not in", values, "status"); - return (Criteria) this; - } - - public Criteria andStatusBetween(Integer value1, Integer value2) { - addCriterion("status between", value1, value2, "status"); - return (Criteria) this; - } - - public Criteria andStatusNotBetween(Integer value1, Integer value2) { - addCriterion("status not between", value1, value2, "status"); - return (Criteria) this; - } - - public Criteria andCreateTimeIsNull() { - addCriterion("create_time is null"); - return (Criteria) this; - } - - public Criteria andCreateTimeIsNotNull() { - addCriterion("create_time is not null"); - return (Criteria) this; - } - - public Criteria andCreateTimeEqualTo(Date value) { - addCriterion("create_time =", value, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeNotEqualTo(Date value) { - addCriterion("create_time <>", value, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeGreaterThan(Date value) { - addCriterion("create_time >", value, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeGreaterThanOrEqualTo(Date value) { - addCriterion("create_time >=", value, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeLessThan(Date value) { - addCriterion("create_time <", value, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeLessThanOrEqualTo(Date value) { - addCriterion("create_time <=", value, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeIn(List values) { - addCriterion("create_time in", values, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeNotIn(List values) { - addCriterion("create_time not in", values, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeBetween(Date value1, Date value2) { - addCriterion("create_time between", value1, value2, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateTimeNotBetween(Date value1, Date value2) { - addCriterion("create_time not between", value1, value2, "createTime"); - return (Criteria) this; - } - - public Criteria andCreateUserIdIsNull() { - addCriterion("create_user_id is null"); - return (Criteria) this; - } - - public Criteria andCreateUserIdIsNotNull() { - addCriterion("create_user_id is not null"); - return (Criteria) this; - } - - public Criteria andCreateUserIdEqualTo(String value) { - addCriterion("create_user_id =", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdNotEqualTo(String value) { - addCriterion("create_user_id <>", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdGreaterThan(String value) { - addCriterion("create_user_id >", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdGreaterThanOrEqualTo(String value) { - addCriterion("create_user_id >=", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdLessThan(String value) { - addCriterion("create_user_id <", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdLessThanOrEqualTo(String value) { - addCriterion("create_user_id <=", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdLike(String value) { - addCriterion("create_user_id like", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdNotLike(String value) { - addCriterion("create_user_id not like", value, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdIn(List values) { - addCriterion("create_user_id in", values, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdNotIn(List values) { - addCriterion("create_user_id not in", values, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdBetween(String value1, String value2) { - addCriterion("create_user_id between", value1, value2, "createUserId"); - return (Criteria) this; - } - - public Criteria andCreateUserIdNotBetween(String value1, String value2) { - addCriterion("create_user_id not between", value1, value2, "createUserId"); - return (Criteria) this; - } - - public Criteria andUpdateTimeIsNull() { - addCriterion("update_time is null"); - return (Criteria) this; - } - - public Criteria andUpdateTimeIsNotNull() { - addCriterion("update_time is not null"); - return (Criteria) this; - } - - public Criteria andUpdateTimeEqualTo(Date value) { - addCriterion("update_time =", value, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeNotEqualTo(Date value) { - addCriterion("update_time <>", value, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeGreaterThan(Date value) { - addCriterion("update_time >", value, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeGreaterThanOrEqualTo(Date value) { - addCriterion("update_time >=", value, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeLessThan(Date value) { - addCriterion("update_time <", value, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeLessThanOrEqualTo(Date value) { - addCriterion("update_time <=", value, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeIn(List values) { - addCriterion("update_time in", values, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeNotIn(List values) { - addCriterion("update_time not in", values, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeBetween(Date value1, Date value2) { - addCriterion("update_time between", value1, value2, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateTimeNotBetween(Date value1, Date value2) { - addCriterion("update_time not between", value1, value2, "updateTime"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdIsNull() { - addCriterion("update_user_id is null"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdIsNotNull() { - addCriterion("update_user_id is not null"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdEqualTo(String value) { - addCriterion("update_user_id =", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdNotEqualTo(String value) { - addCriterion("update_user_id <>", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdGreaterThan(String value) { - addCriterion("update_user_id >", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdGreaterThanOrEqualTo(String value) { - addCriterion("update_user_id >=", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdLessThan(String value) { - addCriterion("update_user_id <", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdLessThanOrEqualTo(String value) { - addCriterion("update_user_id <=", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdLike(String value) { - addCriterion("update_user_id like", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdNotLike(String value) { - addCriterion("update_user_id not like", value, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdIn(List values) { - addCriterion("update_user_id in", values, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdNotIn(List values) { - addCriterion("update_user_id not in", values, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdBetween(String value1, String value2) { - addCriterion("update_user_id between", value1, value2, "updateUserId"); - return (Criteria) this; - } - - public Criteria andUpdateUserIdNotBetween(String value1, String value2) { - addCriterion("update_user_id not between", value1, value2, "updateUserId"); - return (Criteria) this; - } - - public Criteria andResourcesIsNull() { - addCriterion("resources is null"); - return (Criteria) this; - } - - public Criteria andResourcesIsNotNull() { - addCriterion("resources is not null"); - return (Criteria) this; - } - - public Criteria andResourcesEqualTo(String value) { - addCriterion("resources =", value, "resources"); - return (Criteria) this; - } - - public Criteria andResourcesNotEqualTo(String value) { - addCriterion("resources <>", value, "resources"); - return (Criteria) this; - } - - public Criteria andResourcesGreaterThan(String value) { - addCriterion("resources >", value, "resources"); - return (Criteria) this; - } - - public Criteria andResourcesGreaterThanOrEqualTo(String value) { - addCriterion("resources >=", value, "resources"); - return (Criteria) this; - } - - public Criteria andResourcesLessThan(String value) { - addCriterion("resources <", value, "resources"); - return (Criteria) this; - } - - public Criteria andResourcesLessThanOrEqualTo(String value) { - addCriterion("resources <=", value, "resources"); - return (Criteria) this; - } - - public Criteria andResourcesLike(String value) { - addCriterion("resources like", value, "resources"); - return (Criteria) this; - } - - public Criteria andResourcesNotLike(String value) { - addCriterion("resources not like", value, "resources"); - return (Criteria) this; - } - - public Criteria andResourcesIn(List values) { - addCriterion("resources in", values, "resources"); - return (Criteria) this; - } - - public Criteria andResourcesNotIn(List values) { - addCriterion("resources not in", values, "resources"); - return (Criteria) this; - } - - public Criteria andResourcesBetween(String value1, String value2) { - addCriterion("resources between", value1, value2, "resources"); - return (Criteria) this; - } - - public Criteria andResourcesNotBetween(String value1, String value2) { - addCriterion("resources not between", value1, value2, "resources"); - return (Criteria) this; - } - } - - public static class Criteria extends GeneratedCriteria { - - protected Criteria() { - super(); - } - } - - public static class Criterion { - private String condition; - - private Object value; - - private Object secondValue; - - private boolean noValue; - - private boolean singleValue; - - private boolean betweenValue; - - private boolean listValue; - - private String typeHandler; - - public String getCondition() { - return condition; - } - - public Object getValue() { - return value; - } - - public Object getSecondValue() { - return secondValue; - } - - public boolean isNoValue() { - return noValue; - } - - public boolean isSingleValue() { - return singleValue; - } - - public boolean isBetweenValue() { - return betweenValue; - } - - public boolean isListValue() { - return listValue; - } - - public String getTypeHandler() { - return typeHandler; - } - - protected Criterion(String condition) { - super(); - this.condition = condition; - this.typeHandler = null; - this.noValue = true; - } - - protected Criterion(String condition, Object value, String typeHandler) { - super(); - this.condition = condition; - this.value = value; - this.typeHandler = typeHandler; - if (value instanceof List) { - this.listValue = true; - } else { - this.singleValue = true; - } - } - - protected Criterion(String condition, Object value) { - this(condition, value, null); - } - - protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { - super(); - this.condition = condition; - this.value = value; - this.secondValue = secondValue; - this.typeHandler = typeHandler; - this.betweenValue = true; - } - - protected Criterion(String condition, Object value, Object secondValue) { - this(condition, value, secondValue, null); - } - } -} \ No newline at end of file diff --git a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/service/SysRoleService.java b/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/service/SysRoleService.java deleted file mode 100644 index 493bc261..00000000 --- a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/service/SysRoleService.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.cpq.txapp02.sysrole.service; - -public interface SysRoleService{ - int add(); -} diff --git a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/service/SysRoleServiceImpl.java b/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/service/SysRoleServiceImpl.java deleted file mode 100644 index 7d1b1392..00000000 --- a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/sysrole/service/SysRoleServiceImpl.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.cpq.txapp02.sysrole.service; - -import com.codingapi.tx.annotation.TxTransaction; -import com.cpq.txapp02.sysrole.mapper.SysRoleMapper; -import com.cpq.txapp02.sysrole.model.SysRole; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.UUID; - -@Service -public class SysRoleServiceImpl implements SysRoleService{ - - @Autowired - SysRoleMapper sysRoleMapper; - - @Override - @Transactional - @TxTransaction - public int add(){ - - SysRole role = new SysRole(); - role.setId(UUID.randomUUID().toString().replace("-","")); - role.setName("name01"); - int num = sysRoleMapper.insert(role); - System.out.println("***********已执行*************"); - return num; - } -} diff --git a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/txlcn/TxManagerHttpRequestServiceImpl.java b/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/txlcn/TxManagerHttpRequestServiceImpl.java deleted file mode 100644 index 6a203425..00000000 --- a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/txlcn/TxManagerHttpRequestServiceImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.cpq.txapp02.txlcn; - -import com.codingapi.tx.netty.service.TxManagerHttpRequestService; -import com.lorne.core.framework.utils.http.HttpUtils; -import org.springframework.stereotype.Service; - -@Service -public class TxManagerHttpRequestServiceImpl implements TxManagerHttpRequestService{ - - @Override - public String httpGet(String url) { - return HttpUtils.get(url); - } - - @Override - public String httpPost(String url, String params) { - return HttpUtils.post(url,params); - } -} diff --git a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/txlcn/TxManagerTxUrlServiceImpl.java b/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/txlcn/TxManagerTxUrlServiceImpl.java deleted file mode 100644 index b25483ed..00000000 --- a/spring-cloud-learn/tx-app02/src/main/java/com/cpq/txapp02/txlcn/TxManagerTxUrlServiceImpl.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.cpq.txapp02.txlcn; - -import com.codingapi.tx.config.service.TxManagerTxUrlService; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -@Service -public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService{ - @Value("${tm.manager.url}") - private String url; - - @Override - public String getTxUrl() { - return url; - } -} diff --git a/spring-cloud-learn/tx-app02/src/main/resources/application.properties b/spring-cloud-learn/tx-app02/src/main/resources/application.properties deleted file mode 100644 index 7dc11a4f..00000000 --- a/spring-cloud-learn/tx-app02/src/main/resources/application.properties +++ /dev/null @@ -1,36 +0,0 @@ -spring.application.name = tx-app02 -server.port = 7002 - -eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ -#\u4F7F\u7528ip\u66FF\u4EE3\u5B9E\u4F8B\u540D -eureka.instance.prefer-ip-address=true -#\u8BBE\u7F6E\u5B9E\u4F8B\u7684ID\u4E3A\uFF1A \u670D\u52A1\u540D:port -eureka.instance.instance-id=${spring.application.name}:${server.port} - -#feign.hystrix.enabled=true -## \u5173\u4E8Espringcloud-hystrix\u673A\u5236 http://www.jianshu.com/p/b8d21248c9b1 -#hystrix.command.default.execution.isolation.strategy= SEMAPHORE -#hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000 - -#Ribbon\u7684\u8D1F\u8F7D\u5747\u8861\u7B56\u7565 -ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule -ribbon.MaxAutoRetriesNextServer=0 - -#txmanager\u5730\u5740 -tm.manager.url=http://127.0.0.1:7000/tx/manager/ - -spring.datasource.type=com.alibaba.druid.pool.DruidDataSource -spring.datasource.driver-class-name = com.mysql.jdbc.Driver -spring.datasource.url= jdbc:mysql://localhost:3306/cpq -spring.datasource.username= root -spring.datasource.password=cpq..123 -spring.datasource.initialSize=10 -spring.datasource.maxActive=50 -spring.datasource.minIdle=0 -spring.datasource.maxWait=60000 -spring.datasource.testWhileIdle=true -spring.datasource.testOnBorrow=false -spring.datasource.poolPreparedStatements=false - - - diff --git a/spring-cloud-learn/tx-app02/src/main/resources/generatorConfig.xml b/spring-cloud-learn/tx-app02/src/main/resources/generatorConfig.xml deleted file mode 100644 index 03bc24ee..00000000 --- a/spring-cloud-learn/tx-app02/src/main/resources/generatorConfig.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-cloud-learn/tx-manager/.gitignore b/spring-cloud-learn/tx-manager/.gitignore deleted file mode 100644 index 76331cfb..00000000 --- a/spring-cloud-learn/tx-manager/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -nbproject/private/ -nbbuild/ -dist/ -nbdist/ -.nb-gradle/ \ No newline at end of file diff --git a/spring-cloud-learn/tx-manager/pom.xml b/spring-cloud-learn/tx-manager/pom.xml deleted file mode 100644 index a0c5f052..00000000 --- a/spring-cloud-learn/tx-manager/pom.xml +++ /dev/null @@ -1,179 +0,0 @@ - - - 4.0.0 - - com.codingapi - tx-manager - 4.2.0-SNAPSHOT - jar - - tx-manager - tx-manager - - - org.springframework.boot - spring-boot-starter-parent - 1.5.4.RELEASE - - - - - UTF-8 - UTF-8 - 1.7 - 1.7 - 1.7 - - 19.0 - Dalston.SR1 - true - true - - - - - - - com.github.1991wangliang - lorne_core - 1.0.0 - - - org.slf4j - * - - - - - - - io.netty - netty-all - 4.1.12.Final - - - - org.springframework.cloud - spring-cloud-starter-eureka-server - - - com.google.guava - guava - - - - - - - - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-starter-redis - 1.3.8.RELEASE - - - - com.google.guava - guava - ${guava.version} - - - - - - - - - org.springframework.cloud - spring-cloud-dependencies - ${spring-cloud.version} - pom - import - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - ${maven.compile.source} - ${maven.compile.target} - ${project.build.sourceEncoding} - - - - - org.apache.maven.plugins - maven-deploy-plugin - 2.8.2 - - true - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.6 - - - - com.codingapi.tm.TxManagerApplication - true - lib/ - - - ./ - - - - config/** - - - - - - maven-assembly-plugin - - false - - src/main/build/package.xml - - - - - make-assembly - package - - single - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - - - diff --git a/spring-cloud-learn/tx-manager/src/main/build/package.xml b/spring-cloud-learn/tx-manager/src/main/build/package.xml deleted file mode 100644 index 5b5cf98e..00000000 --- a/spring-cloud-learn/tx-manager/src/main/build/package.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - package - - zip - - true - - - bin - / - - - src/main/resources - / - - - ${project.build.directory} - / - - *.jar - - - - - - lib - runtime - - ${groupId}:${artifactId} - - - - diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/Constants.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/Constants.java deleted file mode 100644 index 22252df8..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/Constants.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.codingapi.tm; - -/** - * Created by lorne on 2017/6/8. - */ -public class Constants { - - - public static int maxConnection; - - public static int socketPort; - - public static String address; - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/CorsConfig.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/CorsConfig.java deleted file mode 100644 index 53e4f9db..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/CorsConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.codingapi.tm; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.web.filter.CorsFilter; - -/** - * create by lorne on 2017/8/17 - */ -@Configuration -public class CorsConfig { - - /** - * 跨域过滤器 - * - * @return - */ - private CorsConfiguration buildConfig() { - CorsConfiguration corsConfiguration = new CorsConfiguration(); - corsConfiguration.addAllowedOrigin("*"); - corsConfiguration.addAllowedHeader("*"); - corsConfiguration.addAllowedMethod("*"); - return corsConfiguration; - } - - - /** - * 跨域过滤器 - * - * @return - */ - @Bean - public CorsFilter corsFilter() { - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", buildConfig()); // 4 - return new CorsFilter(source); - } -} \ No newline at end of file diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/RestConfig.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/RestConfig.java deleted file mode 100644 index 6583e362..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/RestConfig.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.codingapi.tm; - -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.client.RestTemplate; - -/** - * Created by lorne on 2017/7/5. - */ - -@Configuration -@EnableAutoConfiguration -public class RestConfig { - - - @Bean - public RestTemplate getRestTemplate() { - RestTemplate restTemplate = new RestTemplate(); - return restTemplate; - } - - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/ServletInitializer.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/ServletInitializer.java deleted file mode 100644 index f9ee98fd..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/ServletInitializer.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.codingapi.tm; - -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.web.support.SpringBootServletInitializer; - -/** - * Created by lorne on 2017/7/3. - */ -public class ServletInitializer extends SpringBootServletInitializer { - - - @Override - protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { - return builder.sources(TxManagerApplication.class); - } - - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/TxManagerApplication.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/TxManagerApplication.java deleted file mode 100644 index 156b2821..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/TxManagerApplication.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.codingapi.tm; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; - - -@SpringBootApplication -@EnableDiscoveryClient -public class TxManagerApplication { - - - - public static void main(String[] args) { - SpringApplication.run(TxManagerApplication.class, args); - } - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/controller/AdminController.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/controller/AdminController.java deleted file mode 100644 index 460d8863..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/controller/AdminController.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.codingapi.tm.api.controller; - -import com.codingapi.tm.api.service.ApiAdminService; -import com.codingapi.tm.api.service.ApiModelService; -import com.codingapi.tm.compensate.model.TxModel; -import com.codingapi.tm.model.ModelInfo; -import com.codingapi.tm.model.ModelName; -import com.codingapi.tm.model.TxState; -import com.lorne.core.framework.exception.ServiceException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * Created by lorne on 2017/7/1. - */ -@RestController -@RequestMapping("/admin") -public class AdminController { - - @Autowired - private ApiAdminService apiAdminService; - - @Autowired - private ApiModelService apiModelService; - - - @RequestMapping(value = "/onlines", method = RequestMethod.GET) - public List onlines() { - return apiModelService.onlines(); - } - - - @RequestMapping(value = "/setting", method = RequestMethod.GET) - public TxState setting() { - return apiAdminService.getState(); - } - - @RequestMapping(value = "/json", method = RequestMethod.GET) - public String json() { - return apiAdminService.loadNotifyJson(); - } - - @RequestMapping(value = "/modelList", method = RequestMethod.GET) - public List modelList() { - return apiAdminService.modelList(); - } - - @RequestMapping(value = "/modelTimes", method = RequestMethod.GET) - public List modelTimes(@RequestParam("model") String model) { - return apiAdminService.modelTimes(model); - } - - - @RequestMapping(value = "/modelInfos", method = RequestMethod.GET) - public List modelInfos(@RequestParam("path") String path) { - return apiAdminService.modelInfos(path); - } - - @RequestMapping(value = "/compensate", method = RequestMethod.GET) - public boolean compensate(@RequestParam("path") String path) throws ServiceException { - return apiAdminService.compensate(path); - } - - @RequestMapping(value = "/delCompensate", method = RequestMethod.GET) - public boolean delCompensate(@RequestParam("path") String path) throws ServiceException { - return apiAdminService.delCompensate(path); - } - - @RequestMapping(value = "/hasCompensate", method = RequestMethod.GET) - public boolean hasCompensate() throws ServiceException { - return apiAdminService.hasCompensate(); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/controller/TxManagerController.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/controller/TxManagerController.java deleted file mode 100644 index 91e7a5c0..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/controller/TxManagerController.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.codingapi.tm.api.controller; - -import com.codingapi.tm.api.service.ApiTxManagerService; -import com.codingapi.tm.model.TxServer; -import com.codingapi.tm.model.TxState; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -/** - * Created by lorne on 2017/7/1. - */ -@RestController -@RequestMapping("/tx/manager") -public class TxManagerController { - - @Autowired - private ApiTxManagerService apiTxManagerService; - - - @RequestMapping("/getServer") - public TxServer getServer() { - return apiTxManagerService.getServer(); - } - - - @RequestMapping("/cleanNotifyTransaction") - public int cleanNotifyTransaction(@RequestParam("groupId") String groupId,@RequestParam("taskId") String taskId) { - return apiTxManagerService.cleanNotifyTransaction(groupId,taskId); - } - - - @RequestMapping("/sendMsg") - public String sendMsg(@RequestParam("msg") String msg,@RequestParam("model") String model) { - return apiTxManagerService.sendMsg(model,msg); - } - - - @RequestMapping("/sendCompensateMsg") - public boolean sendCompensateMsg(@RequestParam("model") String model, @RequestParam("uniqueKey") String uniqueKey, - @RequestParam("currentTime") long currentTime, - @RequestParam("groupId") String groupId, @RequestParam("className") String className, - @RequestParam("time") int time, @RequestParam("data") String data, - @RequestParam("methodStr") String methodStr, @RequestParam("address") String address, - @RequestParam("startError") int startError) { - return apiTxManagerService.sendCompensateMsg(currentTime, groupId, model, address, uniqueKey, className, methodStr, data, time,startError); - } - - - - @RequestMapping("/state") - public TxState state() { - return apiTxManagerService.getState(); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/ApiAdminService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/ApiAdminService.java deleted file mode 100644 index cd799dc2..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/ApiAdminService.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.codingapi.tm.api.service; - -import com.codingapi.tm.compensate.model.TxModel; -import com.codingapi.tm.model.ModelName; -import com.codingapi.tm.model.TxState; -import com.lorne.core.framework.exception.ServiceException; - -import java.util.List; - -/** - * create by lorne on 2017/11/12 - */ -public interface ApiAdminService { - - TxState getState(); - - String loadNotifyJson(); - - List modelList(); - - - List modelTimes(String model); - - List modelInfos(String path); - - boolean compensate(String path) throws ServiceException; - - boolean hasCompensate(); - - boolean delCompensate(String path); - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/ApiModelService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/ApiModelService.java deleted file mode 100644 index 7bad0515..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/ApiModelService.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.codingapi.tm.api.service; - -import com.codingapi.tm.model.ModelInfo; - -import java.util.List; - -/** - * create by lorne on 2017/11/13 - */ -public interface ApiModelService { - - List onlines(); - - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/ApiTxManagerService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/ApiTxManagerService.java deleted file mode 100644 index e5c5f87b..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/ApiTxManagerService.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.codingapi.tm.api.service; - -import com.codingapi.tm.model.TxServer; -import com.codingapi.tm.model.TxState; - -/** - * Created by lorne on 2017/7/1. - */ -public interface ApiTxManagerService { - - - /** - * 获取本服务的信息 - * - * @return 服务信息 - */ - TxServer getServer(); - - - /** - * 给 tx模块发送指令 - * @param model 模块名称 - * @param msg 指令 - * @return 指令返回结果 - */ - String sendMsg(String model, String msg); - - /** - * 检查并清理事务数据 - * @param groupId 事务组Id - * @param taskId 任务Id - * @param isGroup 是否合并事务 - * @return 事务状态 - */ - int cleanNotifyTransaction(String groupId, String taskId); - - - /** - * 保存事务补偿日志信息 - * @param currentTime 时间 - * @param groupId 事务组id - * @param model 模块名称 - * @param address 模块地址 - * @param uniqueKey 唯一标示 - * @param className 事务启动类 - * @param methodStr 事务启动方法 - * @param data 切面数据 - * @param time 执行时间 - * @param startError 启动模块异常 - * @return 是否保存成功 - */ - boolean sendCompensateMsg(long currentTime, String groupId, String model, String address, String uniqueKey, String className, String methodStr, String data, int time,int startError); - - /** - * 获取服务器状态 - * - * @return 状态 - */ - TxState getState(); - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/impl/ApiAdminServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/impl/ApiAdminServiceImpl.java deleted file mode 100644 index 35388bf1..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/impl/ApiAdminServiceImpl.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.codingapi.tm.api.service.impl; - -import com.codingapi.tm.api.service.ApiAdminService; -import com.codingapi.tm.compensate.model.TxModel; -import com.codingapi.tm.compensate.service.CompensateService; -import com.codingapi.tm.manager.service.MicroService; -import com.codingapi.tm.model.ModelName; -import com.codingapi.tm.model.TxState; -import com.codingapi.tm.redis.service.RedisServerService; -import com.lorne.core.framework.exception.ServiceException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - * create by lorne on 2017/11/12 - */ -@Service -public class ApiAdminServiceImpl implements ApiAdminService { - - - @Autowired - private MicroService eurekaService; - - @Autowired - private RedisServerService redisServerService; - - @Autowired - private CompensateService compensateService; - - @Override - public TxState getState() { - return eurekaService.getState(); - } - - @Override - public String loadNotifyJson() { - return redisServerService.loadNotifyJson(); - } - - @Override - public List modelList() { - return compensateService.loadModelList(); - } - - - @Override - public List modelTimes(String model) { - return compensateService.loadCompensateTimes(model); - } - - @Override - public List modelInfos(String path) { - return compensateService.loadCompensateByModelAndTime(path); - } - - @Override - public boolean compensate(String path) throws ServiceException { - return compensateService.executeCompensate(path); - } - - @Override - public boolean delCompensate(String path) { - return compensateService.delCompensate(path); - } - - @Override - public boolean hasCompensate() { - return compensateService.hasCompensate(); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/impl/ApiModelServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/impl/ApiModelServiceImpl.java deleted file mode 100644 index c0d8cea3..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/impl/ApiModelServiceImpl.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.codingapi.tm.api.service.impl; - -import com.codingapi.tm.api.service.ApiModelService; -import com.codingapi.tm.manager.ModelInfoManager; -import com.codingapi.tm.model.ModelInfo; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - * create by lorne on 2017/11/13 - */ -@Service -public class ApiModelServiceImpl implements ApiModelService { - - - @Override - public List onlines() { - return ModelInfoManager.getInstance().getOnlines(); - } - - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/impl/ApiTxManagerServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/impl/ApiTxManagerServiceImpl.java deleted file mode 100644 index 37376f89..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/api/service/impl/ApiTxManagerServiceImpl.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.codingapi.tm.api.service.impl; - - -import com.codingapi.tm.api.service.ApiTxManagerService; -import com.codingapi.tm.compensate.model.TransactionCompensateMsg; -import com.codingapi.tm.compensate.service.CompensateService; -import com.codingapi.tm.config.ConfigReader; -import com.codingapi.tm.manager.service.MicroService; -import com.codingapi.tm.manager.service.TxManagerSenderService; -import com.codingapi.tm.manager.service.TxManagerService; -import com.codingapi.tm.model.TxServer; -import com.codingapi.tm.model.TxState; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * Created by lorne on 2017/7/1. - */ -@Service -public class ApiTxManagerServiceImpl implements ApiTxManagerService { - - - @Autowired - private TxManagerService managerService; - - @Autowired - private MicroService eurekaService; - - @Autowired - private CompensateService compensateService; - - - @Autowired - private TxManagerSenderService txManagerSenderService; - - @Autowired - private ConfigReader configReader; - - - @Override - public TxServer getServer() { - return eurekaService.getServer(); - } - - - @Override - public int cleanNotifyTransaction(String groupId, String taskId) { - return managerService.cleanNotifyTransaction(groupId,taskId); - } - - - @Override - public boolean sendCompensateMsg(long currentTime, String groupId, String model, String address, String uniqueKey, String className, String methodStr, String data, int time,int startError) { - TransactionCompensateMsg transactionCompensateMsg = new TransactionCompensateMsg(currentTime, groupId, model, address, uniqueKey, className, methodStr, data, time, 0,startError); - return compensateService.saveCompensateMsg(transactionCompensateMsg); - } - - @Override - public String sendMsg(String model,String msg) { - return txManagerSenderService.sendMsg(model, msg, configReader.getTransactionNettyDelayTime()); - } - - - @Override - public TxState getState() { - return eurekaService.getState(); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/dao/CompensateDao.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/dao/CompensateDao.java deleted file mode 100644 index e11f51e4..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/dao/CompensateDao.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.codingapi.tm.compensate.dao; - -import com.codingapi.tm.compensate.model.TransactionCompensateMsg; - -import java.util.List; - -/** - * create by lorne on 2017/11/11 - */ -public interface CompensateDao { - - String saveCompensateMsg(TransactionCompensateMsg transactionCompensateMsg); - - List loadCompensateKeys(); - - List loadCompensateTimes(String model); - - List loadCompensateByModelAndTime(String path); - - String getCompensate(String key); - - String getCompensateByGroupId(String groupId); - - void deleteCompensateByPath(String path); - - void deleteCompensateByKey(String key); - - boolean hasCompensate(); -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/dao/impl/CompensateDaoImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/dao/impl/CompensateDaoImpl.java deleted file mode 100644 index 46b7c227..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/dao/impl/CompensateDaoImpl.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.codingapi.tm.compensate.dao.impl; - -import com.alibaba.fastjson.JSON; -import com.codingapi.tm.compensate.dao.CompensateDao; -import com.codingapi.tm.compensate.model.TransactionCompensateMsg; -import com.codingapi.tm.config.ConfigReader; -import com.codingapi.tm.redis.service.RedisServerService; -import com.lorne.core.framework.utils.DateUtil; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; - -/** - * create by lorne on 2017/11/11 - */ -@Service -public class CompensateDaoImpl implements CompensateDao { - - - @Autowired - private RedisServerService redisServerService; - - @Autowired - private ConfigReader configReader; - - - @Override - public String saveCompensateMsg(TransactionCompensateMsg transactionCompensateMsg) { - - String name = String.format("%s%s:%s:%s.json", configReader.getKeyPrefixCompensate(), transactionCompensateMsg.getModel(), DateUtil.getCurrentDateFormat(), transactionCompensateMsg.getGroupId()); - - String json = JSON.toJSONString(transactionCompensateMsg); - - redisServerService.saveCompensateMsg(name, json); - - return name; - } - - - @Override - public List loadCompensateKeys() { - String key = configReader.getKeyPrefixCompensate() + "*"; - return redisServerService.getKeys(key); - } - - - @Override - public boolean hasCompensate() { - String key = configReader.getKeyPrefixCompensate() + "*"; - List keys = redisServerService.getKeys(key); - return keys != null && keys.size() > 0; - } - - @Override - public List loadCompensateTimes(String model) { - String key = configReader.getKeyPrefixCompensate() + model + ":*"; - List keys = redisServerService.getKeys(key); - List times = new ArrayList(); - for (String k : keys) { - if(k.length()>36) { - String time = k.substring(k.length() - 24, k.length() - 14); - if (!times.contains(time)) { - times.add(time); - } - } - } - return times; - } - - - @Override - public List loadCompensateByModelAndTime(String path) { - String key = String.format("%s%s*", configReader.getKeyPrefixCompensate(), path); - List keys = redisServerService.getKeys(key); - List values = redisServerService.getValuesByKeys(keys); - return values; - } - - @Override - public String getCompensate(String path) { - String key = String.format("%s%s.json", configReader.getKeyPrefixCompensate(), path); - return redisServerService.getValueByKey(key); - } - - - @Override - public void deleteCompensateByPath(String path) { - String key = String.format("%s%s.json", configReader.getKeyPrefixCompensate(), path); - redisServerService.deleteKey(key); - } - - - @Override - public void deleteCompensateByKey(String key) { - redisServerService.deleteKey(key); - } - - @Override - public String getCompensateByGroupId(String groupId) { - String key = String.format("%s*%s.json", configReader.getKeyPrefixCompensate(), groupId); - List keys = redisServerService.getKeys(key); - if (keys != null && keys.size() == 1) { - String k = keys.get(0); - return redisServerService.getValueByKey(k); - } - return null; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/model/TransactionCompensateMsg.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/model/TransactionCompensateMsg.java deleted file mode 100644 index 63748411..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/model/TransactionCompensateMsg.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.codingapi.tm.compensate.model; - -import com.codingapi.tm.netty.model.TxGroup; - -/** - * create by lorne on 2017/11/11 - */ -public class TransactionCompensateMsg { - - private long currentTime; - private String groupId; - private String model; - private String address; - private String uniqueKey; - private String className; - private String methodStr; - private String data; - private int time; - private int startError; - - private TxGroup txGroup; - - private int state; - - - public TransactionCompensateMsg(long currentTime, String groupId, String model, String address, - String uniqueKey, String className, - String methodStr, String data, int time, int state,int startError) { - this.currentTime = currentTime; - this.groupId = groupId; - this.model = model; - this.uniqueKey = uniqueKey; - this.className = className; - this.methodStr = methodStr; - this.data = data; - this.time = time; - this.address = address; - this.state = state; - this.startError = startError; - } - - public int getStartError() { - return startError; - } - - public void setStartError(int startError) { - this.startError = startError; - } - - public int getState() { - return state; - } - - public void setState(int state) { - this.state = state; - } - - public long getCurrentTime() { - return currentTime; - } - - public void setCurrentTime(long currentTime) { - this.currentTime = currentTime; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public TxGroup getTxGroup() { - return txGroup; - } - - public void setTxGroup(TxGroup txGroup) { - this.txGroup = txGroup; - } - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - public String getUniqueKey() { - return uniqueKey; - } - - public void setUniqueKey(String uniqueKey) { - this.uniqueKey = uniqueKey; - } - - public String getClassName() { - return className; - } - - public void setClassName(String className) { - this.className = className; - } - - - public String getMethodStr() { - return methodStr; - } - - public void setMethodStr(String methodStr) { - this.methodStr = methodStr; - } - - public String getData() { - return data; - } - - public void setData(String data) { - this.data = data; - } - - public int getTime() { - return time; - } - - public void setTime(int time) { - this.time = time; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/model/TxModel.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/model/TxModel.java deleted file mode 100644 index 7e54afbf..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/model/TxModel.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.codingapi.tm.compensate.model; - -/** - * create by lorne on 2017/11/12 - */ -public class TxModel { - - private String time; - - private String className; - - private String method; - - private int executeTime; - - private String base64; - - private int state; - - private long order; - - private String key; - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public long getOrder() { - return order; - } - - public void setOrder(long order) { - this.order = order; - } - - public String getBase64() { - return base64; - } - - public void setBase64(String base64) { - this.base64 = base64; - } - - public String getClassName() { - return className; - } - - public void setClassName(String className) { - this.className = className; - } - - public String getMethod() { - return method; - } - - public void setMethod(String method) { - this.method = method; - } - - public int getExecuteTime() { - return executeTime; - } - - public void setExecuteTime(int executeTime) { - this.executeTime = executeTime; - } - - public String getTime() { - return time; - } - - public void setTime(String time) { - this.time = time; - } - - public int getState() { - return state; - } - - public void setState(int state) { - this.state = state; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/service/CompensateService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/service/CompensateService.java deleted file mode 100644 index 94cac8a4..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/service/CompensateService.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.codingapi.tm.compensate.service; - -import com.codingapi.tm.compensate.model.TransactionCompensateMsg; -import com.codingapi.tm.compensate.model.TxModel; -import com.codingapi.tm.model.ModelName; -import com.codingapi.tm.netty.model.TxGroup; -import com.lorne.core.framework.exception.ServiceException; - -import java.util.List; - -/** - * create by lorne on 2017/11/11 - */ -public interface CompensateService { - - boolean saveCompensateMsg(TransactionCompensateMsg transactionCompensateMsg); - - List loadModelList(); - - List loadCompensateTimes(String model); - - List loadCompensateByModelAndTime(String path); - - void autoCompensate(String compensateKey, TransactionCompensateMsg transactionCompensateMsg); - - boolean executeCompensate(String key) throws ServiceException; - - void reloadCompensate(TxGroup txGroup); - - boolean hasCompensate(); - - boolean delCompensate(String path); - - TxGroup getCompensateByGroupId(String groupId); -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/service/impl/CompensateServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/service/impl/CompensateServiceImpl.java deleted file mode 100644 index 84d183bd..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/compensate/service/impl/CompensateServiceImpl.java +++ /dev/null @@ -1,336 +0,0 @@ -package com.codingapi.tm.compensate.service.impl; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tm.compensate.dao.CompensateDao; -import com.codingapi.tm.compensate.model.TransactionCompensateMsg; -import com.codingapi.tm.compensate.model.TxModel; -import com.codingapi.tm.compensate.service.CompensateService; -import com.codingapi.tm.config.ConfigReader; -import com.codingapi.tm.manager.ModelInfoManager; -import com.codingapi.tm.manager.service.TxManagerSenderService; -import com.codingapi.tm.manager.service.TxManagerService; -import com.codingapi.tm.model.ModelInfo; -import com.codingapi.tm.model.ModelName; -import com.codingapi.tm.netty.model.TxGroup; -import com.codingapi.tm.netty.model.TxInfo; -import com.google.common.collect.Lists; -import com.lorne.core.framework.exception.ServiceException; -import com.lorne.core.framework.utils.DateUtil; -import com.lorne.core.framework.utils.encode.Base64Utils; -import com.lorne.core.framework.utils.http.HttpUtils; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.*; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -/** - * create by lorne on 2017/11/11 - */ -@Service -public class CompensateServiceImpl implements CompensateService { - - - private Logger logger = LoggerFactory.getLogger(CompensateServiceImpl.class); - - @Autowired - private CompensateDao compensateDao; - - @Autowired - private ConfigReader configReader; - - @Autowired - private TxManagerSenderService managerSenderService; - - @Autowired - private TxManagerService managerService; - - - private Executor threadPool = Executors.newFixedThreadPool(20); - - @Override - public boolean saveCompensateMsg(final TransactionCompensateMsg transactionCompensateMsg) { - - TxGroup txGroup =managerService.getTxGroup(transactionCompensateMsg.getGroupId()); - if (txGroup == null) { - //仅发起方异常,其他模块正常 - txGroup = new TxGroup(); - txGroup.setNowTime(System.currentTimeMillis()); - txGroup.setGroupId(transactionCompensateMsg.getGroupId()); - txGroup.setIsCompensate(1); - }else { - managerService.deleteTxGroup(txGroup); - } - - transactionCompensateMsg.setTxGroup(txGroup); - - final String json = JSON.toJSONString(transactionCompensateMsg); - - logger.info("Compensate->" + json); - - final String compensateKey = compensateDao.saveCompensateMsg(transactionCompensateMsg); - - //调整自动补偿机制,若开启了自动补偿,需要通知业务返回success,方可执行自动补偿 - threadPool.execute(new Runnable() { - @Override - public void run() { - try { - String groupId = transactionCompensateMsg.getGroupId(); - JSONObject requestJson = new JSONObject(); - requestJson.put("action", "compensate"); - requestJson.put("groupId", groupId); - requestJson.put("json", json); - - String url = configReader.getCompensateNotifyUrl(); - logger.error("Compensate Callback Address->" + url); - String res = HttpUtils.postJson(url, requestJson.toJSONString()); - logger.error("Compensate Callback Result->" + res); - if (configReader.isCompensateAuto()) { - //自动补偿,是否自动执行补偿 - if (res.contains("success") || res.contains("SUCCESS")) { - //自动补偿 - autoCompensate(compensateKey, transactionCompensateMsg); - } - } - } catch (Exception e) { - logger.error("Compensate Callback Fails->" + e.getMessage()); - } - } - }); - - return StringUtils.isNotEmpty(compensateKey); - - - - } - - @Override - public void autoCompensate(final String compensateKey, TransactionCompensateMsg transactionCompensateMsg) { - final String json = JSON.toJSONString(transactionCompensateMsg); - logger.info("Auto Compensate->" + json); - //自动补偿业务执行... - final int tryTime = configReader.getCompensateTryTime(); - boolean autoExecuteRes = false; - try { - int executeCount = 0; - autoExecuteRes = _executeCompensate(json); - logger.info("Automatic Compensate Result->" + autoExecuteRes + ",json->" + json); - while (!autoExecuteRes) { - logger.info("Compensate Failure, Entering Compensate Queue->" + autoExecuteRes + ",json->" + json); - executeCount++; - if(executeCount==3){ - autoExecuteRes = false; - break; - } - try { - Thread.sleep(tryTime * 1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - autoExecuteRes = _executeCompensate(json); - } - - //执行成功删除数据 - if(autoExecuteRes) { - compensateDao.deleteCompensateByKey(compensateKey); - } - - }catch (Exception e){ - logger.error("Auto Compensate Fails,msg:"+e.getLocalizedMessage()); - //推送数据给第三方通知 - autoExecuteRes = false; - } - - //执行补偿以后通知给业务方 - String groupId = transactionCompensateMsg.getGroupId(); - JSONObject requestJson = new JSONObject(); - requestJson.put("action","notify"); - requestJson.put("groupId",groupId); - requestJson.put("resState",autoExecuteRes); - - String url = configReader.getCompensateNotifyUrl(); - logger.error("Compensate Result Callback Address->" + url); - String res = HttpUtils.postJson(url, requestJson.toJSONString()); - logger.error("Compensate Result Callback Result->" + res); - - } - - - - @Override - public List loadModelList() { - List keys = compensateDao.loadCompensateKeys(); - - Map models = new HashMap(); - - for(String key:keys){ - if(key.length()>36){ - String name = key.substring(11,key.length()-25); - int v = 1; - if(models.containsKey(name)){ - v = models.get(name)+1; - } - models.put(name,v); - } - } - List names = new ArrayList<>(); - - for(String key:models.keySet()){ - int v = models.get(key); - ModelName modelName = new ModelName(); - modelName.setName(key); - modelName.setCount(v); - names.add(modelName); - } - return names; - } - - @Override - public List loadCompensateTimes(String model) { - return compensateDao.loadCompensateTimes(model); - } - - @Override - public List loadCompensateByModelAndTime(String path) { - List logs = compensateDao.loadCompensateByModelAndTime(path); - - List models = new ArrayList<>(); - for (String json : logs) { - JSONObject jsonObject = JSON.parseObject(json); - TxModel model = new TxModel(); - long currentTime = jsonObject.getLong("currentTime"); - model.setTime(DateUtil.formatDate(new Date(currentTime), DateUtil.FULL_DATE_TIME_FORMAT)); - model.setClassName(jsonObject.getString("className")); - model.setMethod(jsonObject.getString("methodStr")); - model.setExecuteTime(jsonObject.getInteger("time")); - model.setBase64(Base64Utils.encode(json.getBytes())); - model.setState(jsonObject.getInteger("state")); - model.setOrder(currentTime); - - String groupId = jsonObject.getString("groupId"); - - String key = path + ":" + groupId; - model.setKey(key); - - models.add(model); - } - Collections.sort(models, new Comparator() { - @Override - public int compare(TxModel o1, TxModel o2) { - if (o2.getOrder() > o1.getOrder()) { - return 1; - } else { - return -1; - } - } - }); - return models; - } - - @Override - public boolean hasCompensate() { - return compensateDao.hasCompensate(); - } - - @Override - public boolean delCompensate(String path) { - compensateDao.deleteCompensateByPath(path); - return true; - } - - @Override - public void reloadCompensate(TxGroup txGroup) { - TxGroup compensateGroup = getCompensateByGroupId(txGroup.getGroupId()); - if (compensateGroup != null) { - - if(compensateGroup.getList() != null && !compensateGroup.getList().isEmpty()){ - //引用集合 iterator,方便匹配后剔除列表 - Iterator iterator = Lists.newArrayList(compensateGroup.getList()).iterator(); - for (TxInfo txInfo : txGroup.getList()) { - while (iterator.hasNext()) { - TxInfo cinfo = iterator.next(); - if (cinfo.getModel().equals(txInfo.getModel()) && cinfo.getMethodStr().equals(txInfo.getMethodStr())) { - //根据之前的数据补偿现在的事务 - int oldNotify = cinfo.getNotify(); - - if (oldNotify == 1) { - //本次回滚 - txInfo.setIsCommit(0); - } else { - //本次提交 - txInfo.setIsCommit(1); - } - //匹配后剔除列表 - iterator.remove(); - break; - } - } - } - }else{//当没有List数据只记录了补偿数据时,理解问仅发起方提交其他均回滚 - for (TxInfo txInfo : txGroup.getList()) { - //本次回滚 - txInfo.setIsCommit(0); - } - } - } - logger.info("Compensate Loaded->"+JSON.toJSONString(txGroup)); - } - - public TxGroup getCompensateByGroupId(String groupId) { - String json = compensateDao.getCompensateByGroupId(groupId); - if (json == null) { - return null; - } - JSONObject jsonObject = JSON.parseObject(json); - String txGroup = jsonObject.getString("txGroup"); - return JSON.parseObject(txGroup, TxGroup.class); - } - - - @Override - public boolean executeCompensate(String path) throws ServiceException { - - String json = compensateDao.getCompensate(path); - if (json == null) { - throw new ServiceException("no data existing"); - } - - boolean hasOk = _executeCompensate(json); - if (hasOk) { - // 删除本地补偿数据 - compensateDao.deleteCompensateByPath(path); - - return true; - } - return false; - } - - - private boolean _executeCompensate(String json) throws ServiceException { - JSONObject jsonObject = JSON.parseObject(json); - - String model = jsonObject.getString("model"); - - int startError = jsonObject.getInteger("startError"); - - ModelInfo modelInfo = ModelInfoManager.getInstance().getModelByModel(model); - if (modelInfo == null) { - throw new ServiceException("current model offline."); - } - - String data = jsonObject.getString("data"); - - String groupId = jsonObject.getString("groupId"); - - String res = managerSenderService.sendCompensateMsg(modelInfo.getChannelName(), groupId, data,startError); - - logger.debug("executeCompensate->"+json+",@@->"+res); - - return "1".equals(res); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/config/ConfigReader.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/config/ConfigReader.java deleted file mode 100644 index 666f2944..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/config/ConfigReader.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.codingapi.tm.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -/** - * create by lorne on 2017/11/11 - */ -@Component -public class ConfigReader { - - @Value("${tm.socket.port}") - private int socketPort; - - @Value("${tm.socket.maxconnection}") - private int socketMaxConnection; - - @Value("${tm.transaction.netty.hearttime}") - private int transactionNettyHeartTime; - - @Value("${tm.transaction.netty.delaytime}") - private int transactionNettyDelayTime; - - @Value("${tm.redis.savemaxtime}") - private int redisSaveMaxTime; - - - @Value("${tm.compensate.notifyUrl}") - private String compensateNotifyUrl; - - - @Value("${tm.compensate.auto}") - private boolean isCompensateAuto; - - - @Value("${tm.compensate.tryTime}") - private int compensateTryTime; - - @Value("${tm.compensate.maxWaitTime}") - private int compensateMaxWaitTime; - - /** - * 事务默认数据的位置,有最大时间 - */ - private final String key_prefix = "tx:manager:default:"; - /** - * 负载均衡模块存储信息 - */ - private final String key_prefix_loadbalance = "tx:manager:loadbalance:"; - - /** - * 补偿事务永久存储数据 - */ - private final String key_prefix_compensate = "tx:manager:compensate:"; - - - public String getKeyPrefixLoadbalance() { - return key_prefix_loadbalance; - } - - public String getCompensateNotifyUrl() { - return compensateNotifyUrl; - } - - public String getKeyPrefix() { - return key_prefix; - } - - public String getKeyPrefixCompensate() { - return key_prefix_compensate; - } - - public int getSocketPort(){ - return socketPort; - } - - public int getSocketMaxConnection() { - return socketMaxConnection; - } - - public int getTransactionNettyHeartTime() { - return transactionNettyHeartTime; - } - - public int getRedisSaveMaxTime() { - return redisSaveMaxTime; - } - - public int getTransactionNettyDelayTime() { - return transactionNettyDelayTime; - } - - public boolean isCompensateAuto() { - return isCompensateAuto; - } - - public int getCompensateTryTime() { - return compensateTryTime; - } - - public int getCompensateMaxWaitTime() { - return compensateMaxWaitTime; - } - - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/framework/utils/SocketManager.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/framework/utils/SocketManager.java deleted file mode 100644 index 7476944d..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/framework/utils/SocketManager.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.codingapi.tm.framework.utils; - -import com.codingapi.tm.Constants; -import io.netty.channel.Channel; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * Created by lorne on 2017/6/30. - */ -public class SocketManager { - - /** - * 最大连接数 - */ - private int maxConnection = Constants.maxConnection; - - /** - * 当前连接数 - */ - private int nowConnection; - - /** - * 允许连接请求 true允许 false拒绝 - */ - private boolean allowConnection = true; - - private List clients = null; - - private Map lines = null; - - private static SocketManager manager = null; - - public static SocketManager getInstance() { - if (manager == null){ - synchronized (SocketManager.class){ - if(manager==null){ - manager = new SocketManager(); - } - } - } - return manager; - } - - - public Channel getChannelByModelName(String name) { - for (Channel channel : clients) { - String modelName = channel.remoteAddress().toString(); - - if (modelName.equals(name)) { - return channel; - } - } - return null; - } - - private SocketManager() { - clients = new CopyOnWriteArrayList(); - lines = new ConcurrentHashMap(); - } - - public void addClient(Channel client) { - clients.add(client); - nowConnection = clients.size(); - - allowConnection = (maxConnection != nowConnection); - } - - public void removeClient(Channel client) { - clients.remove(client); - nowConnection = clients.size(); - - allowConnection = (maxConnection != nowConnection); - } - - - public int getMaxConnection() { - return maxConnection; - } - - public int getNowConnection() { - return nowConnection; - } - - public boolean isAllowConnection() { - return allowConnection; - } - - public void outLine(String modelName) { - lines.remove(modelName); - } - - public void onLine(String modelName, String uniqueKey) { - lines.put(modelName,uniqueKey); - } - - public Channel getChannelByUniqueKey(String uniqueKey) { - for (Channel channel : clients) { - String modelName = channel.remoteAddress().toString(); - String value = lines.get(modelName); - if (uniqueKey.equals(value)) { - return channel; - } - } - return null; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/framework/utils/SocketUtils.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/framework/utils/SocketUtils.java deleted file mode 100644 index 973b8bd6..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/framework/utils/SocketUtils.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.codingapi.tm.framework.utils; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.util.ReferenceCountUtil; - -/** - * Created by lorne on 2017/7/6. - */ -public class SocketUtils { - - public static String getJson(Object msg) { - String json; - try { - ByteBuf buf = (ByteBuf) msg; - byte[] bytes = new byte[buf.readableBytes()]; - buf.readBytes(bytes); - json = new String(bytes); - } finally { - ReferenceCountUtil.release(msg); - } - return json; - - } - - public static void sendMsg(ChannelHandlerContext ctx, String msg){ - ctx.writeAndFlush(Unpooled.buffer().writeBytes(msg.getBytes())); - } - - - public static void sendMsg(Channel ctx,String msg){ - ctx.writeAndFlush(Unpooled.buffer().writeBytes(msg.getBytes())); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/listener/ApplicationStartListener.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/listener/ApplicationStartListener.java deleted file mode 100644 index ecb20d04..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/listener/ApplicationStartListener.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.codingapi.tm.listener; - -import com.codingapi.tm.Constants; -import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.stereotype.Component; - -import java.net.InetAddress; -import java.net.UnknownHostException; - -/** - * create by lorne on 2017/8/7 - */ -@Component -public class ApplicationStartListener implements ApplicationListener { - - - @Override - public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) { - int serverPort = event.getEmbeddedServletContainer().getPort(); - String ip = getIp(); - Constants.address = ip+":"+serverPort; - } - - - - private String getIp(){ - String host = null; - try { - host = InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException e) { - e.printStackTrace(); - } - return host; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/listener/ServerListener.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/listener/ServerListener.java deleted file mode 100644 index 894e516d..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/listener/ServerListener.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.codingapi.tm.listener; - -import com.codingapi.tm.listener.service.InitService; -import org.springframework.stereotype.Component; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; - -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; - -/** - * Created by lorne on 2017/7/1. - */ - -@Component -public class ServerListener implements ServletContextListener { - - private WebApplicationContext springContext; - - - private InitService initService; - - @Override - public void contextInitialized(ServletContextEvent event) { - springContext = WebApplicationContextUtils - .getWebApplicationContext(event.getServletContext()); - initService = springContext.getBean(InitService.class); - initService.start(); - } - - - @Override - public void contextDestroyed(ServletContextEvent event) { - initService.close(); - } - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/listener/service/InitService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/listener/service/InitService.java deleted file mode 100644 index 58c53eaf..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/listener/service/InitService.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.codingapi.tm.listener.service; - -/** - * Created by lorne on 2017/7/4. - */ -public interface InitService { - - void start(); - - - void close(); -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/listener/service/impl/InitServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/listener/service/impl/InitServiceImpl.java deleted file mode 100644 index e9f40184..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/listener/service/impl/InitServiceImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.codingapi.tm.listener.service.impl; - -import com.codingapi.tm.Constants; -import com.codingapi.tm.config.ConfigReader; -import com.codingapi.tm.listener.service.InitService; -import com.codingapi.tm.netty.service.NettyServerService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - - -/** - * Created by lorne on 2017/7/4. - */ -@Service -public class InitServiceImpl implements InitService { - - @Autowired - private NettyServerService nettyServerService; - - @Autowired - private ConfigReader configReader; - - - @Override - public void start() { - /**加载本地服务信息**/ - - Constants.socketPort = configReader.getSocketPort(); - Constants.maxConnection = configReader.getSocketMaxConnection(); - nettyServerService.start(); - } - - @Override - public void close() { - nettyServerService.close(); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/ModelInfoManager.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/ModelInfoManager.java deleted file mode 100644 index 0b36186c..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/ModelInfoManager.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.codingapi.tm.manager; - -import com.codingapi.tm.model.ModelInfo; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * create by lorne on 2017/11/13 - */ -public class ModelInfoManager { - - - private List modelInfos = new CopyOnWriteArrayList(); - - private static ModelInfoManager manager = null; - - - public static ModelInfoManager getInstance() { - if (manager == null) { - synchronized (ModelInfoManager.class) { - if (manager == null) { - manager = new ModelInfoManager(); - } - } - } - return manager; - } - - public void removeModelInfo(String channelName) { - for (ModelInfo modelInfo : modelInfos) { - if (channelName.equalsIgnoreCase(modelInfo.getChannelName())) { - modelInfos.remove(modelInfo); - } - } - } - - - public void addModelInfo(ModelInfo minfo) { - for (ModelInfo modelInfo : modelInfos) { - if (minfo.getChannelName().equalsIgnoreCase(modelInfo.getChannelName())) { - return; - } - - if (minfo.getIpAddress().equalsIgnoreCase(modelInfo.getIpAddress())) { - return; - } - } - modelInfos.add(minfo); - } - - public List getOnlines() { - return modelInfos; - } - - public ModelInfo getModelByChannelName(String channelName) { - for (ModelInfo modelInfo : modelInfos) { - if (channelName.equalsIgnoreCase(modelInfo.getChannelName())) { - return modelInfo; - } - } - return null; - } - - public ModelInfo getModelByModel(String model) { - for (ModelInfo modelInfo : modelInfos) { - if (model.equalsIgnoreCase(modelInfo.getModel())) { - return modelInfo; - } - } - return null; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/LoadBalanceService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/LoadBalanceService.java deleted file mode 100644 index 3b909238..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/LoadBalanceService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.codingapi.tm.manager.service; - -import com.codingapi.tm.model.LoadBalanceInfo; - -/** - * create by lorne on 2017/12/5 - */ -public interface LoadBalanceService { - - boolean put(LoadBalanceInfo loadBalanceInfo); - - LoadBalanceInfo get(String groupId,String key); - - boolean remove(String groupId); - - String getLoadBalanceGroupName(String groupId); - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/MicroService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/MicroService.java deleted file mode 100644 index 9f5ae322..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/MicroService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.codingapi.tm.manager.service; - -import com.codingapi.tm.model.TxServer; -import com.codingapi.tm.model.TxState; - -/** - * create by lorne on 2017/11/11 - */ -public interface MicroService { - - String tmKey = "tx-manager"; - - TxServer getServer(); - - TxState getState(); -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/TxManagerSenderService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/TxManagerSenderService.java deleted file mode 100644 index c7db2654..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/TxManagerSenderService.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.codingapi.tm.manager.service; - -import com.codingapi.tm.netty.model.TxGroup; - -/** - * Created by lorne on 2017/6/9. - */ -public interface TxManagerSenderService { - - int confirm(TxGroup group); - - String sendMsg(String model, String msg, int delay); - - String sendCompensateMsg(String model, String groupId, String data,int startState); -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/TxManagerService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/TxManagerService.java deleted file mode 100644 index 74cfd08a..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/TxManagerService.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.codingapi.tm.manager.service; - -import com.codingapi.tm.netty.model.TxGroup; - -/** - * Created by lorne on 2017/6/7. - */ - -public interface TxManagerService { - - - /** - * 创建事物组 - * - * @param groupId 补偿事务组id - */ - TxGroup createTransactionGroup(String groupId); - - - /** - * 添加事务组子对象 - * - * @return - */ - - TxGroup addTransactionGroup(String groupId, String taskId,int isGroup, String channelAddress, String methodStr); - - - /** - * 关闭事务组 - * @param groupId 事务组id - * @param state 事务状态 - * @return 0 事务存在补偿 1 事务正常 -1 事务强制回滚 - */ - int closeTransactionGroup(String groupId,int state); - - - void dealTxGroup(TxGroup txGroup, boolean hasOk ); - - - /** - * 删除事务组 - * @param txGroup 事务组 - */ - void deleteTxGroup(TxGroup txGroup); - - - /** - * 获取事务组信息 - * @param groupId 事务组id - * @return 事务组 - */ - TxGroup getTxGroup(String groupId); - - - /** - * 获取事务组的key - * @param groupId 事务组id - * @return key - */ - String getTxGroupKey(String groupId); - - - /** - * 检查事务组数据 - * @param groupId 事务组id - * @param taskId 任务id - * @return 本次请求的是否提交 1提交 0回滚 - */ - int cleanNotifyTransaction(String groupId, String taskId); - - - /** - * 设置强制回滚事务 - * @param groupId 事务组id - * @return true 成功 false 失败 - */ - boolean rollbackTransactionGroup(String groupId); -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/impl/LoadBalanceServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/impl/LoadBalanceServiceImpl.java deleted file mode 100644 index 3fc8f8a5..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/impl/LoadBalanceServiceImpl.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.codingapi.tm.manager.service.impl; - -import com.codingapi.tm.config.ConfigReader; -import com.codingapi.tm.manager.service.LoadBalanceService; -import com.codingapi.tm.model.LoadBalanceInfo; -import com.codingapi.tm.redis.service.RedisServerService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * create by lorne on 2017/12/5 - */ -@Service -public class LoadBalanceServiceImpl implements LoadBalanceService { - - @Autowired - private RedisServerService redisServerService; - - @Autowired - private ConfigReader configReader; - - @Override - public boolean put(LoadBalanceInfo loadBalanceInfo) { - String groupName = getLoadBalanceGroupName(loadBalanceInfo.getGroupId()); - redisServerService.saveLoadBalance(groupName,loadBalanceInfo.getKey(),loadBalanceInfo.getData()); - return true; - } - - @Override - public LoadBalanceInfo get(String groupId, String key) { - String groupName = getLoadBalanceGroupName(groupId); - String bytes = redisServerService.getLoadBalance(groupName,key); - if(bytes==null) { - return null; - } - - LoadBalanceInfo loadBalanceInfo = new LoadBalanceInfo(); - loadBalanceInfo.setGroupId(groupId); - loadBalanceInfo.setKey(key); - loadBalanceInfo.setData(bytes); - return loadBalanceInfo; - } - - @Override - public boolean remove(String groupId) { - redisServerService.deleteKey(getLoadBalanceGroupName(groupId)); - return true; - } - - @Override - public String getLoadBalanceGroupName(String groupId) { - return configReader.getKeyPrefixLoadbalance()+groupId; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/impl/MicroServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/impl/MicroServiceImpl.java deleted file mode 100644 index 5a74ac91..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/impl/MicroServiceImpl.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.codingapi.tm.manager.service.impl; - -import com.codingapi.tm.Constants; -import com.codingapi.tm.config.ConfigReader; -import com.codingapi.tm.framework.utils.SocketManager; -import com.codingapi.tm.manager.service.MicroService; -import com.codingapi.tm.model.TxServer; -import com.codingapi.tm.model.TxState; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * create by lorne on 2017/11/11 - */ -@Service -public class MicroServiceImpl implements MicroService { - - - @Autowired - private RestTemplate restTemplate; - - @Autowired - private ConfigReader configReader; - - - @Autowired - private DiscoveryClient discoveryClient; - - - - private boolean isIp(String ipAddress) { - String ip = "([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}"; - Pattern pattern = Pattern.compile(ip); - Matcher matcher = pattern.matcher(ipAddress); - return matcher.matches(); - } - - - - @Override - public TxState getState() { - TxState state = new TxState(); - String ipAddress = discoveryClient.getLocalServiceInstance().getHost(); - if(!isIp(ipAddress)){ - ipAddress = "127.0.0.1"; - } - state.setIp(ipAddress); - state.setPort(Constants.socketPort); - state.setMaxConnection(SocketManager.getInstance().getMaxConnection()); - state.setNowConnection(SocketManager.getInstance().getNowConnection()); - state.setRedisSaveMaxTime(configReader.getRedisSaveMaxTime()); - state.setTransactionNettyDelayTime(configReader.getTransactionNettyDelayTime()); - state.setTransactionNettyHeartTime(configReader.getTransactionNettyHeartTime()); - state.setNotifyUrl(configReader.getCompensateNotifyUrl()); - state.setCompensate(configReader.isCompensateAuto()); - state.setCompensateTryTime(configReader.getCompensateTryTime()); - state.setCompensateMaxWaitTime(configReader.getCompensateMaxWaitTime()); - state.setSlbList(getServices()); - return state; - } - - private List getServices(){ - List urls = new ArrayList<>(); - List serviceInstances = discoveryClient.getInstances(tmKey); - for (ServiceInstance instanceInfo : serviceInstances) { - urls.add(instanceInfo.getUri().toASCIIString()); - } - return urls; - } - - @Override - public TxServer getServer() { - List urls= getServices(); - List states = new ArrayList<>(); - for(String url:urls){ - try { - TxState state = restTemplate.getForObject(url + "/tx/manager/state", TxState.class); - states.add(state); - } catch (Exception e) { - } - - } - if(states.size()<=1) { - TxState state = getState(); - if (state.getMaxConnection() > state.getNowConnection()) { - return TxServer.format(state); - } else { - return null; - } - }else{ - //找默认数据 - TxState state = getDefault(states,0); - if (state == null) { - //没有满足的默认数据 - return null; - } - return TxServer.format(state); - } - } - - private TxState getDefault(List states, int index) { - TxState state = states.get(index); - if (state.getMaxConnection() == state.getNowConnection()) { - index++; - if (states.size() - 1 >= index) { - return getDefault(states, index); - } else { - return null; - } - } else { - return state; - } - } - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/impl/TxManagerSenderServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/impl/TxManagerSenderServiceImpl.java deleted file mode 100644 index e5ee4e27..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/impl/TxManagerSenderServiceImpl.java +++ /dev/null @@ -1,323 +0,0 @@ -package com.codingapi.tm.manager.service.impl; - - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; - -import com.codingapi.tm.Constants; -import com.codingapi.tm.compensate.service.CompensateService; -import com.codingapi.tm.config.ConfigReader; -import com.codingapi.tm.framework.utils.SocketUtils; -import com.codingapi.tm.manager.service.TxManagerSenderService; -import com.codingapi.tm.manager.service.TxManagerService; -import com.codingapi.tm.framework.utils.SocketManager; -import com.codingapi.tm.netty.model.TxGroup; -import com.codingapi.tm.model.ChannelSender; -import com.codingapi.tm.redis.service.RedisServerService; -import com.lorne.core.framework.utils.KidUtils; -import com.lorne.core.framework.utils.task.ConditionUtils; -import com.lorne.core.framework.utils.task.IBack; -import com.lorne.core.framework.utils.task.Task; -import com.lorne.core.framework.utils.thread.CountDownLatchHelper; -import com.lorne.core.framework.utils.thread.IExecute; -import com.codingapi.tm.netty.model.TxInfo; - - -import io.netty.channel.Channel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.concurrent.*; - - -/** - * Created by lorne on 2017/6/9. - */ -@Service -public class TxManagerSenderServiceImpl implements TxManagerSenderService { - - - private Logger logger = LoggerFactory.getLogger(TxManagerSenderServiceImpl.class); - - private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(100); - - private Executor threadPool = Executors.newFixedThreadPool(100); - - @Autowired - private TxManagerService txManagerService; - - @Autowired - private RedisServerService redisServerService; - - @Autowired - private ConfigReader configReader; - - @Autowired - private CompensateService compensateService; - - @Override - public int confirm(TxGroup txGroup) { - //绑定管道对象,检查网络 - setChannel(txGroup.getList()); - - //事务不满足直接回滚事务 - if (txGroup.getState()==0) { - transaction(txGroup, 0); - return 0; - } - - if(txGroup.getRollback()==1){ - transaction(txGroup, 0); - return -1; - } - - boolean hasOk = transaction(txGroup, 1); - txManagerService.dealTxGroup(txGroup,hasOk); - return hasOk?1:0; - } - - - /** - * 匹配管道 - * - * @param list - */ - private void setChannel(List list) { - for (TxInfo info : list) { - if(Constants.address.equals(info.getAddress())){ - Channel channel = SocketManager.getInstance().getChannelByModelName(info.getChannelAddress()); - if (channel != null &&channel.isActive()) { - ChannelSender sender = new ChannelSender(); - sender.setChannel(channel); - - info.setChannel(sender); - } - }else{ - ChannelSender sender = new ChannelSender(); - sender.setAddress(info.getAddress()); - sender.setModelName(info.getChannelAddress()); - - info.setChannel(sender); - } - } - } - - - - /** - * 事务提交或回归 - * - * @param checkSate - */ - private boolean transaction(final TxGroup txGroup, final int checkSate) { - - - if (checkSate == 1) { - - //补偿请求,加载历史数据 - if (txGroup.getIsCompensate() == 1) { - compensateService.reloadCompensate(txGroup); - } - - CountDownLatchHelper countDownLatchHelper = new CountDownLatchHelper(); - for (final TxInfo txInfo : txGroup.getList()) { - if (txInfo.getIsGroup() == 0) { - countDownLatchHelper.addExecute(new IExecute() { - @Override - public Boolean execute() { - if(txInfo.getChannel()==null){ - return false; - } - - final JSONObject jsonObject = new JSONObject(); - jsonObject.put("a", "t"); - - - if (txGroup.getIsCompensate() == 1) { //补偿请求 - jsonObject.put("c", txInfo.getIsCommit()); - } else { //正常业务 - jsonObject.put("c", checkSate); - } - - jsonObject.put("t", txInfo.getKid()); - final String key = KidUtils.generateShortUuid(); - jsonObject.put("k", key); - - Task task = ConditionUtils.getInstance().createTask(key); - - ScheduledFuture future = schedule(key, configReader.getTransactionNettyDelayTime()); - - threadAwaitSend(task, txInfo, jsonObject.toJSONString()); - - task.awaitTask(); - - if (!future.isDone()) { - future.cancel(false); - } - - try { - String data = (String) task.getBack().doing(); - // 1 成功 0 失败 -1 task为空 -2 超过 - boolean res = "1".equals(data); - - if (res) { - txInfo.setNotify(1); - } - - return res; - } catch (Throwable throwable) { - throwable.printStackTrace(); - return false; - } finally { - task.remove(); - } - } - }); - } - } - - List hasOks = countDownLatchHelper.execute().getData(); - - String key = configReader.getKeyPrefix() + txGroup.getGroupId(); - redisServerService.saveTransaction(key, txGroup.toJsonString()); - - boolean hasOk = true; - for (boolean bl : hasOks) { - if (!bl) { - hasOk = false; - break; - } - } - logger.info("--->" + hasOk + ",group:" + txGroup.getGroupId() + ",state:" + checkSate + ",list:" + txGroup.toJsonString()); - return hasOk; - }else{ - //回滚操作只发送通过不需要等待确认 - for (TxInfo txInfo : txGroup.getList()) { - if(txInfo.getChannel()!=null) { - if (txInfo.getIsGroup() == 0) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("a", "t"); - jsonObject.put("c", checkSate); - jsonObject.put("t", txInfo.getKid()); - String key = KidUtils.generateShortUuid(); - jsonObject.put("k", key); - txInfo.getChannel().send(jsonObject.toJSONString()); - } - } - } - txManagerService.deleteTxGroup(txGroup); - return true; - } - - } - - @Override - public String sendCompensateMsg(String model, String groupId, String data,int startState) { - JSONObject newCmd = new JSONObject(); - newCmd.put("a", "c"); - newCmd.put("d", data); - newCmd.put("ss", startState); - newCmd.put("g", groupId); - newCmd.put("k", KidUtils.generateShortUuid()); - return sendMsg(model, newCmd.toJSONString(), configReader.getRedisSaveMaxTime()); - } - - @Override - public String sendMsg(final String model,final String msg, int delay) { - JSONObject jsonObject = JSON.parseObject(msg); - String key = jsonObject.getString("k"); - - //创建Task - final Task task = ConditionUtils.getInstance().createTask(key); - - threadPool.execute(new Runnable() { - @Override - public void run() { - while (!task.isAwait() && !Thread.currentThread().interrupted()) { - try { - Thread.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - Channel channel = SocketManager.getInstance().getChannelByModelName(model); - if (channel != null && channel.isActive()) { - SocketUtils.sendMsg(channel, msg); - } - } - }); - - ScheduledFuture future = schedule(key, delay); - - task.awaitTask(); - - if (!future.isDone()) { - future.cancel(false); - } - - try { - return (String)task.getBack().doing(); - } catch (Throwable throwable) { - return "-1"; - }finally { - task.remove(); - } - } - - - - private void threadAwaitSend(final Task task, final TxInfo txInfo, final String msg){ - threadPool.execute(new Runnable() { - @Override - public void run() { - while (!task.isAwait() && !Thread.currentThread().interrupted()) { - try { - Thread.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - if(txInfo!=null&&txInfo.getChannel()!=null) { - txInfo.getChannel().send(msg,task); - }else{ - task.setBack(new IBack() { - @Override - public Object doing(Object... objs) throws Throwable { - return "-2"; - } - }); - task.signalTask(); - } - } - }); - - } - - - private ScheduledFuture schedule(final String key, int delayTime) { - ScheduledFuture future = executorService.schedule(new Runnable() { - @Override - public void run() { - Task task = ConditionUtils.getInstance().getTask(key); - if(task!=null&&!task.isNotify()) { - task.setBack(new IBack() { - @Override - public Object doing(Object... objs) throws Throwable { - return "-2"; - } - }); - task.signalTask(); - } - } - }, delayTime, TimeUnit.SECONDS); - - return future; - } - - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/impl/TxManagerServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/impl/TxManagerServiceImpl.java deleted file mode 100644 index da6e1360..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/manager/service/impl/TxManagerServiceImpl.java +++ /dev/null @@ -1,213 +0,0 @@ -package com.codingapi.tm.manager.service.impl; - - -import com.codingapi.tm.Constants; -import com.codingapi.tm.compensate.service.CompensateService; -import com.codingapi.tm.manager.ModelInfoManager; -import com.codingapi.tm.manager.service.LoadBalanceService; -import com.codingapi.tm.manager.service.TxManagerSenderService; -import com.codingapi.tm.manager.service.TxManagerService; -import com.codingapi.tm.config.ConfigReader; -import com.codingapi.tm.model.ModelInfo; -import com.codingapi.tm.netty.model.TxGroup; -import com.codingapi.tm.netty.model.TxInfo; -import com.codingapi.tm.redis.service.RedisServerService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * Created by lorne on 2017/6/7. - */ -@Service -public class TxManagerServiceImpl implements TxManagerService { - - - - @Autowired - private ConfigReader configReader; - - @Autowired - private RedisServerService redisServerService; - - - @Autowired - private TxManagerSenderService transactionConfirmService; - - - @Autowired - private LoadBalanceService loadBalanceService; - - @Autowired - private CompensateService compensateService; - - - private Logger logger = LoggerFactory.getLogger(TxManagerServiceImpl.class); - - - @Override - public TxGroup createTransactionGroup(String groupId) { - TxGroup txGroup = new TxGroup(); - if (compensateService.getCompensateByGroupId(groupId)!=null) { - txGroup.setIsCompensate(1); - } - - txGroup.setStartTime(System.currentTimeMillis()); - txGroup.setGroupId(groupId); - - String key = configReader.getKeyPrefix() + groupId; - redisServerService.saveTransaction(key, txGroup.toJsonString()); - - return txGroup; - } - - - @Override - public TxGroup addTransactionGroup(String groupId, String taskId, int isGroup, String channelAddress, String methodStr) { - String key = getTxGroupKey(groupId); - TxGroup txGroup = getTxGroup(groupId); - if (txGroup==null) { - return null; - } - TxInfo txInfo = new TxInfo(); - txInfo.setChannelAddress(channelAddress); - txInfo.setKid(taskId); - txInfo.setAddress(Constants.address); - txInfo.setIsGroup(isGroup); - txInfo.setMethodStr(methodStr); - - - ModelInfo modelInfo = ModelInfoManager.getInstance().getModelByChannelName(channelAddress); - if(modelInfo!=null) { - txInfo.setUniqueKey(modelInfo.getUniqueKey()); - txInfo.setModelIpAddress(modelInfo.getIpAddress()); - txInfo.setModel(modelInfo.getModel()); - } - - txGroup.addTransactionInfo(txInfo); - - redisServerService.saveTransaction(key, txGroup.toJsonString()); - - return txGroup; - } - - @Override - public boolean rollbackTransactionGroup(String groupId) { - String key = getTxGroupKey(groupId); - TxGroup txGroup = getTxGroup(groupId); - if (txGroup==null) { - return false; - } - txGroup.setRollback(1); - redisServerService.saveTransaction(key, txGroup.toJsonString()); - return true; - } - - @Override - public int cleanNotifyTransaction(String groupId, String taskId) { - int res = 0; - logger.info("start-cleanNotifyTransaction->groupId:"+groupId+",taskId:"+taskId); - String key = getTxGroupKey(groupId); - TxGroup txGroup = getTxGroup(groupId); - if (txGroup==null) { - logger.info("cleanNotifyTransaction - > txGroup is null "); - return res; - } - - if(txGroup.getHasOver()==0){ - - //整个事务回滚. - txGroup.setRollback(1); - redisServerService.saveTransaction(key, txGroup.toJsonString()); - - logger.info("cleanNotifyTransaction - > groupId "+groupId+" not over,all transaction must rollback !"); - return 0; - } - - if(txGroup.getRollback()==1){ - logger.info("cleanNotifyTransaction - > groupId "+groupId+" only rollback !"); - return 0; - } - - //更新数据 - boolean hasSet = false; - for (TxInfo info : txGroup.getList()) { - if (info.getKid().equals(taskId)) { - if(info.getNotify()==0&&info.getIsGroup()==0) { - info.setNotify(1); - hasSet = true; - res = 1; - - break; - } - } - } - - //判断是否都结束 - boolean isOver = true; - for (TxInfo info : txGroup.getList()) { - if (info.getIsGroup() == 0 && info.getNotify() == 0) { - isOver = false; - break; - } - } - - if (isOver) { - deleteTxGroup(txGroup); - } - - //有更新的数据,需要修改记录 - if(!isOver&&hasSet) { - redisServerService.saveTransaction(key, txGroup.toJsonString()); - } - - logger.info("end-cleanNotifyTransaction->groupId:"+groupId+",taskId:"+taskId+",res(1:commit,0:rollback):"+res); - return res; - } - - - @Override - public int closeTransactionGroup(String groupId,int state) { - String key = getTxGroupKey(groupId); - TxGroup txGroup = getTxGroup(groupId); - if(txGroup==null){ - return 0; - } - txGroup.setState(state); - txGroup.setHasOver(1); - redisServerService.saveTransaction(key,txGroup.toJsonString()); - return transactionConfirmService.confirm(txGroup); - } - - - @Override - public void dealTxGroup(TxGroup txGroup, boolean hasOk) { - if(hasOk) { - deleteTxGroup(txGroup); - } - } - - - @Override - public void deleteTxGroup(TxGroup txGroup) { - String groupId = txGroup.getGroupId(); - - String key = getTxGroupKey(groupId); - redisServerService.deleteKey(key); - - loadBalanceService.remove(groupId); - } - - - @Override - public TxGroup getTxGroup(String groupId) { - String key = getTxGroupKey(groupId); - return redisServerService.getTxGroupByKey(key); - } - - @Override - public String getTxGroupKey(String groupId) { - return configReader.getKeyPrefix() + groupId; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/ChannelSender.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/ChannelSender.java deleted file mode 100644 index 2777b382..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/ChannelSender.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.codingapi.tm.model; - -import com.codingapi.tm.framework.utils.SocketUtils; -import com.lorne.core.framework.utils.http.HttpUtils; -import com.lorne.core.framework.utils.task.IBack; -import com.lorne.core.framework.utils.task.Task; -import io.netty.channel.Channel; -import org.apache.commons.lang.StringUtils; - -/** - * create by lorne on 2017/8/7 - */ -public class ChannelSender { - - - private Channel channel; - - private String address; - - private String modelName; - - public void setModelName(String modelName) { - this.modelName = modelName; - } - - public void setChannel(Channel channel) { - this.channel = channel; - } - - public void setAddress(String address) { - this.address = address; - } - - - public void send(String msg){ - if(channel!=null){ - SocketUtils.sendMsg(channel,msg); - } - - } - - public void send(String msg,Task task){ - if(channel!=null){ - SocketUtils.sendMsg(channel,msg); - }else{ - String url = String.format("http://%s/tx/manager/sendMsg",address); - final String res = HttpUtils.post(url,"msg="+msg+"&model="+modelName); - if(StringUtils.isNotEmpty(res)){ - if(task!=null) { - task.setBack(new IBack() { - @Override - public Object doing(Object... objs) throws Throwable { - return res; - } - }); - task.signalTask(); - } - }else{ - if(task!=null) { - task.setBack(new IBack() { - @Override - public Object doing(Object... objs) throws Throwable { - return "-2"; - } - }); - task.signalTask(); - } - } - } - - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/LoadBalanceInfo.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/LoadBalanceInfo.java deleted file mode 100644 index 5dae5ead..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/LoadBalanceInfo.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.codingapi.tm.model; - -/** - * 负载均衡模块信息 - * create by lorne on 2017/12/5 - */ -public class LoadBalanceInfo { - - private String groupId; - - private String key; - - private String data; - - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public String getData() { - return data; - } - - public void setData(String data) { - this.data = data; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/ModelInfo.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/ModelInfo.java deleted file mode 100644 index 2ff743d6..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/ModelInfo.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.codingapi.tm.model; - -/** - * 模块信息 - * create by lorne on 2017/11/13 - */ -public class ModelInfo { - - private String model; - - private String ipAddress; - - private String channelName; - - private String uniqueKey; - - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - public String getIpAddress() { - return ipAddress; - } - - public void setIpAddress(String ipAddress) { - this.ipAddress = ipAddress; - } - - public String getChannelName() { - return channelName; - } - - public void setChannelName(String channelName) { - this.channelName = channelName; - } - - public String getUniqueKey() { - return uniqueKey; - } - - public void setUniqueKey(String uniqueKey) { - this.uniqueKey = uniqueKey; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/ModelName.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/ModelName.java deleted file mode 100644 index f6c008d2..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/ModelName.java +++ /dev/null @@ -1,28 +0,0 @@ - - -package com.codingapi.tm.model; - -/** - * create by lorne on 2017/11/22 - */ -public class ModelName { - - private String name; - private int count; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/TxServer.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/TxServer.java deleted file mode 100644 index 83c4d8af..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/TxServer.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.codingapi.tm.model; - -/** - * Created by lorne on 2017/7/1. - */ -public class TxServer { - - private String ip; - private int port; - private int heart; - private int delay; - private int compensateMaxWaitTime; - - public static TxServer format(TxState state) { - TxServer txServer = new TxServer(); - txServer.setIp(state.getIp()); - txServer.setPort(state.getPort()); - txServer.setHeart(state.getTransactionNettyHeartTime()); - txServer.setDelay(state.getTransactionNettyDelayTime()); - txServer.setCompensateMaxWaitTime(state.getCompensateMaxWaitTime()); - return txServer; - } - - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - public int getHeart() { - return heart; - } - - public void setHeart(int heart) { - this.heart = heart; - } - - public int getDelay() { - return delay; - } - - public void setDelay(int delay) { - this.delay = delay; - } - - - public int getCompensateMaxWaitTime() { - return compensateMaxWaitTime; - } - - - public void setCompensateMaxWaitTime(int compensateMaxWaitTime) { - this.compensateMaxWaitTime = compensateMaxWaitTime; - } - - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/TxState.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/TxState.java deleted file mode 100644 index 07c40084..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/model/TxState.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.codingapi.tm.model; - -import java.util.List; - -/** - * Created by lorne on 2017/7/1. - */ -public class TxState { - - /** - * socket ip - */ - private String ip; - /** - * socket port - */ - private int port; - - /** - * max connection - */ - private int maxConnection; - - /** - * now connection - */ - private int nowConnection; - - - /** - * transaction_netty_heart_time - */ - private int transactionNettyHeartTime; - - /** - * transaction_netty_delay_time - */ - private int transactionNettyDelayTime; - - - /** - * redis_save_max_time - */ - private int redisSaveMaxTime; - - - /** - * 回调地址 - */ - private String notifyUrl; - - /** - * 自动补偿 - */ - private boolean isCompensate; - - /** - * 补偿尝试时间 - */ - private int compensateTryTime; - - /** - * slb list - */ - private List slbList; - - /** - * 自动补偿间隔时间 - */ - private int compensateMaxWaitTime; - - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - public int getMaxConnection() { - return maxConnection; - } - - public void setMaxConnection(int maxConnection) { - this.maxConnection = maxConnection; - } - - public int getNowConnection() { - return nowConnection; - } - - public void setNowConnection(int nowConnection) { - this.nowConnection = nowConnection; - } - - public boolean isCompensate() { - return isCompensate; - } - - public void setCompensate(boolean compensate) { - isCompensate = compensate; - } - - public int getCompensateTryTime() { - return compensateTryTime; - } - - public void setCompensateTryTime(int compensateTryTime) { - this.compensateTryTime = compensateTryTime; - } - - public int getRedisSaveMaxTime() { - return redisSaveMaxTime; - } - - public void setRedisSaveMaxTime(int redisSaveMaxTime) { - this.redisSaveMaxTime = redisSaveMaxTime; - } - - public List getSlbList() { - return slbList; - } - - public void setSlbList(List slbList) { - this.slbList = slbList; - } - - public int getTransactionNettyHeartTime() { - return transactionNettyHeartTime; - } - - public void setTransactionNettyHeartTime(int transactionNettyHeartTime) { - this.transactionNettyHeartTime = transactionNettyHeartTime; - } - - public int getTransactionNettyDelayTime() { - return transactionNettyDelayTime; - } - - public void setTransactionNettyDelayTime(int transactionNettyDelayTime) { - this.transactionNettyDelayTime = transactionNettyDelayTime; - } - - public String getNotifyUrl() { - return notifyUrl; - } - - public void setNotifyUrl(String notifyUrl) { - this.notifyUrl = notifyUrl; - } - - public int getCompensateMaxWaitTime() { - return compensateMaxWaitTime; - } - - public void setCompensateMaxWaitTime(int compensateMaxWaitTime) { - this.compensateMaxWaitTime = compensateMaxWaitTime; - } - - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/handler/TxCoreServerHandler.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/handler/TxCoreServerHandler.java deleted file mode 100644 index ed60b073..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/handler/TxCoreServerHandler.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.codingapi.tm.netty.handler; - -/** - * Created by lorne on 2017/6/29. - */ - -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tm.framework.utils.SocketManager; -import com.codingapi.tm.framework.utils.SocketUtils; -import com.codingapi.tm.manager.ModelInfoManager; -import com.codingapi.tm.netty.service.IActionService; -import com.codingapi.tm.netty.service.NettyService; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.handler.timeout.IdleState; -import io.netty.handler.timeout.IdleStateEvent; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.Executor; - -/** - * Handles a server-side channel. - */ - -@ChannelHandler.Sharable -public class TxCoreServerHandler extends ChannelInboundHandlerAdapter { // (1) - - private NettyService nettyService; - - - private Logger logger = LoggerFactory.getLogger(TxCoreServerHandler.class); - - - private Executor threadPool; - - - public TxCoreServerHandler(Executor threadPool,NettyService nettyService) { - this.threadPool = threadPool; - this.nettyService = nettyService; - } - - @Override - public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { - final String json = SocketUtils.getJson(msg); - logger.debug("request->"+json); - threadPool.execute(new Runnable() { - @Override - public void run() { - service(json,ctx); - } - }); - } - - private void service(String json,ChannelHandlerContext ctx){ - if (StringUtils.isNotEmpty(json)) { - JSONObject jsonObject = JSONObject.parseObject(json); - String action = jsonObject.getString("a"); - String key = jsonObject.getString("k"); - JSONObject params = JSONObject.parseObject(jsonObject.getString("p")); - String channelAddress = ctx.channel().remoteAddress().toString(); - - IActionService actionService = nettyService.getActionService(action); - - String res = actionService.execute(channelAddress,key,params); - - JSONObject resObj = new JSONObject(); - resObj.put("k", key); - resObj.put("d", res); - - SocketUtils.sendMsg(ctx,resObj.toString()); - - } - } - - @Override - public void channelRegistered(ChannelHandlerContext ctx) throws Exception { - - //是否到达最大上线连接数 - if (SocketManager.getInstance().isAllowConnection()) { - SocketManager.getInstance().addClient(ctx.channel()); - } else { - ctx.close(); - } - super.channelRegistered(ctx); - } - - @Override - public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { - - SocketManager.getInstance().removeClient(ctx.channel()); - String modelName = ctx.channel().remoteAddress().toString(); - SocketManager.getInstance().outLine(modelName); - - ModelInfoManager.getInstance().removeModelInfo(modelName); - super.channelUnregistered(ctx); - } - - @Override - public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { - ctx.flush(); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - cause.printStackTrace(); - //ctx.close(); - } - - @Override - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { - //心跳配置 - if (IdleStateEvent.class.isAssignableFrom(evt.getClass())) { - IdleStateEvent event = (IdleStateEvent) evt; - if (event.state() == IdleState.READER_IDLE) { - ctx.close(); - } - } - } - -} \ No newline at end of file diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/model/TxGroup.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/model/TxGroup.java deleted file mode 100644 index 6432c619..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/model/TxGroup.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.codingapi.tm.netty.model; - - -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - -import java.util.ArrayList; -import java.util.List; - -/** - * Created by lorne on 2017/6/7. - */ -public class TxGroup { - - private String groupId; - - private long startTime; - - private long nowTime; - - private int state; - - private int hasOver; - - /** - * 补偿请求 - */ - private int isCompensate; - - - /** - * 是否强制回滚(1:开启,0:关闭) - */ - private int rollback = 0 ; - - private List list; - - public TxGroup() { - list = new ArrayList(); - } - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public List getList() { - return list; - } - - public void setList(List list) { - this.list = list; - } - - public long getStartTime() { - return startTime; - } - - public void setStartTime(long startTime) { - this.startTime = startTime; - } - - public int getIsCompensate() { - return isCompensate; - } - - public void setIsCompensate(int isCompensate) { - this.isCompensate = isCompensate; - } - - public int getState() { - return state; - } - - public void setState(int state) { - this.state = state; - } - - public void addTransactionInfo(TxInfo info) { - list.add(info); - } - - public long getNowTime() { - return nowTime; - } - - public void setNowTime(long nowTime) { - this.nowTime = nowTime; - } - - - public int getHasOver() { - return hasOver; - } - - public void setHasOver(int hasOver) { - this.hasOver = hasOver; - } - - public int getRollback() { - return rollback; - } - - public void setRollback(int rollback) { - this.rollback = rollback; - } - - public static TxGroup parser(String json) { - try { - JSONObject jsonObject = JSONObject.parseObject(json); - TxGroup txGroup = new TxGroup(); - txGroup.setGroupId(jsonObject.getString("g")); - txGroup.setStartTime(jsonObject.getLong("st")); - txGroup.setNowTime(jsonObject.getLong("nt")); - txGroup.setState(jsonObject.getInteger("s")); - txGroup.setIsCompensate(jsonObject.getInteger("i")); - txGroup.setRollback(jsonObject.getInteger("r")); - txGroup.setHasOver(jsonObject.getInteger("o")); - JSONArray array = jsonObject.getJSONArray("l"); - int length = array.size(); - for (int i = 0; i < length; i++) { - JSONObject object = array.getJSONObject(i); - TxInfo info = new TxInfo(); - info.setKid(object.getString("k")); - info.setChannelAddress(object.getString("ca")); - info.setNotify(object.getInteger("n")); - info.setIsGroup(object.getInteger("ig")); - info.setAddress(object.getString("a")); - info.setUniqueKey(object.getString("u")); - - info.setModel(object.getString("mn")); - info.setModelIpAddress(object.getString("ip")); - info.setMethodStr(object.getString("ms")); - - txGroup.getList().add(info); - } - return txGroup; - - } catch (Exception e) { - return null; - } - - } - - public String toJsonString(boolean noList) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("g", getGroupId()); - jsonObject.put("st", getStartTime()); - jsonObject.put("nt", getNowTime()); - jsonObject.put("s", getState()); - jsonObject.put("i", getIsCompensate()); - jsonObject.put("r", getRollback()); - jsonObject.put("o",getHasOver()); - if(noList) { - JSONArray jsonArray = new JSONArray(); - for (TxInfo info : getList()) { - JSONObject item = new JSONObject(); - item.put("k", info.getKid()); - item.put("ca", info.getChannelAddress()); - item.put("n", info.getNotify()); - item.put("ig", info.getIsGroup()); - item.put("a", info.getAddress()); - item.put("u", info.getUniqueKey()); - - item.put("mn", info.getModel()); - item.put("ip", info.getModelIpAddress()); - item.put("ms", info.getMethodStr()); - - - jsonArray.add(item); - } - jsonObject.put("l", jsonArray); - } - return jsonObject.toString(); - } - - public String toJsonString() { - return toJsonString(true); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/model/TxInfo.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/model/TxInfo.java deleted file mode 100644 index 493ccfe9..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/model/TxInfo.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.codingapi.tm.netty.model; - -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tm.model.ChannelSender; -import com.lorne.core.framework.model.JsonModel; - -/** - * Created by lorne on 2017/6/7. - */ -public class TxInfo extends JsonModel { - - /** - * 任务唯一标示 - */ - private String kid; - - /** - * 模块管道名称(netty管道名称) - */ - private String channelAddress; - - /** - * 是否通知成功 - */ - private int notify; - - /** - * 0 不组合 - * 1 组合 - */ - private int isGroup; - - /** - * tm识别标示 - */ - private String address; - - /** - * tx识别标示 - */ - private String uniqueKey; - - - /** - * 管道发送数据 - */ - private ChannelSender channel; - - - /** - * 业务方法名称 - */ - private String methodStr; - - /** - * 模块名称 - */ - private String model; - - /** - * 模块地址 - */ - private String modelIpAddress; - - /** - * 是否提交(临时数据) - */ - private int isCommit; - - public int getIsCommit() { - return isCommit; - } - - public void setIsCommit(int isCommit) { - this.isCommit = isCommit; - } - - public String getMethodStr() { - return methodStr; - } - - public void setMethodStr(String methodStr) { - this.methodStr = methodStr; - } - - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - public String getModelIpAddress() { - return modelIpAddress; - } - - public void setModelIpAddress(String modelIpAddress) { - this.modelIpAddress = modelIpAddress; - } - - public String getKid() { - return kid; - } - - public void setKid(String kid) { - this.kid = kid; - } - - public ChannelSender getChannel() { - return channel; - } - - public void setChannel(ChannelSender channel) { - this.channel = channel; - } - - public String getChannelAddress() { - return channelAddress; - } - - public void setChannelAddress(String channelAddress) { - this.channelAddress = channelAddress; - } - - public int getNotify() { - return notify; - } - - public void setNotify(int notify) { - this.notify = notify; - } - - public int getIsGroup() { - return isGroup; - } - - public void setIsGroup(int isGroup) { - this.isGroup = isGroup; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getUniqueKey() { - return uniqueKey; - } - - public void setUniqueKey(String uniqueKey) { - this.uniqueKey = uniqueKey; - } - - @Override - public String toString() { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("kid",getKid()); - jsonObject.put("channelAddress", getChannelAddress()); - jsonObject.put("notify",getNotify()); - jsonObject.put("isGroup",getIsGroup()); - jsonObject.put("address",getAddress()); - jsonObject.put("uniqueKey",getUniqueKey()); - - jsonObject.put("model", getModel()); - jsonObject.put("modelIpAddress", getModelIpAddress()); - jsonObject.put("methodStr", getMethodStr()); - - return jsonObject.toString(); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/IActionService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/IActionService.java deleted file mode 100644 index 5a7926ba..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/IActionService.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.codingapi.tm.netty.service; - -import com.alibaba.fastjson.JSONObject; - -/** - * create by lorne on 2017/11/11 - */ -public interface IActionService { - - - String execute(String channelAddress,String key,JSONObject params); - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/NettyServerService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/NettyServerService.java deleted file mode 100644 index 64253dc1..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/NettyServerService.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.codingapi.tm.netty.service; - -/** - * Created by lorne on 2017/6/30. - */ -public interface NettyServerService { - - void start(); - - void close(); - - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/NettyService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/NettyService.java deleted file mode 100644 index 19450b2e..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/NettyService.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.codingapi.tm.netty.service; - -/** - * create by lorne on 2017/11/11 - */ -public interface NettyService { - - - - IActionService getActionService(String action); -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionATGServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionATGServiceImpl.java deleted file mode 100644 index 85adf62b..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionATGServiceImpl.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tm.manager.service.TxManagerService; -import com.codingapi.tm.netty.model.TxGroup; -import com.codingapi.tm.netty.service.IActionService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * 添加事务组 - * create by lorne on 2017/11/11 - */ -@Service(value = "atg") -public class ActionATGServiceImpl implements IActionService{ - - - @Autowired - private TxManagerService txManagerService; - - @Override - public String execute(String channelAddress,String key,JSONObject params ) { - String res = ""; - String groupId = params.getString("g"); - String taskId = params.getString("t"); - String methodStr = params.getString("ms"); - int isGroup = params.getInteger("s"); - - TxGroup txGroup = txManagerService.addTransactionGroup(groupId, taskId, isGroup, channelAddress, methodStr); - - if(txGroup!=null) { - txGroup.setNowTime(System.currentTimeMillis()); - res = txGroup.toJsonString(false); - }else { - res = ""; - } - return res; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionCGServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionCGServiceImpl.java deleted file mode 100644 index a0648ac0..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionCGServiceImpl.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tm.manager.service.TxManagerService; -import com.codingapi.tm.netty.model.TxGroup; -import com.codingapi.tm.netty.service.IActionService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * 创建事务组 - * create by lorne on 2017/11/11 - */ -@Service(value = "cg") -public class ActionCGServiceImpl implements IActionService{ - - - @Autowired - private TxManagerService txManagerService; - - @Override - public String execute(String channelAddress, String key, JSONObject params ) { - String res = ""; - String groupId = params.getString("g"); - TxGroup txGroup = txManagerService.createTransactionGroup(groupId); - if(txGroup!=null) { - txGroup.setNowTime(System.currentTimeMillis()); - res = txGroup.toJsonString(false); - }else { - res = ""; - } - return res; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionCKGServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionCKGServiceImpl.java deleted file mode 100644 index 4a8d254c..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionCKGServiceImpl.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tm.manager.service.TxManagerService; -import com.codingapi.tm.netty.service.IActionService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * 检查事务组 - * create by lorne on 2017/11/11 - */ -@Service(value = "ckg") -public class ActionCKGServiceImpl implements IActionService{ - - - @Autowired - private TxManagerService txManagerService; - - @Override - public String execute(String channelAddress, String key, JSONObject params ) { - String res = ""; - String groupId = params.getString("g"); - String taskId = params.getString("t"); - int bs = txManagerService.cleanNotifyTransaction(groupId,taskId); - - res = String.valueOf(bs); - return res; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionCServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionCServiceImpl.java deleted file mode 100644 index 87cd5c90..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionCServiceImpl.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.codingapi.tm.netty.service.IActionService; -import org.springframework.stereotype.Service; - -/** - * 补偿回调 - * create by lorne on 2017/11/11 - */ -@Service(value = "c") -public class ActionCServiceImpl extends BaseSignalTaskService implements IActionService { - - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionCTGServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionCTGServiceImpl.java deleted file mode 100644 index a4ac3a1b..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionCTGServiceImpl.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tm.manager.service.TxManagerService; -import com.codingapi.tm.netty.service.IActionService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * 关闭事务组 - * create by lorne on 2017/11/11 - */ -@Service(value = "ctg") -public class ActionCTGServiceImpl implements IActionService{ - - - @Autowired - private TxManagerService txManagerService; - - @Override - public String execute(String channelAddress, String key, JSONObject params ) { - String groupId = params.getString("g"); - int state = params.getInteger("s"); - String res = String.valueOf(txManagerService.closeTransactionGroup(groupId,state)); - return res; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionGLBServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionGLBServiceImpl.java deleted file mode 100644 index 6e282e44..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionGLBServiceImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tm.manager.service.LoadBalanceService; -import com.codingapi.tm.model.LoadBalanceInfo; -import com.codingapi.tm.netty.service.IActionService; -import com.lorne.core.framework.utils.encode.Base64Utils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * 获取负载模块信息 - * create by lorne on 2017/11/11 - */ -@Service(value = "glb") -public class ActionGLBServiceImpl implements IActionService{ - - - @Autowired - private LoadBalanceService loadBalanceService; - - - @Override - public String execute(String channelAddress, String key, JSONObject params ) { - String res; - String groupId = params.getString("g"); - String k = params.getString("k"); - - LoadBalanceInfo loadBalanceInfo = loadBalanceService.get(groupId,k); - if(loadBalanceInfo==null){ - res = ""; - }else{ - res = loadBalanceInfo.getData(); - } - return res; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionHServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionHServiceImpl.java deleted file mode 100644 index a4795292..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionHServiceImpl.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tm.config.ConfigReader; -import com.codingapi.tm.manager.service.TxManagerService; -import com.codingapi.tm.netty.service.IActionService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * 心跳包 - * create by lorne on 2017/11/11 - */ -@Service(value = "h") -public class ActionHServiceImpl implements IActionService{ - - - @Autowired - private ConfigReader configReader; - - @Override - public String execute(String channelAddress, String key, JSONObject params ) { - return String.valueOf(configReader.getTransactionNettyDelayTime()); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionPLBServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionPLBServiceImpl.java deleted file mode 100644 index 702ceb67..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionPLBServiceImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tm.manager.service.LoadBalanceService; -import com.codingapi.tm.model.LoadBalanceInfo; -import com.codingapi.tm.netty.service.IActionService; -import com.lorne.core.framework.utils.encode.Base64Utils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * 添加负载模块信息 - * create by lorne on 2017/11/11 - */ -@Service(value = "plb") -public class ActionPLBServiceImpl implements IActionService{ - - - @Autowired - private LoadBalanceService loadBalanceService; - - - @Override - public String execute(String channelAddress, String key, JSONObject params ) { - - String groupId = params.getString("g"); - String k = params.getString("k"); - String data = params.getString("d"); - - LoadBalanceInfo loadBalanceInfo = new LoadBalanceInfo(); - loadBalanceInfo.setData(data); - loadBalanceInfo.setKey(k); - loadBalanceInfo.setGroupId(groupId); - boolean ok = loadBalanceService.put(loadBalanceInfo); - - return ok?"1":"0"; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionRGServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionRGServiceImpl.java deleted file mode 100644 index 63ca2809..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionRGServiceImpl.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tm.manager.service.TxManagerService; -import com.codingapi.tm.netty.service.IActionService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * 强制回滚事务组 - * create by lorne on 2017/11/11 - */ -@Service(value = "rg") -public class ActionRGServiceImpl implements IActionService{ - - - @Autowired - private TxManagerService txManagerService; - - @Override - public String execute(String channelAddress, String key, JSONObject params ) { - String res = ""; - String groupId = params.getString("g"); - boolean bs = txManagerService.rollbackTransactionGroup(groupId); - res = bs ? "1" : "0"; - return res; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionTServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionTServiceImpl.java deleted file mode 100644 index 7407a19f..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionTServiceImpl.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.codingapi.tm.netty.service.IActionService; -import org.springframework.stereotype.Service; - -/** - * 通知事务回调 - * create by lorne on 2017/11/11 - */ -@Service(value = "t") -public class ActionTServiceImpl extends BaseSignalTaskService implements IActionService { - - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionUMIServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionUMIServiceImpl.java deleted file mode 100644 index eb1d3ec1..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/ActionUMIServiceImpl.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tm.framework.utils.SocketManager; -import com.codingapi.tm.manager.ModelInfoManager; -import com.codingapi.tm.model.ModelInfo; -import com.codingapi.tm.netty.service.IActionService; -import org.springframework.stereotype.Service; - -/** - * 上传模块信息 - * create by lorne on 2017/11/11 - */ -@Service(value = "umi") -public class ActionUMIServiceImpl implements IActionService { - - - @Override - public String execute(String channelAddress, String key, JSONObject params) { - String res = "1"; - - String uniqueKey = params.getString("u"); - String ipAddress = params.getString("i"); - String model = params.getString("m"); - - - ModelInfo modelInfo = new ModelInfo(); - modelInfo.setChannelName(channelAddress); - modelInfo.setIpAddress(ipAddress); - modelInfo.setModel(model); - modelInfo.setUniqueKey(uniqueKey); - - ModelInfoManager.getInstance().addModelInfo(modelInfo); - - SocketManager.getInstance().onLine(channelAddress, uniqueKey); - - return res; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/BaseSignalTaskService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/BaseSignalTaskService.java deleted file mode 100644 index ae77e249..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/BaseSignalTaskService.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.alibaba.fastjson.JSONObject; -import com.lorne.core.framework.utils.task.ConditionUtils; -import com.lorne.core.framework.utils.task.IBack; -import com.lorne.core.framework.utils.task.Task; - -/** - * create by lorne on 2017/11/13 - */ -public class BaseSignalTaskService { - - public String execute(String channelAddress, String key, JSONObject params) { - String res = ""; - final String data = params.getString("d"); - Task task = ConditionUtils.getInstance().getTask(key); - if (task != null) { - task.setBack(new IBack() { - @Override - public Object doing(Object... objs) throws Throwable { - return data; - } - }); - task.signalTask(); - } - return res; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/NettyServerServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/NettyServerServiceImpl.java deleted file mode 100644 index 4efa4363..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/NettyServerServiceImpl.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.codingapi.tm.Constants; -import com.codingapi.tm.config.ConfigReader; -import com.codingapi.tm.netty.handler.TxCoreServerHandler; -import com.codingapi.tm.netty.service.NettyServerService; -import com.codingapi.tm.netty.service.NettyService; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.codec.LengthFieldBasedFrameDecoder; -import io.netty.handler.codec.LengthFieldPrepender; -import io.netty.handler.logging.LogLevel; -import io.netty.handler.logging.LoggingHandler; -import io.netty.handler.timeout.IdleStateHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -/** - * Created by lorne on 2017/6/30. - */ -@Service -public class NettyServerServiceImpl implements NettyServerService,DisposableBean { - - - @Autowired - private NettyService nettyService; - - private Logger logger = LoggerFactory.getLogger(NettyServerServiceImpl.class); - - private EventLoopGroup bossGroup; - private EventLoopGroup workerGroup; - - private TxCoreServerHandler txCoreServerHandler; - - private ExecutorService threadPool = Executors.newFixedThreadPool(100); - - @Autowired - private ConfigReader configReader; - - - @Override - public void start() { - final int heartTime = configReader.getTransactionNettyHeartTime()+10; - txCoreServerHandler = new TxCoreServerHandler(threadPool,nettyService); - bossGroup = new NioEventLoopGroup(50); // (1) - workerGroup = new NioEventLoopGroup(); - try { - ServerBootstrap b = new ServerBootstrap(); - b.group(bossGroup, workerGroup) - .channel(NioServerSocketChannel.class) - .option(ChannelOption.SO_BACKLOG, 100) - .handler(new LoggingHandler(LogLevel.INFO)) - .childHandler(new ChannelInitializer() { - @Override - public void initChannel(SocketChannel ch) throws Exception { - ch.pipeline().addLast("timeout", new IdleStateHandler(heartTime, heartTime, heartTime, TimeUnit.SECONDS)); - - ch.pipeline().addLast(new LengthFieldPrepender(4, false)); - ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)); - - ch.pipeline().addLast(txCoreServerHandler); - } - }); - - // Start the server. - b.bind(Constants.socketPort); - logger.info("Socket started on port(s): " + Constants.socketPort + " (socket)"); - - } catch (Exception e) { - // Shut down all event loops to terminate all threads. - e.printStackTrace(); - } - } - - @Override - public void close() { - if (workerGroup != null) { - workerGroup.shutdownGracefully(); - } - if (bossGroup != null) { - bossGroup.shutdownGracefully(); - } - - } - - @Override - public void destroy() throws Exception { - close(); - threadPool.shutdown(); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/NettyServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/NettyServiceImpl.java deleted file mode 100644 index 033a9903..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/netty/service/impl/NettyServiceImpl.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.codingapi.tm.netty.service.impl; - -import com.codingapi.tm.netty.service.IActionService; -import com.codingapi.tm.netty.service.NettyService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.stereotype.Service; - -/** - * create by lorne on 2017/11/11 - */ -@Service -public class NettyServiceImpl implements NettyService{ - - @Autowired - private ApplicationContext spring; - - @Override - public IActionService getActionService(String action) { - return spring.getBean(action,IActionService.class); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/JedisClusterConfig.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/JedisClusterConfig.java deleted file mode 100644 index 6f711243..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/JedisClusterConfig.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.codingapi.tm.redis; - - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.core.env.MapPropertySource; -import org.springframework.data.redis.connection.RedisClusterConfiguration; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import redis.clients.jedis.HostAndPort; -import redis.clients.jedis.JedisCluster; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * Created by lorne on 2017/10/31. - */ -@ConditionalOnClass({JedisCluster.class}) -@EnableConfigurationProperties(RedisProperties.class) -public class JedisClusterConfig { - - @Autowired - private RedisProperties redisProperties; - - @Bean - public JedisCluster jedisClusterFactory() { - String[] serverArray = redisProperties.getNodes().split(","); - Set nodes = new HashSet(); - for (String ipPort: serverArray) { - String[] ipPortPair = ipPort.split(":"); - nodes.add(new HostAndPort(ipPortPair[0].trim(),Integer.valueOf(ipPortPair[1].trim()))); - } - return new JedisCluster(nodes, redisProperties.getCommandTimeout()); - } - - @Bean - public RedisTemplate redisTemplateFactory(){ - RedisTemplate redisTemplate =new RedisTemplate(); - redisTemplate.setConnectionFactory(jedisConnectionFactory()); - - //指定具体序列化方式 不过这种方式不是很好,一个系统中可能对应值的类型不一样,如果全部使用StringRedisSerializer 序列化 - //会照成其他类型报错,所以还是推荐使用第一种,直接指定泛型的类型,spring 会根据指定类型序列化。 -// redisTemplate.setKeySerializer( new StringRedisSerializer()); -// redisTemplate.setValueSerializer(new StringRedisSerializer()); -// redisTemplate.setHashKeySerializer(new StringRedisSerializer()); -// redisTemplate.setHashValueSerializer(new StringRedisSerializer()); - return redisTemplate; - } - - - /** - * redisCluster配置 - * @return - */ - @Bean - public RedisClusterConfiguration redisClusterConfiguration() { - Map source = new HashMap(); - source.put("spring.redis.cluster.nodes", redisProperties.getNodes()); - source.put("spring.redis.cluster.timeout", redisProperties.getCommandTimeout()); - return new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source)); - } - - - /** - * 其实在JedisConnectionFactory的afterPropertiesSet()方法 中 - * if(cluster !=null) this.cluster =createCluster(); - * 也就是当 - * spring.redis.cluster.nodes 配置好的情况下,就可以实例化 JedisCluster. - * 也就是说,我们使用JedisCluster 的方式只需要在application.properties 配置文件中 - * - * #redis cluster - * spring.redis.cluster.nodes=127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002 - * - * RedisTemplate.afterPropertiesSet() 中查看到最终方法中使用了JedisCluster 对象。 - * 也就是说 redisTemplate依赖jedis ,内部操作的就是jedis,同理内部也操作jedisCluster. - * - * - * @return - */ - @Bean - public JedisConnectionFactory jedisConnectionFactory() { - return new JedisConnectionFactory(redisClusterConfiguration()); - } -} \ No newline at end of file diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/RedisConfig.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/RedisConfig.java deleted file mode 100644 index 81ceae8f..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/RedisConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.codingapi.tm.redis; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import redis.clients.jedis.JedisPoolConfig; - -/** - * Created by lorne on 2017/7/5. - */ - -@EnableAutoConfiguration -public class RedisConfig { - - private static Logger logger = LoggerFactory.getLogger(RedisConfig.class); - - @Bean - @ConfigurationProperties(prefix = "spring.redis") - public JedisPoolConfig getRedisConfig() { - JedisPoolConfig config = new JedisPoolConfig(); - return config; - } - - @Bean - @ConfigurationProperties(prefix = "spring.redis") - public JedisConnectionFactory getConnectionFactory() { - JedisConnectionFactory factory = new JedisConnectionFactory(); - JedisPoolConfig config = getRedisConfig(); - factory.setPoolConfig(config); - logger.info("JedisConnectionFactory bean init success."); - return factory; - } - - - @Bean - public RedisTemplate getRedisTemplate() { - return new StringRedisTemplate(getConnectionFactory()); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/RedisProperties.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/RedisProperties.java deleted file mode 100644 index bd1815c9..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/RedisProperties.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.codingapi.tm.redis; - - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** - * Created by lorne on 2017/10/31. - */ - -@Component -@ConfigurationProperties(prefix = "spring.redis.cluster") -public class RedisProperties { - - - private String nodes; - - private Integer commandTimeout; - - public String getNodes() { - return nodes; - } - - public void setNodes(String nodes) { - this.nodes = nodes; - } - - public Integer getCommandTimeout() { - return commandTimeout; - } - - public void setCommandTimeout(Integer commandTimeout) { - this.commandTimeout = commandTimeout; - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/service/RedisServerService.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/service/RedisServerService.java deleted file mode 100644 index 0fadc5e1..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/service/RedisServerService.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.codingapi.tm.redis.service; - -import com.codingapi.tm.netty.model.TxGroup; - -import java.util.List; - -/** - * create by lorne on 2017/11/11 - */ -public interface RedisServerService { - - String loadNotifyJson(); - - void saveTransaction(String key, String json); - - TxGroup getTxGroupByKey(String key); - - void saveCompensateMsg(String name, String json); - - List getKeys(String key); - - List getValuesByKeys(List keys); - - String getValueByKey(String key); - - void deleteKey(String key); - - void saveLoadBalance(String groupName,String key,String data); - - - String getLoadBalance(String groupName,String key); - - -} diff --git a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/service/impl/RedisServerServiceImpl.java b/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/service/impl/RedisServerServiceImpl.java deleted file mode 100644 index 2b28ff36..00000000 --- a/spring-cloud-learn/tx-manager/src/main/java/com/codingapi/tm/redis/service/impl/RedisServerServiceImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.codingapi.tm.redis.service.impl; - -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import com.codingapi.tm.config.ConfigReader; -import com.codingapi.tm.netty.model.TxGroup; -import com.codingapi.tm.redis.service.RedisServerService; -import org.apache.commons.lang.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.HashOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ValueOperations; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -/** - * create by lorne on 2017/11/11 - */ -@Service -public class RedisServerServiceImpl implements RedisServerService{ - - @Autowired - private RedisTemplate redisTemplate; - - @Autowired - private ConfigReader configReader; - - - public String loadNotifyJson() { - Set keys = redisTemplate.keys(configReader.getKeyPrefixCompensate()+"*"); - ValueOperations value = redisTemplate.opsForValue(); - JSONArray jsonArray = new JSONArray(); - for(String key:keys){ - String json = value.get(key); - JSONObject jsonObject = new JSONObject(); - jsonObject.put("key",key); - jsonObject.put("value",JSONObject.parse(json)); - jsonArray.add(jsonObject); - } - return jsonArray.toJSONString(); - } - - @Override - public void saveTransaction(String key, String json) { - ValueOperations value = redisTemplate.opsForValue(); - value.set(key, json, configReader.getRedisSaveMaxTime(), TimeUnit.SECONDS); - } - - - @Override - public TxGroup getTxGroupByKey(String key) { - ValueOperations value = redisTemplate.opsForValue(); - String json = value.get(key); - if (StringUtils.isEmpty(json)) { - return null; - } - return TxGroup.parser(json); - } - - - @Override - public void saveCompensateMsg(String name, String json) { - ValueOperations value = redisTemplate.opsForValue(); - value.set(name, json); - } - - @Override - public List getKeys(String key) { - Set keys = redisTemplate.keys(key); - List list = new ArrayList(); - for (String k : keys) { - list.add(k); - } - return list; - } - - @Override - public List getValuesByKeys(List keys) { - ValueOperations value = redisTemplate.opsForValue(); - List list = new ArrayList<>(); - for (String key : keys) { - String json = value.get(key); - list.add(json); - } - return list; - } - - @Override - public String getValueByKey(String key) { - ValueOperations value = redisTemplate.opsForValue(); - return value.get(key); - } - - @Override - public void deleteKey(String key) { - redisTemplate.delete(key); - } - - @Override - public void saveLoadBalance(String groupName, String key, String data) { - HashOperations value = redisTemplate.opsForHash(); - value.put(groupName,key,data); - } - - - @Override - public String getLoadBalance(String groupName, String key) { - HashOperations value = redisTemplate.opsForHash(); - return value.get(groupName,key); - } -} diff --git a/spring-cloud-learn/tx-manager/src/main/resources/application.properties b/spring-cloud-learn/tx-manager/src/main/resources/application.properties deleted file mode 100644 index 857ad802..00000000 --- a/spring-cloud-learn/tx-manager/src/main/resources/application.properties +++ /dev/null @@ -1,100 +0,0 @@ - -#######################################txmanager-start################################################# -#\u670D\u52A1\u7AEF\u53E3 -server.port=7000 - -#tx-manager\u4E0D\u5F97\u4FEE\u6539 -spring.application.name=tx-manager - -spring.mvc.static-path-pattern=/** -spring.resources.static-locations=classpath:/static/ -#######################################txmanager-end################################################# - - -#zookeeper\u5730\u5740 -#spring.cloud.zookeeper.connect-string=127.0.0.1:2181 -#spring.cloud.zookeeper.discovery.preferIpAddress = true - -#eureka \u5730\u5740 -eureka.client.service-url.defaultZone=http://127.0.0.1:1000/eureka/ -eureka.instance.prefer-ip-address=true - -#######################################redis-start################################################# -#redis \u914D\u7F6E\u6587\u4EF6\uFF0C\u6839\u636E\u60C5\u51B5\u9009\u62E9\u96C6\u7FA4\u6216\u8005\u5355\u673A\u6A21\u5F0F - -##redis \u96C6\u7FA4\u73AF\u5883\u914D\u7F6E -##redis cluster -#spring.redis.cluster.nodes=127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003 -#spring.redis.cluster.commandTimeout=5000 - -##redis \u5355\u70B9\u73AF\u5883\u914D\u7F6E -#redis -#redis\u4E3B\u673A\u5730\u5740 -spring.redis.host=localhost -#redis\u4E3B\u673A\u7AEF\u53E3 -spring.redis.port=6379 -#redis\u94FE\u63A5\u5BC6\u7801 -spring.redis.password= -spring.redis.pool.maxActive=10 -spring.redis.pool.maxWait=-1 -spring.redis.pool.maxIdle=5 -spring.redis.pool.minIdle=0 -spring.redis.timeout=0 -#####################################redis-end################################################### - - - - -#######################################LCN-start################################################# -#\u4E1A\u52A1\u6A21\u5757\u4E0ETxManager\u4E4B\u95F4\u901A\u8BAF\u7684\u6700\u5927\u7B49\u5F85\u65F6\u95F4\uFF08\u5355\u4F4D\uFF1A\u79D2\uFF09 -#\u901A\u8BAF\u65F6\u95F4\u662F\u6307\uFF1A\u53D1\u8D77\u65B9\u4E0E\u54CD\u5E94\u65B9\u4E4B\u95F4\u5B8C\u6210\u4E00\u6B21\u7684\u901A\u8BAF\u65F6\u95F4\u3002 -#\u8BE5\u5B57\u6BB5\u4EE3\u8868\u7684\u662FTx-Client\u6A21\u5757\u4E0ETxManager\u6A21\u5757\u4E4B\u95F4\u7684\u6700\u5927\u901A\u8BAF\u65F6\u95F4\uFF0C\u8D85\u8FC7\u8BE5\u65F6\u95F4\u672A\u54CD\u5E94\u672C\u6B21\u8BF7\u6C42\u5931\u8D25\u3002 -tm.transaction.netty.delaytime = 5 - -#\u4E1A\u52A1\u6A21\u5757\u4E0ETxManager\u4E4B\u95F4\u901A\u8BAF\u7684\u5FC3\u8DF3\u65F6\u95F4\uFF08\u5355\u4F4D\uFF1A\u79D2\uFF09 -tm.transaction.netty.hearttime = 15 - -#\u5B58\u50A8\u5230redis\u4E0B\u7684\u6570\u636E\u6700\u5927\u4FDD\u5B58\u65F6\u95F4\uFF08\u5355\u4F4D\uFF1A\u79D2\uFF09 -#\u8BE5\u5B57\u6BB5\u4EC5\u4EE3\u8868\u7684\u4E8B\u52A1\u6A21\u5757\u6570\u636E\u7684\u6700\u5927\u4FDD\u5B58\u65F6\u95F4\uFF0C\u8865\u507F\u6570\u636E\u4F1A\u6C38\u4E45\u4FDD\u5B58\u3002 -tm.redis.savemaxtime=30 - -#socket server Socket\u5BF9\u5916\u670D\u52A1\u7AEF\u53E3 -#TxManager\u7684LCN\u534F\u8BAE\u7684\u7AEF\u53E3 -tm.socket.port=9999 - -#\u6700\u5927socket\u8FDE\u63A5\u6570 -#TxManager\u6700\u5927\u5141\u8BB8\u7684\u5EFA\u7ACB\u8FDE\u63A5\u6570\u91CF -tm.socket.maxconnection=100 - -#\u4E8B\u52A1\u81EA\u52A8\u8865\u507F (true:\u5F00\u542F\uFF0Cfalse:\u5173\u95ED) -# \u8BF4\u660E\uFF1A -# \u5F00\u542F\u81EA\u52A8\u8865\u507F\u4EE5\u540E\uFF0C\u5FC5\u987B\u8981\u914D\u7F6E tm.compensate.notifyUrl \u5730\u5740\uFF0C\u4EC5\u5F53tm.compensate.notifyUrl \u5728\u8BF7\u6C42\u8865\u507F\u786E\u8BA4\u65F6\u8FD4\u56DEsuccess\u6216\u8005SUCCESS\u65F6\uFF0C\u624D\u4F1A\u6267\u884C\u81EA\u52A8\u8865\u507F\uFF0C\u5426\u5219\u4E0D\u4F1A\u81EA\u52A8\u8865\u507F\u3002 -# \u5173\u95ED\u81EA\u52A8\u8865\u507F\uFF0C\u5F53\u51FA\u73B0\u6570\u636E\u65F6\u4E5F\u4F1A tm.compensate.notifyUrl \u5730\u5740\u3002 -# \u5F53tm.compensate.notifyUrl \u65E0\u6548\u65F6\uFF0C\u4E0D\u5F71\u54CDTxManager\u8FD0\u884C\uFF0C\u4EC5\u4F1A\u5F71\u54CD\u81EA\u52A8\u8865\u507F\u3002 -tm.compensate.auto=false - -#\u4E8B\u52A1\u8865\u507F\u8BB0\u5F55\u56DE\u8C03\u5730\u5740(rest api \u5730\u5740\uFF0Cpost json\u683C\u5F0F) -#\u8BF7\u6C42\u8865\u507F\u662F\u5728\u5F00\u542F\u81EA\u52A8\u8865\u507F\u65F6\u624D\u4F1A\u8BF7\u6C42\u7684\u5730\u5740\u3002\u8BF7\u6C42\u5206\u4E3A\u4E24\u79CD\uFF1A1.\u8865\u507F\u51B3\u7B56\uFF0C2.\u8865\u507F\u7ED3\u679C\u901A\u77E5\uFF0C\u53EF\u901A\u8FC7\u901A\u8FC7action\u53C2\u6570\u533A\u5206compensate\u4E3A\u8865\u507F\u8BF7\u6C42\u3001notify\u4E3A\u8865\u507F\u901A\u77E5\u3002 -#*\u6CE8\u610F\u5F53\u8BF7\u6C42\u8865\u507F\u51B3\u7B56\u65F6\uFF0C\u9700\u8981\u8865\u507F\u670D\u52A1\u8FD4\u56DE"SUCCESS"\u5B57\u7B26\u4E32\u4EE5\u540E\u624D\u53EF\u4EE5\u6267\u884C\u81EA\u52A8\u8865\u507F\u3002 -#\u8BF7\u6C42\u8865\u507F\u7ED3\u679C\u901A\u77E5\u5219\u53EA\u9700\u8981\u63A5\u53D7\u901A\u77E5\u5373\u53EF\u3002 -#\u8BF7\u6C42\u8865\u507F\u7684\u6837\u4F8B\u6570\u636E\u683C\u5F0F: -#{"groupId":"TtQxTwJP","action":"compensate","json":"{\"address\":\"133.133.5.100:8081\",\"className\":\"com.example.demo.test02.service.DemoServiceImpl\",\"currentTime\":1511356150413,\"data\":\"C5IBLWNvbS5leGFtcGxlLmRlbW8uc2VydmljZS5pbXBsLkRlbW9TZXJ2aWNlSW1wbAwSBHNhdmUbehBqYXZhLmxhbmcuT2JqZWN0GAAQARwjeg9qYXZhLmxhbmcuQ2xhc3MYABABJCo/cHVibGljIGludCBjb20uZXhhbXBsZS5kZW1vLnNlcnZpY2UuaW1wbC5EZW1vU2VydmljZUltcGwuc2F2ZSgp\",\"groupId\":\"TtQxTwJP\",\"methodStr\":\"public int com.example.demo.test02.service.DemoServiceImpl.save()\",\"model\":\"demo1\",\"state\":0,\"time\":36,\"txGroup\":{\"groupId\":\"TtQxTwJP\",\"hasOver\":1,\"isCompensate\":0,\"list\":[{\"address\":\"133.133.5.100:8899\",\"isCompensate\":0,\"isGroup\":0,\"kid\":\"wnlEJoSl\",\"methodStr\":\"public int com.example.demo.test02.service.DemoServiceImpl.save()\",\"model\":\"demo2\",\"modelIpAddress\":\"133.133.5.100:8082\",\"channelAddress\":\"/133.133.5.100:64153\",\"notify\":1,\"uniqueKey\":\"bc13881a5d2ab2ace89ae5d34d608447\"}],\"nowTime\":0,\"startTime\":1511356150379,\"state\":1},\"uniqueKey\":\"be6eea31e382f1f0878d07cef319e4d7\"}"} -#\u8BF7\u6C42\u8865\u507F\u7684\u8FD4\u56DE\u6570\u636E\u6837\u4F8B\u6570\u636E\u683C\u5F0F: -#SUCCESS -#\u8BF7\u6C42\u8865\u507F\u7ED3\u679C\u901A\u77E5\u7684\u6837\u4F8B\u6570\u636E\u683C\u5F0F: -#{"resState":true,"groupId":"TtQxTwJP","action":"notify"} -tm.compensate.notifyUrl=http://ip:port/path - -#\u8865\u507F\u5931\u8D25\uFF0C\u518D\u6B21\u5C1D\u8BD5\u95F4\u9694\uFF08\u79D2\uFF09\uFF0C\u6700\u5927\u5C1D\u8BD5\u6B21\u65703\u6B21\uFF0C\u5F53\u8D85\u8FC73\u6B21\u5373\u4E3A\u8865\u507F\u5931\u8D25,\u5931\u8D25\u7684\u6570\u636E\u4F9D\u65E7\u8FD8\u4F1A\u5B58\u5728TxManager\u4E0B\u3002 -tm.compensate.tryTime=30 - -#\u5404\u4E8B\u52A1\u6A21\u5757\u81EA\u52A8\u8865\u507F\u7684\u65F6\u95F4\u4E0A\u9650(\u6BEB\u79D2) -#\u6307\u7684\u662F\u6A21\u5757\u6267\u884C\u81EA\u52A8\u8D85\u65F6\u7684\u6700\u5927\u65F6\u95F4\uFF0C\u8BE5\u6700\u5927\u65F6\u95F4\u82E5\u8FC7\u6BB5\u4F1A\u5BFC\u81F4\u4E8B\u52A1\u673A\u5236\u5F02\u5E38\uFF0C\u8BE5\u65F6\u95F4\u5FC5\u987B\u8981\u6A21\u5757\u4E4B\u95F4\u901A\u8BAF\u7684\u6700\u5927\u8D85\u8FC7\u65F6\u95F4\u3002 -#\u4F8B\u5982\uFF0C\u82E5\u6A21\u5757A\u4E0E\u6A21\u5757B\uFF0C\u8BF7\u6C42\u8D85\u65F6\u7684\u6700\u5927\u65F6\u95F4\u662F5\u79D2\uFF0C\u5219\u5EFA\u8BAE\u6539\u65F6\u95F4\u81F3\u5C11\u5927\u4E8E5\u79D2\u3002 -tm.compensate.maxWaitTime=5000 -#######################################LCN-end################################################# - - - - -logging.level.com.codingapi=debug \ No newline at end of file diff --git a/spring-cloud-learn/tx-manager/src/main/resources/banner.txt b/spring-cloud-learn/tx-manager/src/main/resources/banner.txt deleted file mode 100644 index 879248ac..00000000 --- a/spring-cloud-learn/tx-manager/src/main/resources/banner.txt +++ /dev/null @@ -1,13 +0,0 @@ - - >=> >=> >==> >=> - >=> >=> >=> >> >=> >=> - >=> >=> >=> >=> >=> - >=> >=> >=> >=>>=> - >=> >=> >=> > >=> - >=> >=> >=> >=> >>=> - >=======> >===> >=> >=> - - LCN-TxManager version:4.1.0 - - - diff --git a/spring-cloud-learn/tx-manager/src/main/resources/static/index.html b/spring-cloud-learn/tx-manager/src/main/resources/static/index.html deleted file mode 100644 index 4e8e3214..00000000 --- a/spring-cloud-learn/tx-manager/src/main/resources/static/index.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - TxManager v4.1.0 - - - - - - - - - - - - -
- -

TxManagerV4.1.0 服务已启动

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
属性名称属性值
- Socket对外服务IP -
- Socket对外服务端口 -
- 最大连接数 -
- 当前连接数 -
- TxManager模块心跳间隔时间(秒) -
- TxManager模块通讯最大等待时间(秒) -
- redis服务状态 -
- redis存储最大时间(秒) -
- 负载均衡服务器地址 -
- 补偿回调地址(rest api 地址,post json格式) -
- 存在补偿数据 -
- 开启自动补偿(true 开启,false 关闭) -
- 补偿失败尝试间隔时间(秒) -
- 各事务模块自动补偿的时间上限(毫秒) -
- - - 在线模块 - 事务补偿 - -
- - - -
- - \ No newline at end of file diff --git a/spring-cloud-learn/tx-manager/src/main/resources/static/log.html b/spring-cloud-learn/tx-manager/src/main/resources/static/log.html deleted file mode 100644 index 84aa09d7..00000000 --- a/spring-cloud-learn/tx-manager/src/main/resources/static/log.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - 事务补偿 - - - - - - - - - - - - - - -
- - 返回首页 - -
- 说明:
- 补偿记录展示的数据是分布事务发起方的切面数据信息。
- 记录时间是指发起方出现补偿时记录下的时间,并非是TxManager的记录数据的时间。
-
- - - -
-
-
- - - - - - - - - - - - - - - - - -
模块名称条数
-
-
- -
-
- - - - - - - - - - - - - - -
记录日期
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
记录时间执行方法执行时间操作
-
-
-
- - - - - - - - - - - - -
- - \ No newline at end of file diff --git a/spring-cloud-learn/tx-manager/src/main/resources/static/model.html b/spring-cloud-learn/tx-manager/src/main/resources/static/model.html deleted file mode 100644 index 45853cb1..00000000 --- a/spring-cloud-learn/tx-manager/src/main/resources/static/model.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - 在线模块 - - - - - - - - - - - - - - -
- - 返回首页 - - -
- - - - - - - - - - - - - - - - - - - - - - - -
模块名称唯一标示模块地址管道名称
-
- - - - -
- - \ No newline at end of file diff --git a/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap-theme.css b/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap-theme.css deleted file mode 100644 index 09e3f044..00000000 --- a/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap-theme.css +++ /dev/null @@ -1,650 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ -.btn-default, -.btn-primary, -.btn-success, -.btn-info, -.btn-warning, -.btn-danger { - text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); -} - -.btn-default:active, -.btn-primary:active, -.btn-success:active, -.btn-info:active, -.btn-warning:active, -.btn-danger:active, -.btn-default.active, -.btn-primary.active, -.btn-success.active, -.btn-info.active, -.btn-warning.active, -.btn-danger.active { - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); -} - -.btn-default.disabled, -.btn-primary.disabled, -.btn-success.disabled, -.btn-info.disabled, -.btn-warning.disabled, -.btn-danger.disabled, -.btn-default[disabled], -.btn-primary[disabled], -.btn-success[disabled], -.btn-info[disabled], -.btn-warning[disabled], -.btn-danger[disabled], -fieldset[disabled] .btn-default, -fieldset[disabled] .btn-primary, -fieldset[disabled] .btn-success, -fieldset[disabled] .btn-info, -fieldset[disabled] .btn-warning, -fieldset[disabled] .btn-danger { - -webkit-box-shadow: none; - box-shadow: none; -} - -.btn-default .badge, -.btn-primary .badge, -.btn-success .badge, -.btn-info .badge, -.btn-warning .badge, -.btn-danger .badge { - text-shadow: none; -} - -.btn:active, -.btn.active { - background-image: none; -} - -.btn-default { - text-shadow: 0 1px 0 #fff; - background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); - background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); - background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - background-repeat: repeat-x; - border-color: #dbdbdb; - border-color: #ccc; -} - -.btn-default:hover, -.btn-default:focus { - background-color: #e0e0e0; - background-position: 0 -15px; -} - -.btn-default:active, -.btn-default.active { - background-color: #e0e0e0; - border-color: #dbdbdb; -} - -.btn-default.disabled, -.btn-default[disabled], -fieldset[disabled] .btn-default, -.btn-default.disabled:hover, -.btn-default[disabled]:hover, -fieldset[disabled] .btn-default:hover, -.btn-default.disabled:focus, -.btn-default[disabled]:focus, -fieldset[disabled] .btn-default:focus, -.btn-default.disabled.focus, -.btn-default[disabled].focus, -fieldset[disabled] .btn-default.focus, -.btn-default.disabled:active, -.btn-default[disabled]:active, -fieldset[disabled] .btn-default:active, -.btn-default.disabled.active, -.btn-default[disabled].active, -fieldset[disabled] .btn-default.active { - background-color: #e0e0e0; - background-image: none; -} - -.btn-primary { - background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); - background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); - background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - background-repeat: repeat-x; - border-color: #245580; -} - -.btn-primary:hover, -.btn-primary:focus { - background-color: #265a88; - background-position: 0 -15px; -} - -.btn-primary:active, -.btn-primary.active { - background-color: #265a88; - border-color: #245580; -} - -.btn-primary.disabled, -.btn-primary[disabled], -fieldset[disabled] .btn-primary, -.btn-primary.disabled:hover, -.btn-primary[disabled]:hover, -fieldset[disabled] .btn-primary:hover, -.btn-primary.disabled:focus, -.btn-primary[disabled]:focus, -fieldset[disabled] .btn-primary:focus, -.btn-primary.disabled.focus, -.btn-primary[disabled].focus, -fieldset[disabled] .btn-primary.focus, -.btn-primary.disabled:active, -.btn-primary[disabled]:active, -fieldset[disabled] .btn-primary:active, -.btn-primary.disabled.active, -.btn-primary[disabled].active, -fieldset[disabled] .btn-primary.active { - background-color: #265a88; - background-image: none; -} - -.btn-success { - background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); - background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); - background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - background-repeat: repeat-x; - border-color: #3e8f3e; -} - -.btn-success:hover, -.btn-success:focus { - background-color: #419641; - background-position: 0 -15px; -} - -.btn-success:active, -.btn-success.active { - background-color: #419641; - border-color: #3e8f3e; -} - -.btn-success.disabled, -.btn-success[disabled], -fieldset[disabled] .btn-success, -.btn-success.disabled:hover, -.btn-success[disabled]:hover, -fieldset[disabled] .btn-success:hover, -.btn-success.disabled:focus, -.btn-success[disabled]:focus, -fieldset[disabled] .btn-success:focus, -.btn-success.disabled.focus, -.btn-success[disabled].focus, -fieldset[disabled] .btn-success.focus, -.btn-success.disabled:active, -.btn-success[disabled]:active, -fieldset[disabled] .btn-success:active, -.btn-success.disabled.active, -.btn-success[disabled].active, -fieldset[disabled] .btn-success.active { - background-color: #419641; - background-image: none; -} - -.btn-info { - background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); - background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); - background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - background-repeat: repeat-x; - border-color: #28a4c9; -} - -.btn-info:hover, -.btn-info:focus { - background-color: #2aabd2; - background-position: 0 -15px; -} - -.btn-info:active, -.btn-info.active { - background-color: #2aabd2; - border-color: #28a4c9; -} - -.btn-info.disabled, -.btn-info[disabled], -fieldset[disabled] .btn-info, -.btn-info.disabled:hover, -.btn-info[disabled]:hover, -fieldset[disabled] .btn-info:hover, -.btn-info.disabled:focus, -.btn-info[disabled]:focus, -fieldset[disabled] .btn-info:focus, -.btn-info.disabled.focus, -.btn-info[disabled].focus, -fieldset[disabled] .btn-info.focus, -.btn-info.disabled:active, -.btn-info[disabled]:active, -fieldset[disabled] .btn-info:active, -.btn-info.disabled.active, -.btn-info[disabled].active, -fieldset[disabled] .btn-info.active { - background-color: #2aabd2; - background-image: none; -} - -.btn-warning { - background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); - background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); - background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - background-repeat: repeat-x; - border-color: #e38d13; -} - -.btn-warning:hover, -.btn-warning:focus { - background-color: #eb9316; - background-position: 0 -15px; -} - -.btn-warning:active, -.btn-warning.active { - background-color: #eb9316; - border-color: #e38d13; -} - -.btn-warning.disabled, -.btn-warning[disabled], -fieldset[disabled] .btn-warning, -.btn-warning.disabled:hover, -.btn-warning[disabled]:hover, -fieldset[disabled] .btn-warning:hover, -.btn-warning.disabled:focus, -.btn-warning[disabled]:focus, -fieldset[disabled] .btn-warning:focus, -.btn-warning.disabled.focus, -.btn-warning[disabled].focus, -fieldset[disabled] .btn-warning.focus, -.btn-warning.disabled:active, -.btn-warning[disabled]:active, -fieldset[disabled] .btn-warning:active, -.btn-warning.disabled.active, -.btn-warning[disabled].active, -fieldset[disabled] .btn-warning.active { - background-color: #eb9316; - background-image: none; -} - -.btn-danger { - background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); - background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); - background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - background-repeat: repeat-x; - border-color: #b92c28; -} - -.btn-danger:hover, -.btn-danger:focus { - background-color: #c12e2a; - background-position: 0 -15px; -} - -.btn-danger:active, -.btn-danger.active { - background-color: #c12e2a; - border-color: #b92c28; -} - -.btn-danger.disabled, -.btn-danger[disabled], -fieldset[disabled] .btn-danger, -.btn-danger.disabled:hover, -.btn-danger[disabled]:hover, -fieldset[disabled] .btn-danger:hover, -.btn-danger.disabled:focus, -.btn-danger[disabled]:focus, -fieldset[disabled] .btn-danger:focus, -.btn-danger.disabled.focus, -.btn-danger[disabled].focus, -fieldset[disabled] .btn-danger.focus, -.btn-danger.disabled:active, -.btn-danger[disabled]:active, -fieldset[disabled] .btn-danger:active, -.btn-danger.disabled.active, -.btn-danger[disabled].active, -fieldset[disabled] .btn-danger.active { - background-color: #c12e2a; - background-image: none; -} - -.thumbnail, -.img-thumbnail { - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); - box-shadow: 0 1px 2px rgba(0, 0, 0, .075); -} - -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus { - background-color: #e8e8e8; - background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); - background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); - background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); - background-repeat: repeat-x; -} - -.dropdown-menu > .active > a, -.dropdown-menu > .active > a:hover, -.dropdown-menu > .active > a:focus { - background-color: #2e6da4; - background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); - background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); - background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); - background-repeat: repeat-x; -} - -.navbar-default { - background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); - background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); - background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - background-repeat: repeat-x; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); -} - -.navbar-default .navbar-nav > .open > a, -.navbar-default .navbar-nav > .active > a { - background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); - background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); - background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); - background-repeat: repeat-x; - -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); - box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); -} - -.navbar-brand, -.navbar-nav > li > a { - text-shadow: 0 1px 0 rgba(255, 255, 255, .25); -} - -.navbar-inverse { - background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); - background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); - background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - background-repeat: repeat-x; - border-radius: 4px; -} - -.navbar-inverse .navbar-nav > .open > a, -.navbar-inverse .navbar-nav > .active > a { - background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); - background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); - background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); - background-repeat: repeat-x; - -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); - box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); -} - -.navbar-inverse .navbar-brand, -.navbar-inverse .navbar-nav > li > a { - text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); -} - -.navbar-static-top, -.navbar-fixed-top, -.navbar-fixed-bottom { - border-radius: 0; -} - -@media (max-width: 767px) { - .navbar .navbar-nav .open .dropdown-menu > .active > a, - .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #fff; - background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); - background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); - background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); - background-repeat: repeat-x; - } -} - -.alert { - text-shadow: 0 1px 0 rgba(255, 255, 255, .2); - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); -} - -.alert-success { - background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); - background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); - background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); - background-repeat: repeat-x; - border-color: #b2dba1; -} - -.alert-info { - background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); - background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); - background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); - background-repeat: repeat-x; - border-color: #9acfea; -} - -.alert-warning { - background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); - background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); - background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); - background-repeat: repeat-x; - border-color: #f5e79e; -} - -.alert-danger { - background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); - background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); - background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); - background-repeat: repeat-x; - border-color: #dca7a7; -} - -.progress { - background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); - background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); - background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); - background-repeat: repeat-x; -} - -.progress-bar { - background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); - background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); - background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); - background-repeat: repeat-x; -} - -.progress-bar-success { - background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); - background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); - background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); - background-repeat: repeat-x; -} - -.progress-bar-info { - background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); - background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); - background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); - background-repeat: repeat-x; -} - -.progress-bar-warning { - background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); - background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); - background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); - background-repeat: repeat-x; -} - -.progress-bar-danger { - background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); - background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); - background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); - background-repeat: repeat-x; -} - -.progress-bar-striped { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} - -.list-group { - border-radius: 4px; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); - box-shadow: 0 1px 2px rgba(0, 0, 0, .075); -} - -.list-group-item.active, -.list-group-item.active:hover, -.list-group-item.active:focus { - text-shadow: 0 -1px 0 #286090; - background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); - background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); - background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); - background-repeat: repeat-x; - border-color: #2b669a; -} - -.list-group-item.active .badge, -.list-group-item.active:hover .badge, -.list-group-item.active:focus .badge { - text-shadow: none; -} - -.panel { - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); - box-shadow: 0 1px 2px rgba(0, 0, 0, .05); -} - -.panel-default > .panel-heading { - background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); - background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); - background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); - background-repeat: repeat-x; -} - -.panel-primary > .panel-heading { - background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); - background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); - background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); - background-repeat: repeat-x; -} - -.panel-success > .panel-heading { - background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); - background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); - background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); - background-repeat: repeat-x; -} - -.panel-info > .panel-heading { - background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); - background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); - background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); - background-repeat: repeat-x; -} - -.panel-warning > .panel-heading { - background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); - background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); - background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); - background-repeat: repeat-x; -} - -.panel-danger > .panel-heading { - background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); - background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); - background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); - background-repeat: repeat-x; -} - -.well { - background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); - background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); - background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); - background-repeat: repeat-x; - border-color: #dcdcdc; - -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); -} - -/*# sourceMappingURL=bootstrap-theme.css.map */ diff --git a/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap-theme.css.map b/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap-theme.css.map deleted file mode 100644 index cda06aa4..00000000 --- a/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap-theme.css.map +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 3, - "sources": [ - "bootstrap-theme.css", - "less/theme.less", - "less/mixins/vendor-prefixes.less", - "less/mixins/gradients.less", - "less/mixins/reset-filter.less" - ], - "names": [], - "mappings": "AAAA;;;;GAIG;ACeH;;;;;;EAME,yCAAA;EC2CA,4FAAA;EACQ,oFAAA;CFvDT;ACgBC;;;;;;;;;;;;ECsCA,yDAAA;EACQ,iDAAA;CFxCT;ACMC;;;;;;;;;;;;;;;;;;ECiCA,yBAAA;EACQ,iBAAA;CFnBT;AC/BD;;;;;;EAuBI,kBAAA;CDgBH;ACyBC;;EAEE,uBAAA;CDvBH;AC4BD;EErEI,sEAAA;EACA,iEAAA;EACA,2FAAA;EAAA,oEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;EAuC2C,0BAAA;EAA2B,mBAAA;CDjBvE;ACpBC;;EAEE,0BAAA;EACA,6BAAA;CDsBH;ACnBC;;EAEE,0BAAA;EACA,sBAAA;CDqBH;ACfG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CD6BL;ACbD;EEtEI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CD8DD;AC5DC;;EAEE,0BAAA;EACA,6BAAA;CD8DH;AC3DC;;EAEE,0BAAA;EACA,sBAAA;CD6DH;ACvDG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CDqEL;ACpDD;EEvEI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CDsGD;ACpGC;;EAEE,0BAAA;EACA,6BAAA;CDsGH;ACnGC;;EAEE,0BAAA;EACA,sBAAA;CDqGH;AC/FG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CD6GL;AC3FD;EExEI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CD8ID;AC5IC;;EAEE,0BAAA;EACA,6BAAA;CD8IH;AC3IC;;EAEE,0BAAA;EACA,sBAAA;CD6IH;ACvIG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CDqJL;AClID;EEzEI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CDsLD;ACpLC;;EAEE,0BAAA;EACA,6BAAA;CDsLH;ACnLC;;EAEE,0BAAA;EACA,sBAAA;CDqLH;AC/KG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CD6LL;ACzKD;EE1EI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EAEA,uHAAA;ECnBF,oEAAA;EH4CA,4BAAA;EACA,sBAAA;CD8ND;AC5NC;;EAEE,0BAAA;EACA,6BAAA;CD8NH;AC3NC;;EAEE,0BAAA;EACA,sBAAA;CD6NH;ACvNG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CDqOL;AC1MD;;EClCE,mDAAA;EACQ,2CAAA;CFgPT;ACrMD;;EE3FI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF0FF,0BAAA;CD2MD;ACzMD;;;EEhGI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EFgGF,0BAAA;CD+MD;ACtMD;EE7GI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;ECnBF,oEAAA;EH+HA,mBAAA;ECjEA,4FAAA;EACQ,oFAAA;CF8QT;ACjND;;EE7GI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;ED2CF,yDAAA;EACQ,iDAAA;CFwRT;AC9MD;;EAEE,+CAAA;CDgND;AC5MD;EEhII,sEAAA;EACA,iEAAA;EACA,2FAAA;EAAA,oEAAA;EACA,4BAAA;EACA,uHAAA;ECnBF,oEAAA;EHkJA,mBAAA;CDkND;ACrND;;EEhII,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;ED2CF,wDAAA;EACQ,gDAAA;CF+ST;AC/ND;;EAYI,0CAAA;CDuNH;AClND;;;EAGE,iBAAA;CDoND;AC/LD;EAfI;;;IAGE,YAAA;IE7JF,yEAAA;IACA,oEAAA;IACA,8FAAA;IAAA,uEAAA;IACA,4BAAA;IACA,uHAAA;GH+WD;CACF;AC3MD;EACE,8CAAA;EC3HA,2FAAA;EACQ,mFAAA;CFyUT;ACnMD;EEtLI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF8KF,sBAAA;CD+MD;AC1MD;EEvLI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF8KF,sBAAA;CDuND;ACjND;EExLI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF8KF,sBAAA;CD+ND;ACxND;EEzLI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EF8KF,sBAAA;CDuOD;ACxND;EEjMI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CH4ZH;ACrND;EE3MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHmaH;AC3ND;EE5MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CH0aH;ACjOD;EE7MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHibH;ACvOD;EE9MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHwbH;AC7OD;EE/MI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CH+bH;AChPD;EElLI,8MAAA;EACA,yMAAA;EACA,sMAAA;CHqaH;AC5OD;EACE,mBAAA;EC9KA,mDAAA;EACQ,2CAAA;CF6ZT;AC7OD;;;EAGE,8BAAA;EEnOE,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EFiOF,sBAAA;CDmPD;ACxPD;;;EAQI,kBAAA;CDqPH;AC3OD;ECnME,kDAAA;EACQ,0CAAA;CFibT;ACrOD;EE5PI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHoeH;AC3OD;EE7PI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CH2eH;ACjPD;EE9PI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHkfH;ACvPD;EE/PI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHyfH;AC7PD;EEhQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHggBH;ACnQD;EEjQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;CHugBH;ACnQD;EExQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,4BAAA;EACA,uHAAA;EFsQF,sBAAA;EC3NA,0FAAA;EACQ,kFAAA;CFqeT", - "file": "bootstrap-theme.css", - "sourcesContent": [ - "/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-default.disabled,\n.btn-primary.disabled,\n.btn-success.disabled,\n.btn-info.disabled,\n.btn-warning.disabled,\n.btn-danger.disabled,\n.btn-default[disabled],\n.btn-primary[disabled],\n.btn-success[disabled],\n.btn-info[disabled],\n.btn-warning[disabled],\n.btn-danger[disabled],\nfieldset[disabled] .btn-default,\nfieldset[disabled] .btn-primary,\nfieldset[disabled] .btn-success,\nfieldset[disabled] .btn-info,\nfieldset[disabled] .btn-warning,\nfieldset[disabled] .btn-danger {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n text-shadow: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n}\n.btn-default {\n background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #dbdbdb;\n text-shadow: 0 1px 0 #fff;\n border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n background-color: #e0e0e0;\n background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n background-color: #e0e0e0;\n border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n background-color: #e0e0e0;\n background-image: none;\n}\n.btn-primary {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n background-color: #265a88;\n background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n background-color: #265a88;\n border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n background-color: #265a88;\n background-image: none;\n}\n.btn-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n background-color: #419641;\n background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n background-color: #419641;\n border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n background-color: #419641;\n background-image: none;\n}\n.btn-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n background-color: #2aabd2;\n background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n background-color: #2aabd2;\n border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n background-color: #2aabd2;\n background-image: none;\n}\n.btn-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n background-color: #eb9316;\n background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n background-color: #eb9316;\n border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n background-color: #eb9316;\n background-image: none;\n}\n.btn-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n background-color: #c12e2a;\n background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n background-color: #c12e2a;\n border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n background-color: #c12e2a;\n background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-color: #e8e8e8;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-color: #2e6da4;\n}\n.navbar-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n.navbar-inverse {\n background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);\n background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);\n background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n@media (max-width: 767px) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n }\n}\n.alert {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.alert-success {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n border-color: #b2dba1;\n}\n.alert-info {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n border-color: #9acfea;\n}\n.alert-warning {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n border-color: #f5e79e;\n}\n.alert-danger {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n border-color: #dca7a7;\n}\n.progress {\n background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n}\n.progress-bar {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n}\n.progress-bar-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n}\n.progress-bar-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n}\n.progress-bar-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n}\n.progress-bar-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n}\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.list-group {\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 #286090;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n text-shadow: none;\n}\n.panel {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.panel-default > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n}\n.panel-primary > .panel-heading {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n}\n.panel-success > .panel-heading {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n}\n.panel-info > .panel-heading {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n}\n.panel-warning > .panel-heading {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n}\n.panel-danger > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n}\n.well {\n background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n border-color: #dcdcdc;\n -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */", - "/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n .box-shadow(none);\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n border-radius: @navbar-border-radius;\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n", - "// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n", - "// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n", - "// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n" - ] -} \ No newline at end of file diff --git a/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap-theme.min.css b/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap-theme.min.css deleted file mode 100644 index 5e394019..00000000 --- a/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap-theme.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -/*# sourceMappingURL=bootstrap-theme.min.css.map */ \ No newline at end of file diff --git a/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap-theme.min.css.map b/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap-theme.min.css.map deleted file mode 100644 index c7cb37cb..00000000 --- a/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap-theme.min.css.map +++ /dev/null @@ -1,17 +0,0 @@ -{ - "version": 3, - "sources": [ - "less/theme.less", - "less/mixins/vendor-prefixes.less", - "less/mixins/gradients.less", - "less/mixins/reset-filter.less" - ], - "names": [], - "mappings": ";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA", - "sourcesContent": [ - "/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n .box-shadow(none);\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n border-radius: @navbar-border-radius;\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n", - "// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n", - "// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n", - "// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n" - ] -} \ No newline at end of file diff --git a/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap.css b/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap.css deleted file mode 100644 index 33b6ac76..00000000 --- a/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap.css +++ /dev/null @@ -1,8198 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ -/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ -html { - font-family: sans-serif; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} - -body { - margin: 0; -} - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -menu, -nav, -section, -summary { - display: block; -} - -audio, -canvas, -progress, -video { - display: inline-block; - vertical-align: baseline; -} - -audio:not([controls]) { - display: none; - height: 0; -} - -[hidden], -template { - display: none; -} - -a { - background-color: transparent; -} - -a:active, -a:hover { - outline: 0; -} - -abbr[title] { - border-bottom: 1px dotted; -} - -b, -strong { - font-weight: bold; -} - -dfn { - font-style: italic; -} - -h1 { - margin: .67em 0; - font-size: 2em; -} - -mark { - color: #000; - background: #ff0; -} - -small { - font-size: 80%; -} - -sub, -sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; -} - -sup { - top: -.5em; -} - -sub { - bottom: -.25em; -} - -img { - border: 0; -} - -svg:not(:root) { - overflow: hidden; -} - -figure { - margin: 1em 40px; -} - -hr { - height: 0; - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -pre { - overflow: auto; -} - -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} - -button, -input, -optgroup, -select, -textarea { - margin: 0; - font: inherit; - color: inherit; -} - -button { - overflow: visible; -} - -button, -select { - text-transform: none; -} - -button, -html input[type="button"], -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; - cursor: pointer; -} - -button[disabled], -html input[disabled] { - cursor: default; -} - -button::-moz-focus-inner, -input::-moz-focus-inner { - padding: 0; - border: 0; -} - -input { - line-height: normal; -} - -input[type="checkbox"], -input[type="radio"] { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - padding: 0; -} - -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -input[type="search"] { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; - -webkit-appearance: textfield; -} - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -fieldset { - padding: .35em .625em .75em; - margin: 0 2px; - border: 1px solid #c0c0c0; -} - -legend { - padding: 0; - border: 0; -} - -textarea { - overflow: auto; -} - -optgroup { - font-weight: bold; -} - -table { - border-spacing: 0; - border-collapse: collapse; -} - -td, -th { - padding: 0; -} - -/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ -@media print { - *, - *:before, - *:after { - color: #000 !important; - text-shadow: none !important; - background: transparent !important; - -webkit-box-shadow: none !important; - box-shadow: none !important; - } - - a, - a:visited { - text-decoration: underline; - } - - a[href]:after { - content: " (" attr(href) ")"; - } - - abbr[title]:after { - content: " (" attr(title) ")"; - } - - a[href^="#"]:after, - a[href^="javascript:"]:after { - content: ""; - } - - pre, - blockquote { - border: 1px solid #999; - - page-break-inside: avoid; - } - - thead { - display: table-header-group; - } - - tr, - img { - page-break-inside: avoid; - } - - img { - max-width: 100% !important; - } - - p, - h2, - h3 { - orphans: 3; - widows: 3; - } - - h2, - h3 { - page-break-after: avoid; - } - - .navbar { - display: none; - } - - .btn > .caret, - .dropup > .btn > .caret { - border-top-color: #000 !important; - } - - .label { - border: 1px solid #000; - } - - .table { - border-collapse: collapse !important; - } - - .table td, - .table th { - background-color: #fff !important; - } - - .table-bordered th, - .table-bordered td { - border: 1px solid #ddd !important; - } -} - -@font-face { - font-family: 'Glyphicons Halflings'; - - src: url('../fonts/glyphicons-halflings-regular.eot'); - src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); -} - -.glyphicon { - position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: normal; - line-height: 1; - - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.glyphicon-asterisk:before { - content: "\002a"; -} - -.glyphicon-plus:before { - content: "\002b"; -} - -.glyphicon-euro:before, -.glyphicon-eur:before { - content: "\20ac"; -} - -.glyphicon-minus:before { - content: "\2212"; -} - -.glyphicon-cloud:before { - content: "\2601"; -} - -.glyphicon-envelope:before { - content: "\2709"; -} - -.glyphicon-pencil:before { - content: "\270f"; -} - -.glyphicon-glass:before { - content: "\e001"; -} - -.glyphicon-music:before { - content: "\e002"; -} - -.glyphicon-search:before { - content: "\e003"; -} - -.glyphicon-heart:before { - content: "\e005"; -} - -.glyphicon-star:before { - content: "\e006"; -} - -.glyphicon-star-empty:before { - content: "\e007"; -} - -.glyphicon-user:before { - content: "\e008"; -} - -.glyphicon-film:before { - content: "\e009"; -} - -.glyphicon-th-large:before { - content: "\e010"; -} - -.glyphicon-th:before { - content: "\e011"; -} - -.glyphicon-th-list:before { - content: "\e012"; -} - -.glyphicon-ok:before { - content: "\e013"; -} - -.glyphicon-remove:before { - content: "\e014"; -} - -.glyphicon-zoom-in:before { - content: "\e015"; -} - -.glyphicon-zoom-out:before { - content: "\e016"; -} - -.glyphicon-off:before { - content: "\e017"; -} - -.glyphicon-signal:before { - content: "\e018"; -} - -.glyphicon-cog:before { - content: "\e019"; -} - -.glyphicon-trash:before { - content: "\e020"; -} - -.glyphicon-home:before { - content: "\e021"; -} - -.glyphicon-file:before { - content: "\e022"; -} - -.glyphicon-time:before { - content: "\e023"; -} - -.glyphicon-road:before { - content: "\e024"; -} - -.glyphicon-download-alt:before { - content: "\e025"; -} - -.glyphicon-download:before { - content: "\e026"; -} - -.glyphicon-upload:before { - content: "\e027"; -} - -.glyphicon-inbox:before { - content: "\e028"; -} - -.glyphicon-play-circle:before { - content: "\e029"; -} - -.glyphicon-repeat:before { - content: "\e030"; -} - -.glyphicon-refresh:before { - content: "\e031"; -} - -.glyphicon-list-alt:before { - content: "\e032"; -} - -.glyphicon-lock:before { - content: "\e033"; -} - -.glyphicon-flag:before { - content: "\e034"; -} - -.glyphicon-headphones:before { - content: "\e035"; -} - -.glyphicon-volume-off:before { - content: "\e036"; -} - -.glyphicon-volume-down:before { - content: "\e037"; -} - -.glyphicon-volume-up:before { - content: "\e038"; -} - -.glyphicon-qrcode:before { - content: "\e039"; -} - -.glyphicon-barcode:before { - content: "\e040"; -} - -.glyphicon-tag:before { - content: "\e041"; -} - -.glyphicon-tags:before { - content: "\e042"; -} - -.glyphicon-book:before { - content: "\e043"; -} - -.glyphicon-bookmark:before { - content: "\e044"; -} - -.glyphicon-print:before { - content: "\e045"; -} - -.glyphicon-camera:before { - content: "\e046"; -} - -.glyphicon-font:before { - content: "\e047"; -} - -.glyphicon-bold:before { - content: "\e048"; -} - -.glyphicon-italic:before { - content: "\e049"; -} - -.glyphicon-text-height:before { - content: "\e050"; -} - -.glyphicon-text-width:before { - content: "\e051"; -} - -.glyphicon-align-left:before { - content: "\e052"; -} - -.glyphicon-align-center:before { - content: "\e053"; -} - -.glyphicon-align-right:before { - content: "\e054"; -} - -.glyphicon-align-justify:before { - content: "\e055"; -} - -.glyphicon-list:before { - content: "\e056"; -} - -.glyphicon-indent-left:before { - content: "\e057"; -} - -.glyphicon-indent-right:before { - content: "\e058"; -} - -.glyphicon-facetime-video:before { - content: "\e059"; -} - -.glyphicon-picture:before { - content: "\e060"; -} - -.glyphicon-map-marker:before { - content: "\e062"; -} - -.glyphicon-adjust:before { - content: "\e063"; -} - -.glyphicon-tint:before { - content: "\e064"; -} - -.glyphicon-edit:before { - content: "\e065"; -} - -.glyphicon-share:before { - content: "\e066"; -} - -.glyphicon-check:before { - content: "\e067"; -} - -.glyphicon-move:before { - content: "\e068"; -} - -.glyphicon-step-backward:before { - content: "\e069"; -} - -.glyphicon-fast-backward:before { - content: "\e070"; -} - -.glyphicon-backward:before { - content: "\e071"; -} - -.glyphicon-play:before { - content: "\e072"; -} - -.glyphicon-pause:before { - content: "\e073"; -} - -.glyphicon-stop:before { - content: "\e074"; -} - -.glyphicon-forward:before { - content: "\e075"; -} - -.glyphicon-fast-forward:before { - content: "\e076"; -} - -.glyphicon-step-forward:before { - content: "\e077"; -} - -.glyphicon-eject:before { - content: "\e078"; -} - -.glyphicon-chevron-left:before { - content: "\e079"; -} - -.glyphicon-chevron-right:before { - content: "\e080"; -} - -.glyphicon-plus-sign:before { - content: "\e081"; -} - -.glyphicon-minus-sign:before { - content: "\e082"; -} - -.glyphicon-remove-sign:before { - content: "\e083"; -} - -.glyphicon-ok-sign:before { - content: "\e084"; -} - -.glyphicon-question-sign:before { - content: "\e085"; -} - -.glyphicon-info-sign:before { - content: "\e086"; -} - -.glyphicon-screenshot:before { - content: "\e087"; -} - -.glyphicon-remove-circle:before { - content: "\e088"; -} - -.glyphicon-ok-circle:before { - content: "\e089"; -} - -.glyphicon-ban-circle:before { - content: "\e090"; -} - -.glyphicon-arrow-left:before { - content: "\e091"; -} - -.glyphicon-arrow-right:before { - content: "\e092"; -} - -.glyphicon-arrow-up:before { - content: "\e093"; -} - -.glyphicon-arrow-down:before { - content: "\e094"; -} - -.glyphicon-share-alt:before { - content: "\e095"; -} - -.glyphicon-resize-full:before { - content: "\e096"; -} - -.glyphicon-resize-small:before { - content: "\e097"; -} - -.glyphicon-exclamation-sign:before { - content: "\e101"; -} - -.glyphicon-gift:before { - content: "\e102"; -} - -.glyphicon-leaf:before { - content: "\e103"; -} - -.glyphicon-fire:before { - content: "\e104"; -} - -.glyphicon-eye-open:before { - content: "\e105"; -} - -.glyphicon-eye-close:before { - content: "\e106"; -} - -.glyphicon-warning-sign:before { - content: "\e107"; -} - -.glyphicon-plane:before { - content: "\e108"; -} - -.glyphicon-calendar:before { - content: "\e109"; -} - -.glyphicon-random:before { - content: "\e110"; -} - -.glyphicon-comment:before { - content: "\e111"; -} - -.glyphicon-magnet:before { - content: "\e112"; -} - -.glyphicon-chevron-up:before { - content: "\e113"; -} - -.glyphicon-chevron-down:before { - content: "\e114"; -} - -.glyphicon-retweet:before { - content: "\e115"; -} - -.glyphicon-shopping-cart:before { - content: "\e116"; -} - -.glyphicon-folder-close:before { - content: "\e117"; -} - -.glyphicon-folder-open:before { - content: "\e118"; -} - -.glyphicon-resize-vertical:before { - content: "\e119"; -} - -.glyphicon-resize-horizontal:before { - content: "\e120"; -} - -.glyphicon-hdd:before { - content: "\e121"; -} - -.glyphicon-bullhorn:before { - content: "\e122"; -} - -.glyphicon-bell:before { - content: "\e123"; -} - -.glyphicon-certificate:before { - content: "\e124"; -} - -.glyphicon-thumbs-up:before { - content: "\e125"; -} - -.glyphicon-thumbs-down:before { - content: "\e126"; -} - -.glyphicon-hand-right:before { - content: "\e127"; -} - -.glyphicon-hand-left:before { - content: "\e128"; -} - -.glyphicon-hand-up:before { - content: "\e129"; -} - -.glyphicon-hand-down:before { - content: "\e130"; -} - -.glyphicon-circle-arrow-right:before { - content: "\e131"; -} - -.glyphicon-circle-arrow-left:before { - content: "\e132"; -} - -.glyphicon-circle-arrow-up:before { - content: "\e133"; -} - -.glyphicon-circle-arrow-down:before { - content: "\e134"; -} - -.glyphicon-globe:before { - content: "\e135"; -} - -.glyphicon-wrench:before { - content: "\e136"; -} - -.glyphicon-tasks:before { - content: "\e137"; -} - -.glyphicon-filter:before { - content: "\e138"; -} - -.glyphicon-briefcase:before { - content: "\e139"; -} - -.glyphicon-fullscreen:before { - content: "\e140"; -} - -.glyphicon-dashboard:before { - content: "\e141"; -} - -.glyphicon-paperclip:before { - content: "\e142"; -} - -.glyphicon-heart-empty:before { - content: "\e143"; -} - -.glyphicon-link:before { - content: "\e144"; -} - -.glyphicon-phone:before { - content: "\e145"; -} - -.glyphicon-pushpin:before { - content: "\e146"; -} - -.glyphicon-usd:before { - content: "\e148"; -} - -.glyphicon-gbp:before { - content: "\e149"; -} - -.glyphicon-sort:before { - content: "\e150"; -} - -.glyphicon-sort-by-alphabet:before { - content: "\e151"; -} - -.glyphicon-sort-by-alphabet-alt:before { - content: "\e152"; -} - -.glyphicon-sort-by-order:before { - content: "\e153"; -} - -.glyphicon-sort-by-order-alt:before { - content: "\e154"; -} - -.glyphicon-sort-by-attributes:before { - content: "\e155"; -} - -.glyphicon-sort-by-attributes-alt:before { - content: "\e156"; -} - -.glyphicon-unchecked:before { - content: "\e157"; -} - -.glyphicon-expand:before { - content: "\e158"; -} - -.glyphicon-collapse-down:before { - content: "\e159"; -} - -.glyphicon-collapse-up:before { - content: "\e160"; -} - -.glyphicon-log-in:before { - content: "\e161"; -} - -.glyphicon-flash:before { - content: "\e162"; -} - -.glyphicon-log-out:before { - content: "\e163"; -} - -.glyphicon-new-window:before { - content: "\e164"; -} - -.glyphicon-record:before { - content: "\e165"; -} - -.glyphicon-save:before { - content: "\e166"; -} - -.glyphicon-open:before { - content: "\e167"; -} - -.glyphicon-saved:before { - content: "\e168"; -} - -.glyphicon-import:before { - content: "\e169"; -} - -.glyphicon-export:before { - content: "\e170"; -} - -.glyphicon-send:before { - content: "\e171"; -} - -.glyphicon-floppy-disk:before { - content: "\e172"; -} - -.glyphicon-floppy-saved:before { - content: "\e173"; -} - -.glyphicon-floppy-remove:before { - content: "\e174"; -} - -.glyphicon-floppy-save:before { - content: "\e175"; -} - -.glyphicon-floppy-open:before { - content: "\e176"; -} - -.glyphicon-credit-card:before { - content: "\e177"; -} - -.glyphicon-transfer:before { - content: "\e178"; -} - -.glyphicon-cutlery:before { - content: "\e179"; -} - -.glyphicon-header:before { - content: "\e180"; -} - -.glyphicon-compressed:before { - content: "\e181"; -} - -.glyphicon-earphone:before { - content: "\e182"; -} - -.glyphicon-phone-alt:before { - content: "\e183"; -} - -.glyphicon-tower:before { - content: "\e184"; -} - -.glyphicon-stats:before { - content: "\e185"; -} - -.glyphicon-sd-video:before { - content: "\e186"; -} - -.glyphicon-hd-video:before { - content: "\e187"; -} - -.glyphicon-subtitles:before { - content: "\e188"; -} - -.glyphicon-sound-stereo:before { - content: "\e189"; -} - -.glyphicon-sound-dolby:before { - content: "\e190"; -} - -.glyphicon-sound-5-1:before { - content: "\e191"; -} - -.glyphicon-sound-6-1:before { - content: "\e192"; -} - -.glyphicon-sound-7-1:before { - content: "\e193"; -} - -.glyphicon-copyright-mark:before { - content: "\e194"; -} - -.glyphicon-registration-mark:before { - content: "\e195"; -} - -.glyphicon-cloud-download:before { - content: "\e197"; -} - -.glyphicon-cloud-upload:before { - content: "\e198"; -} - -.glyphicon-tree-conifer:before { - content: "\e199"; -} - -.glyphicon-tree-deciduous:before { - content: "\e200"; -} - -.glyphicon-cd:before { - content: "\e201"; -} - -.glyphicon-save-file:before { - content: "\e202"; -} - -.glyphicon-open-file:before { - content: "\e203"; -} - -.glyphicon-level-up:before { - content: "\e204"; -} - -.glyphicon-copy:before { - content: "\e205"; -} - -.glyphicon-paste:before { - content: "\e206"; -} - -.glyphicon-alert:before { - content: "\e209"; -} - -.glyphicon-equalizer:before { - content: "\e210"; -} - -.glyphicon-king:before { - content: "\e211"; -} - -.glyphicon-queen:before { - content: "\e212"; -} - -.glyphicon-pawn:before { - content: "\e213"; -} - -.glyphicon-bishop:before { - content: "\e214"; -} - -.glyphicon-knight:before { - content: "\e215"; -} - -.glyphicon-baby-formula:before { - content: "\e216"; -} - -.glyphicon-tent:before { - content: "\26fa"; -} - -.glyphicon-blackboard:before { - content: "\e218"; -} - -.glyphicon-bed:before { - content: "\e219"; -} - -.glyphicon-apple:before { - content: "\f8ff"; -} - -.glyphicon-erase:before { - content: "\e221"; -} - -.glyphicon-hourglass:before { - content: "\231b"; -} - -.glyphicon-lamp:before { - content: "\e223"; -} - -.glyphicon-duplicate:before { - content: "\e224"; -} - -.glyphicon-piggy-bank:before { - content: "\e225"; -} - -.glyphicon-scissors:before { - content: "\e226"; -} - -.glyphicon-bitcoin:before { - content: "\e227"; -} - -.glyphicon-btc:before { - content: "\e227"; -} - -.glyphicon-xbt:before { - content: "\e227"; -} - -.glyphicon-yen:before { - content: "\00a5"; -} - -.glyphicon-jpy:before { - content: "\00a5"; -} - -.glyphicon-ruble:before { - content: "\20bd"; -} - -.glyphicon-rub:before { - content: "\20bd"; -} - -.glyphicon-scale:before { - content: "\e230"; -} - -.glyphicon-ice-lolly:before { - content: "\e231"; -} - -.glyphicon-ice-lolly-tasted:before { - content: "\e232"; -} - -.glyphicon-education:before { - content: "\e233"; -} - -.glyphicon-option-horizontal:before { - content: "\e234"; -} - -.glyphicon-option-vertical:before { - content: "\e235"; -} - -.glyphicon-menu-hamburger:before { - content: "\e236"; -} - -.glyphicon-modal-window:before { - content: "\e237"; -} - -.glyphicon-oil:before { - content: "\e238"; -} - -.glyphicon-grain:before { - content: "\e239"; -} - -.glyphicon-sunglasses:before { - content: "\e240"; -} - -.glyphicon-text-size:before { - content: "\e241"; -} - -.glyphicon-text-color:before { - content: "\e242"; -} - -.glyphicon-text-background:before { - content: "\e243"; -} - -.glyphicon-object-align-top:before { - content: "\e244"; -} - -.glyphicon-object-align-bottom:before { - content: "\e245"; -} - -.glyphicon-object-align-horizontal:before { - content: "\e246"; -} - -.glyphicon-object-align-left:before { - content: "\e247"; -} - -.glyphicon-object-align-vertical:before { - content: "\e248"; -} - -.glyphicon-object-align-right:before { - content: "\e249"; -} - -.glyphicon-triangle-right:before { - content: "\e250"; -} - -.glyphicon-triangle-left:before { - content: "\e251"; -} - -.glyphicon-triangle-bottom:before { - content: "\e252"; -} - -.glyphicon-triangle-top:before { - content: "\e253"; -} - -.glyphicon-console:before { - content: "\e254"; -} - -.glyphicon-superscript:before { - content: "\e255"; -} - -.glyphicon-subscript:before { - content: "\e256"; -} - -.glyphicon-menu-left:before { - content: "\e257"; -} - -.glyphicon-menu-right:before { - content: "\e258"; -} - -.glyphicon-menu-down:before { - content: "\e259"; -} - -.glyphicon-menu-up:before { - content: "\e260"; -} - -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -*:before, -*:after { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -html { - font-size: 10px; - - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -} - -body { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.42857143; - color: #333; - background-color: #fff; -} - -input, -button, -select, -textarea { - font-family: inherit; - font-size: inherit; - line-height: inherit; -} - -a { - color: #337ab7; - text-decoration: none; -} - -a:hover, -a:focus { - color: #23527c; - text-decoration: underline; -} - -a:focus { - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -figure { - margin: 0; -} - -img { - vertical-align: middle; -} - -.img-responsive, -.thumbnail > img, -.thumbnail a > img, -.carousel-inner > .item > img, -.carousel-inner > .item > a > img { - display: block; - max-width: 100%; - height: auto; -} - -.img-rounded { - border-radius: 6px; -} - -.img-thumbnail { - display: inline-block; - max-width: 100%; - height: auto; - padding: 4px; - line-height: 1.42857143; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 4px; - -webkit-transition: all .2s ease-in-out; - -o-transition: all .2s ease-in-out; - transition: all .2s ease-in-out; -} - -.img-circle { - border-radius: 50%; -} - -hr { - margin-top: 20px; - margin-bottom: 20px; - border: 0; - border-top: 1px solid #eee; -} - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - border: 0; -} - -.sr-only-focusable:active, -.sr-only-focusable:focus { - position: static; - width: auto; - height: auto; - margin: 0; - overflow: visible; - clip: auto; -} - -[role="button"] { - cursor: pointer; -} - -h1, -h2, -h3, -h4, -h5, -h6, -.h1, -.h2, -.h3, -.h4, -.h5, -.h6 { - font-family: inherit; - font-weight: 500; - line-height: 1.1; - color: inherit; -} - -h1 small, -h2 small, -h3 small, -h4 small, -h5 small, -h6 small, -.h1 small, -.h2 small, -.h3 small, -.h4 small, -.h5 small, -.h6 small, -h1 .small, -h2 .small, -h3 .small, -h4 .small, -h5 .small, -h6 .small, -.h1 .small, -.h2 .small, -.h3 .small, -.h4 .small, -.h5 .small, -.h6 .small { - font-weight: normal; - line-height: 1; - color: #777; -} - -h1, -.h1, -h2, -.h2, -h3, -.h3 { - margin-top: 20px; - margin-bottom: 10px; -} - -h1 small, -.h1 small, -h2 small, -.h2 small, -h3 small, -.h3 small, -h1 .small, -.h1 .small, -h2 .small, -.h2 .small, -h3 .small, -.h3 .small { - font-size: 65%; -} - -h4, -.h4, -h5, -.h5, -h6, -.h6 { - margin-top: 10px; - margin-bottom: 10px; -} - -h4 small, -.h4 small, -h5 small, -.h5 small, -h6 small, -.h6 small, -h4 .small, -.h4 .small, -h5 .small, -.h5 .small, -h6 .small, -.h6 .small { - font-size: 75%; -} - -h1, -.h1 { - font-size: 36px; -} - -h2, -.h2 { - font-size: 30px; -} - -h3, -.h3 { - font-size: 24px; -} - -h4, -.h4 { - font-size: 18px; -} - -h5, -.h5 { - font-size: 14px; -} - -h6, -.h6 { - font-size: 12px; -} - -p { - margin: 0 0 10px; -} - -.lead { - margin-bottom: 20px; - font-size: 16px; - font-weight: 300; - line-height: 1.4; -} - -@media (min-width: 768px) { - .lead { - font-size: 21px; - } -} - -small, -.small { - font-size: 85%; -} - -mark, -.mark { - padding: .2em; - background-color: #fcf8e3; -} - -.text-left { - text-align: left; -} - -.text-right { - text-align: right; -} - -.text-center { - text-align: center; -} - -.text-justify { - text-align: justify; -} - -.text-nowrap { - white-space: nowrap; -} - -.text-lowercase { - text-transform: lowercase; -} - -.text-uppercase { - text-transform: uppercase; -} - -.text-capitalize { - text-transform: capitalize; -} - -.text-muted { - color: #777; -} - -.text-primary { - color: #337ab7; -} - -a.text-primary:hover, -a.text-primary:focus { - color: #286090; -} - -.text-success { - color: #3c763d; -} - -a.text-success:hover, -a.text-success:focus { - color: #2b542c; -} - -.text-info { - color: #31708f; -} - -a.text-info:hover, -a.text-info:focus { - color: #245269; -} - -.text-warning { - color: #8a6d3b; -} - -a.text-warning:hover, -a.text-warning:focus { - color: #66512c; -} - -.text-danger { - color: #a94442; -} - -a.text-danger:hover, -a.text-danger:focus { - color: #843534; -} - -.bg-primary { - color: #fff; - background-color: #337ab7; -} - -a.bg-primary:hover, -a.bg-primary:focus { - background-color: #286090; -} - -.bg-success { - background-color: #dff0d8; -} - -a.bg-success:hover, -a.bg-success:focus { - background-color: #c1e2b3; -} - -.bg-info { - background-color: #d9edf7; -} - -a.bg-info:hover, -a.bg-info:focus { - background-color: #afd9ee; -} - -.bg-warning { - background-color: #fcf8e3; -} - -a.bg-warning:hover, -a.bg-warning:focus { - background-color: #f7ecb5; -} - -.bg-danger { - background-color: #f2dede; -} - -a.bg-danger:hover, -a.bg-danger:focus { - background-color: #e4b9b9; -} - -.page-header { - padding-bottom: 9px; - margin: 40px 0 20px; - border-bottom: 1px solid #eee; -} - -ul, -ol { - margin-top: 0; - margin-bottom: 10px; -} - -ul ul, -ol ul, -ul ol, -ol ol { - margin-bottom: 0; -} - -.list-unstyled { - padding-left: 0; - list-style: none; -} - -.list-inline { - padding-left: 0; - margin-left: -5px; - list-style: none; -} - -.list-inline > li { - display: inline-block; - padding-right: 5px; - padding-left: 5px; -} - -dl { - margin-top: 0; - margin-bottom: 20px; -} - -dt, -dd { - line-height: 1.42857143; -} - -dt { - font-weight: bold; -} - -dd { - margin-left: 0; -} - -@media (min-width: 768px) { - .dl-horizontal dt { - float: left; - width: 160px; - overflow: hidden; - clear: left; - text-align: right; - text-overflow: ellipsis; - white-space: nowrap; - } - - .dl-horizontal dd { - margin-left: 180px; - } -} - -abbr[title], -abbr[data-original-title] { - cursor: help; - border-bottom: 1px dotted #777; -} - -.initialism { - font-size: 90%; - text-transform: uppercase; -} - -blockquote { - padding: 10px 20px; - margin: 0 0 20px; - font-size: 17.5px; - border-left: 5px solid #eee; -} - -blockquote p:last-child, -blockquote ul:last-child, -blockquote ol:last-child { - margin-bottom: 0; -} - -blockquote footer, -blockquote small, -blockquote .small { - display: block; - font-size: 80%; - line-height: 1.42857143; - color: #777; -} - -blockquote footer:before, -blockquote small:before, -blockquote .small:before { - content: '\2014 \00A0'; -} - -.blockquote-reverse, -blockquote.pull-right { - padding-right: 15px; - padding-left: 0; - text-align: right; - border-right: 5px solid #eee; - border-left: 0; -} - -.blockquote-reverse footer:before, -blockquote.pull-right footer:before, -.blockquote-reverse small:before, -blockquote.pull-right small:before, -.blockquote-reverse .small:before, -blockquote.pull-right .small:before { - content: ''; -} - -.blockquote-reverse footer:after, -blockquote.pull-right footer:after, -.blockquote-reverse small:after, -blockquote.pull-right small:after, -.blockquote-reverse .small:after, -blockquote.pull-right .small:after { - content: '\00A0 \2014'; -} - -address { - margin-bottom: 20px; - font-style: normal; - line-height: 1.42857143; -} - -code, -kbd, -pre, -samp { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; -} - -code { - padding: 2px 4px; - font-size: 90%; - color: #c7254e; - background-color: #f9f2f4; - border-radius: 4px; -} - -kbd { - padding: 2px 4px; - font-size: 90%; - color: #fff; - background-color: #333; - border-radius: 3px; - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); -} - -kbd kbd { - padding: 0; - font-size: 100%; - font-weight: bold; - -webkit-box-shadow: none; - box-shadow: none; -} - -pre { - display: block; - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - line-height: 1.42857143; - color: #333; - word-break: break-all; - word-wrap: break-word; - background-color: #f5f5f5; - border: 1px solid #ccc; - border-radius: 4px; -} - -pre code { - padding: 0; - font-size: inherit; - color: inherit; - white-space: pre-wrap; - background-color: transparent; - border-radius: 0; -} - -.pre-scrollable { - max-height: 340px; - overflow-y: scroll; -} - -.container { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} - -@media (min-width: 768px) { - .container { - width: 750px; - } -} - -@media (min-width: 992px) { - .container { - width: 970px; - } -} - -@media (min-width: 1200px) { - .container { - width: 1170px; - } -} - -.container-fluid { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} - -.row { - margin-right: -15px; - margin-left: -15px; -} - -.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { - position: relative; - min-height: 1px; - padding-right: 15px; - padding-left: 15px; -} - -.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { - float: left; -} - -.col-xs-12 { - width: 100%; -} - -.col-xs-11 { - width: 91.66666667%; -} - -.col-xs-10 { - width: 83.33333333%; -} - -.col-xs-9 { - width: 75%; -} - -.col-xs-8 { - width: 66.66666667%; -} - -.col-xs-7 { - width: 58.33333333%; -} - -.col-xs-6 { - width: 50%; -} - -.col-xs-5 { - width: 41.66666667%; -} - -.col-xs-4 { - width: 33.33333333%; -} - -.col-xs-3 { - width: 25%; -} - -.col-xs-2 { - width: 16.66666667%; -} - -.col-xs-1 { - width: 8.33333333%; -} - -.col-xs-pull-12 { - right: 100%; -} - -.col-xs-pull-11 { - right: 91.66666667%; -} - -.col-xs-pull-10 { - right: 83.33333333%; -} - -.col-xs-pull-9 { - right: 75%; -} - -.col-xs-pull-8 { - right: 66.66666667%; -} - -.col-xs-pull-7 { - right: 58.33333333%; -} - -.col-xs-pull-6 { - right: 50%; -} - -.col-xs-pull-5 { - right: 41.66666667%; -} - -.col-xs-pull-4 { - right: 33.33333333%; -} - -.col-xs-pull-3 { - right: 25%; -} - -.col-xs-pull-2 { - right: 16.66666667%; -} - -.col-xs-pull-1 { - right: 8.33333333%; -} - -.col-xs-pull-0 { - right: auto; -} - -.col-xs-push-12 { - left: 100%; -} - -.col-xs-push-11 { - left: 91.66666667%; -} - -.col-xs-push-10 { - left: 83.33333333%; -} - -.col-xs-push-9 { - left: 75%; -} - -.col-xs-push-8 { - left: 66.66666667%; -} - -.col-xs-push-7 { - left: 58.33333333%; -} - -.col-xs-push-6 { - left: 50%; -} - -.col-xs-push-5 { - left: 41.66666667%; -} - -.col-xs-push-4 { - left: 33.33333333%; -} - -.col-xs-push-3 { - left: 25%; -} - -.col-xs-push-2 { - left: 16.66666667%; -} - -.col-xs-push-1 { - left: 8.33333333%; -} - -.col-xs-push-0 { - left: auto; -} - -.col-xs-offset-12 { - margin-left: 100%; -} - -.col-xs-offset-11 { - margin-left: 91.66666667%; -} - -.col-xs-offset-10 { - margin-left: 83.33333333%; -} - -.col-xs-offset-9 { - margin-left: 75%; -} - -.col-xs-offset-8 { - margin-left: 66.66666667%; -} - -.col-xs-offset-7 { - margin-left: 58.33333333%; -} - -.col-xs-offset-6 { - margin-left: 50%; -} - -.col-xs-offset-5 { - margin-left: 41.66666667%; -} - -.col-xs-offset-4 { - margin-left: 33.33333333%; -} - -.col-xs-offset-3 { - margin-left: 25%; -} - -.col-xs-offset-2 { - margin-left: 16.66666667%; -} - -.col-xs-offset-1 { - margin-left: 8.33333333%; -} - -.col-xs-offset-0 { - margin-left: 0; -} - -@media (min-width: 768px) { - .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { - float: left; - } - - .col-sm-12 { - width: 100%; - } - - .col-sm-11 { - width: 91.66666667%; - } - - .col-sm-10 { - width: 83.33333333%; - } - - .col-sm-9 { - width: 75%; - } - - .col-sm-8 { - width: 66.66666667%; - } - - .col-sm-7 { - width: 58.33333333%; - } - - .col-sm-6 { - width: 50%; - } - - .col-sm-5 { - width: 41.66666667%; - } - - .col-sm-4 { - width: 33.33333333%; - } - - .col-sm-3 { - width: 25%; - } - - .col-sm-2 { - width: 16.66666667%; - } - - .col-sm-1 { - width: 8.33333333%; - } - - .col-sm-pull-12 { - right: 100%; - } - - .col-sm-pull-11 { - right: 91.66666667%; - } - - .col-sm-pull-10 { - right: 83.33333333%; - } - - .col-sm-pull-9 { - right: 75%; - } - - .col-sm-pull-8 { - right: 66.66666667%; - } - - .col-sm-pull-7 { - right: 58.33333333%; - } - - .col-sm-pull-6 { - right: 50%; - } - - .col-sm-pull-5 { - right: 41.66666667%; - } - - .col-sm-pull-4 { - right: 33.33333333%; - } - - .col-sm-pull-3 { - right: 25%; - } - - .col-sm-pull-2 { - right: 16.66666667%; - } - - .col-sm-pull-1 { - right: 8.33333333%; - } - - .col-sm-pull-0 { - right: auto; - } - - .col-sm-push-12 { - left: 100%; - } - - .col-sm-push-11 { - left: 91.66666667%; - } - - .col-sm-push-10 { - left: 83.33333333%; - } - - .col-sm-push-9 { - left: 75%; - } - - .col-sm-push-8 { - left: 66.66666667%; - } - - .col-sm-push-7 { - left: 58.33333333%; - } - - .col-sm-push-6 { - left: 50%; - } - - .col-sm-push-5 { - left: 41.66666667%; - } - - .col-sm-push-4 { - left: 33.33333333%; - } - - .col-sm-push-3 { - left: 25%; - } - - .col-sm-push-2 { - left: 16.66666667%; - } - - .col-sm-push-1 { - left: 8.33333333%; - } - - .col-sm-push-0 { - left: auto; - } - - .col-sm-offset-12 { - margin-left: 100%; - } - - .col-sm-offset-11 { - margin-left: 91.66666667%; - } - - .col-sm-offset-10 { - margin-left: 83.33333333%; - } - - .col-sm-offset-9 { - margin-left: 75%; - } - - .col-sm-offset-8 { - margin-left: 66.66666667%; - } - - .col-sm-offset-7 { - margin-left: 58.33333333%; - } - - .col-sm-offset-6 { - margin-left: 50%; - } - - .col-sm-offset-5 { - margin-left: 41.66666667%; - } - - .col-sm-offset-4 { - margin-left: 33.33333333%; - } - - .col-sm-offset-3 { - margin-left: 25%; - } - - .col-sm-offset-2 { - margin-left: 16.66666667%; - } - - .col-sm-offset-1 { - margin-left: 8.33333333%; - } - - .col-sm-offset-0 { - margin-left: 0; - } -} - -@media (min-width: 992px) { - .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { - float: left; - } - - .col-md-12 { - width: 100%; - } - - .col-md-11 { - width: 91.66666667%; - } - - .col-md-10 { - width: 83.33333333%; - } - - .col-md-9 { - width: 75%; - } - - .col-md-8 { - width: 66.66666667%; - } - - .col-md-7 { - width: 58.33333333%; - } - - .col-md-6 { - width: 50%; - } - - .col-md-5 { - width: 41.66666667%; - } - - .col-md-4 { - width: 33.33333333%; - } - - .col-md-3 { - width: 25%; - } - - .col-md-2 { - width: 16.66666667%; - } - - .col-md-1 { - width: 8.33333333%; - } - - .col-md-pull-12 { - right: 100%; - } - - .col-md-pull-11 { - right: 91.66666667%; - } - - .col-md-pull-10 { - right: 83.33333333%; - } - - .col-md-pull-9 { - right: 75%; - } - - .col-md-pull-8 { - right: 66.66666667%; - } - - .col-md-pull-7 { - right: 58.33333333%; - } - - .col-md-pull-6 { - right: 50%; - } - - .col-md-pull-5 { - right: 41.66666667%; - } - - .col-md-pull-4 { - right: 33.33333333%; - } - - .col-md-pull-3 { - right: 25%; - } - - .col-md-pull-2 { - right: 16.66666667%; - } - - .col-md-pull-1 { - right: 8.33333333%; - } - - .col-md-pull-0 { - right: auto; - } - - .col-md-push-12 { - left: 100%; - } - - .col-md-push-11 { - left: 91.66666667%; - } - - .col-md-push-10 { - left: 83.33333333%; - } - - .col-md-push-9 { - left: 75%; - } - - .col-md-push-8 { - left: 66.66666667%; - } - - .col-md-push-7 { - left: 58.33333333%; - } - - .col-md-push-6 { - left: 50%; - } - - .col-md-push-5 { - left: 41.66666667%; - } - - .col-md-push-4 { - left: 33.33333333%; - } - - .col-md-push-3 { - left: 25%; - } - - .col-md-push-2 { - left: 16.66666667%; - } - - .col-md-push-1 { - left: 8.33333333%; - } - - .col-md-push-0 { - left: auto; - } - - .col-md-offset-12 { - margin-left: 100%; - } - - .col-md-offset-11 { - margin-left: 91.66666667%; - } - - .col-md-offset-10 { - margin-left: 83.33333333%; - } - - .col-md-offset-9 { - margin-left: 75%; - } - - .col-md-offset-8 { - margin-left: 66.66666667%; - } - - .col-md-offset-7 { - margin-left: 58.33333333%; - } - - .col-md-offset-6 { - margin-left: 50%; - } - - .col-md-offset-5 { - margin-left: 41.66666667%; - } - - .col-md-offset-4 { - margin-left: 33.33333333%; - } - - .col-md-offset-3 { - margin-left: 25%; - } - - .col-md-offset-2 { - margin-left: 16.66666667%; - } - - .col-md-offset-1 { - margin-left: 8.33333333%; - } - - .col-md-offset-0 { - margin-left: 0; - } -} - -@media (min-width: 1200px) { - .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { - float: left; - } - - .col-lg-12 { - width: 100%; - } - - .col-lg-11 { - width: 91.66666667%; - } - - .col-lg-10 { - width: 83.33333333%; - } - - .col-lg-9 { - width: 75%; - } - - .col-lg-8 { - width: 66.66666667%; - } - - .col-lg-7 { - width: 58.33333333%; - } - - .col-lg-6 { - width: 50%; - } - - .col-lg-5 { - width: 41.66666667%; - } - - .col-lg-4 { - width: 33.33333333%; - } - - .col-lg-3 { - width: 25%; - } - - .col-lg-2 { - width: 16.66666667%; - } - - .col-lg-1 { - width: 8.33333333%; - } - - .col-lg-pull-12 { - right: 100%; - } - - .col-lg-pull-11 { - right: 91.66666667%; - } - - .col-lg-pull-10 { - right: 83.33333333%; - } - - .col-lg-pull-9 { - right: 75%; - } - - .col-lg-pull-8 { - right: 66.66666667%; - } - - .col-lg-pull-7 { - right: 58.33333333%; - } - - .col-lg-pull-6 { - right: 50%; - } - - .col-lg-pull-5 { - right: 41.66666667%; - } - - .col-lg-pull-4 { - right: 33.33333333%; - } - - .col-lg-pull-3 { - right: 25%; - } - - .col-lg-pull-2 { - right: 16.66666667%; - } - - .col-lg-pull-1 { - right: 8.33333333%; - } - - .col-lg-pull-0 { - right: auto; - } - - .col-lg-push-12 { - left: 100%; - } - - .col-lg-push-11 { - left: 91.66666667%; - } - - .col-lg-push-10 { - left: 83.33333333%; - } - - .col-lg-push-9 { - left: 75%; - } - - .col-lg-push-8 { - left: 66.66666667%; - } - - .col-lg-push-7 { - left: 58.33333333%; - } - - .col-lg-push-6 { - left: 50%; - } - - .col-lg-push-5 { - left: 41.66666667%; - } - - .col-lg-push-4 { - left: 33.33333333%; - } - - .col-lg-push-3 { - left: 25%; - } - - .col-lg-push-2 { - left: 16.66666667%; - } - - .col-lg-push-1 { - left: 8.33333333%; - } - - .col-lg-push-0 { - left: auto; - } - - .col-lg-offset-12 { - margin-left: 100%; - } - - .col-lg-offset-11 { - margin-left: 91.66666667%; - } - - .col-lg-offset-10 { - margin-left: 83.33333333%; - } - - .col-lg-offset-9 { - margin-left: 75%; - } - - .col-lg-offset-8 { - margin-left: 66.66666667%; - } - - .col-lg-offset-7 { - margin-left: 58.33333333%; - } - - .col-lg-offset-6 { - margin-left: 50%; - } - - .col-lg-offset-5 { - margin-left: 41.66666667%; - } - - .col-lg-offset-4 { - margin-left: 33.33333333%; - } - - .col-lg-offset-3 { - margin-left: 25%; - } - - .col-lg-offset-2 { - margin-left: 16.66666667%; - } - - .col-lg-offset-1 { - margin-left: 8.33333333%; - } - - .col-lg-offset-0 { - margin-left: 0; - } -} - -table { - background-color: transparent; -} - -caption { - padding-top: 8px; - padding-bottom: 8px; - color: #777; - text-align: left; -} - -th { - text-align: left; -} - -.table { - width: 100%; - max-width: 100%; - margin-bottom: 20px; -} - -.table > thead > tr > th, -.table > tbody > tr > th, -.table > tfoot > tr > th, -.table > thead > tr > td, -.table > tbody > tr > td, -.table > tfoot > tr > td { - padding: 8px; - line-height: 1.42857143; - vertical-align: top; - border-top: 1px solid #ddd; -} - -.table > thead > tr > th { - vertical-align: bottom; - border-bottom: 2px solid #ddd; -} - -.table > caption + thead > tr:first-child > th, -.table > colgroup + thead > tr:first-child > th, -.table > thead:first-child > tr:first-child > th, -.table > caption + thead > tr:first-child > td, -.table > colgroup + thead > tr:first-child > td, -.table > thead:first-child > tr:first-child > td { - border-top: 0; -} - -.table > tbody + tbody { - border-top: 2px solid #ddd; -} - -.table .table { - background-color: #fff; -} - -.table-condensed > thead > tr > th, -.table-condensed > tbody > tr > th, -.table-condensed > tfoot > tr > th, -.table-condensed > thead > tr > td, -.table-condensed > tbody > tr > td, -.table-condensed > tfoot > tr > td { - padding: 5px; -} - -.table-bordered { - border: 1px solid #ddd; -} - -.table-bordered > thead > tr > th, -.table-bordered > tbody > tr > th, -.table-bordered > tfoot > tr > th, -.table-bordered > thead > tr > td, -.table-bordered > tbody > tr > td, -.table-bordered > tfoot > tr > td { - border: 1px solid #ddd; -} - -.table-bordered > thead > tr > th, -.table-bordered > thead > tr > td { - border-bottom-width: 2px; -} - -.table-striped > tbody > tr:nth-of-type(odd) { - background-color: #f9f9f9; -} - -.table-hover > tbody > tr:hover { - background-color: #f5f5f5; -} - -table col[class*="col-"] { - position: static; - display: table-column; - float: none; -} - -table td[class*="col-"], -table th[class*="col-"] { - position: static; - display: table-cell; - float: none; -} - -.table > thead > tr > td.active, -.table > tbody > tr > td.active, -.table > tfoot > tr > td.active, -.table > thead > tr > th.active, -.table > tbody > tr > th.active, -.table > tfoot > tr > th.active, -.table > thead > tr.active > td, -.table > tbody > tr.active > td, -.table > tfoot > tr.active > td, -.table > thead > tr.active > th, -.table > tbody > tr.active > th, -.table > tfoot > tr.active > th { - background-color: #f5f5f5; -} - -.table-hover > tbody > tr > td.active:hover, -.table-hover > tbody > tr > th.active:hover, -.table-hover > tbody > tr.active:hover > td, -.table-hover > tbody > tr:hover > .active, -.table-hover > tbody > tr.active:hover > th { - background-color: #e8e8e8; -} - -.table > thead > tr > td.success, -.table > tbody > tr > td.success, -.table > tfoot > tr > td.success, -.table > thead > tr > th.success, -.table > tbody > tr > th.success, -.table > tfoot > tr > th.success, -.table > thead > tr.success > td, -.table > tbody > tr.success > td, -.table > tfoot > tr.success > td, -.table > thead > tr.success > th, -.table > tbody > tr.success > th, -.table > tfoot > tr.success > th { - background-color: #dff0d8; -} - -.table-hover > tbody > tr > td.success:hover, -.table-hover > tbody > tr > th.success:hover, -.table-hover > tbody > tr.success:hover > td, -.table-hover > tbody > tr:hover > .success, -.table-hover > tbody > tr.success:hover > th { - background-color: #d0e9c6; -} - -.table > thead > tr > td.info, -.table > tbody > tr > td.info, -.table > tfoot > tr > td.info, -.table > thead > tr > th.info, -.table > tbody > tr > th.info, -.table > tfoot > tr > th.info, -.table > thead > tr.info > td, -.table > tbody > tr.info > td, -.table > tfoot > tr.info > td, -.table > thead > tr.info > th, -.table > tbody > tr.info > th, -.table > tfoot > tr.info > th { - background-color: #d9edf7; -} - -.table-hover > tbody > tr > td.info:hover, -.table-hover > tbody > tr > th.info:hover, -.table-hover > tbody > tr.info:hover > td, -.table-hover > tbody > tr:hover > .info, -.table-hover > tbody > tr.info:hover > th { - background-color: #c4e3f3; -} - -.table > thead > tr > td.warning, -.table > tbody > tr > td.warning, -.table > tfoot > tr > td.warning, -.table > thead > tr > th.warning, -.table > tbody > tr > th.warning, -.table > tfoot > tr > th.warning, -.table > thead > tr.warning > td, -.table > tbody > tr.warning > td, -.table > tfoot > tr.warning > td, -.table > thead > tr.warning > th, -.table > tbody > tr.warning > th, -.table > tfoot > tr.warning > th { - background-color: #fcf8e3; -} - -.table-hover > tbody > tr > td.warning:hover, -.table-hover > tbody > tr > th.warning:hover, -.table-hover > tbody > tr.warning:hover > td, -.table-hover > tbody > tr:hover > .warning, -.table-hover > tbody > tr.warning:hover > th { - background-color: #faf2cc; -} - -.table > thead > tr > td.danger, -.table > tbody > tr > td.danger, -.table > tfoot > tr > td.danger, -.table > thead > tr > th.danger, -.table > tbody > tr > th.danger, -.table > tfoot > tr > th.danger, -.table > thead > tr.danger > td, -.table > tbody > tr.danger > td, -.table > tfoot > tr.danger > td, -.table > thead > tr.danger > th, -.table > tbody > tr.danger > th, -.table > tfoot > tr.danger > th { - background-color: #f2dede; -} - -.table-hover > tbody > tr > td.danger:hover, -.table-hover > tbody > tr > th.danger:hover, -.table-hover > tbody > tr.danger:hover > td, -.table-hover > tbody > tr:hover > .danger, -.table-hover > tbody > tr.danger:hover > th { - background-color: #ebcccc; -} - -.table-responsive { - min-height: .01%; - overflow-x: auto; -} - -@media screen and (max-width: 767px) { - .table-responsive { - width: 100%; - margin-bottom: 15px; - overflow-y: hidden; - -ms-overflow-style: -ms-autohiding-scrollbar; - border: 1px solid #ddd; - } - - .table-responsive > .table { - margin-bottom: 0; - } - - .table-responsive > .table > thead > tr > th, - .table-responsive > .table > tbody > tr > th, - .table-responsive > .table > tfoot > tr > th, - .table-responsive > .table > thead > tr > td, - .table-responsive > .table > tbody > tr > td, - .table-responsive > .table > tfoot > tr > td { - white-space: nowrap; - } - - .table-responsive > .table-bordered { - border: 0; - } - - .table-responsive > .table-bordered > thead > tr > th:first-child, - .table-responsive > .table-bordered > tbody > tr > th:first-child, - .table-responsive > .table-bordered > tfoot > tr > th:first-child, - .table-responsive > .table-bordered > thead > tr > td:first-child, - .table-responsive > .table-bordered > tbody > tr > td:first-child, - .table-responsive > .table-bordered > tfoot > tr > td:first-child { - border-left: 0; - } - - .table-responsive > .table-bordered > thead > tr > th:last-child, - .table-responsive > .table-bordered > tbody > tr > th:last-child, - .table-responsive > .table-bordered > tfoot > tr > th:last-child, - .table-responsive > .table-bordered > thead > tr > td:last-child, - .table-responsive > .table-bordered > tbody > tr > td:last-child, - .table-responsive > .table-bordered > tfoot > tr > td:last-child { - border-right: 0; - } - - .table-responsive > .table-bordered > tbody > tr:last-child > th, - .table-responsive > .table-bordered > tfoot > tr:last-child > th, - .table-responsive > .table-bordered > tbody > tr:last-child > td, - .table-responsive > .table-bordered > tfoot > tr:last-child > td { - border-bottom: 0; - } -} - -fieldset { - min-width: 0; - padding: 0; - margin: 0; - border: 0; -} - -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: 20px; - font-size: 21px; - line-height: inherit; - color: #333; - border: 0; - border-bottom: 1px solid #e5e5e5; -} - -label { - display: inline-block; - max-width: 100%; - margin-bottom: 5px; - font-weight: bold; -} - -input[type="search"] { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -input[type="radio"], -input[type="checkbox"] { - margin: 4px 0 0; - margin-top: 1px \9; - line-height: normal; -} - -input[type="file"] { - display: block; -} - -input[type="range"] { - display: block; - width: 100%; -} - -select[multiple], -select[size] { - height: auto; -} - -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -output { - display: block; - padding-top: 7px; - font-size: 14px; - line-height: 1.42857143; - color: #555; -} - -.form-control { - display: block; - width: 100%; - height: 34px; - padding: 6px 12px; - font-size: 14px; - line-height: 1.42857143; - color: #555; - background-color: #fff; - background-image: none; - border: 1px solid #ccc; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; - -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; - transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; -} - -.form-control:focus { - border-color: #66afe9; - outline: 0; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); -} - -.form-control::-moz-placeholder { - color: #999; - opacity: 1; -} - -.form-control:-ms-input-placeholder { - color: #999; -} - -.form-control::-webkit-input-placeholder { - color: #999; -} - -.form-control::-ms-expand { - background-color: transparent; - border: 0; -} - -.form-control[disabled], -.form-control[readonly], -fieldset[disabled] .form-control { - background-color: #eee; - opacity: 1; -} - -.form-control[disabled], -fieldset[disabled] .form-control { - cursor: not-allowed; -} - -textarea.form-control { - height: auto; -} - -input[type="search"] { - -webkit-appearance: none; -} - -@media screen and (-webkit-min-device-pixel-ratio: 0) { - input[type="date"].form-control, - input[type="time"].form-control, - input[type="datetime-local"].form-control, - input[type="month"].form-control { - line-height: 34px; - } - - input[type="date"].input-sm, - input[type="time"].input-sm, - input[type="datetime-local"].input-sm, - input[type="month"].input-sm, - .input-group-sm input[type="date"], - .input-group-sm input[type="time"], - .input-group-sm input[type="datetime-local"], - .input-group-sm input[type="month"] { - line-height: 30px; - } - - input[type="date"].input-lg, - input[type="time"].input-lg, - input[type="datetime-local"].input-lg, - input[type="month"].input-lg, - .input-group-lg input[type="date"], - .input-group-lg input[type="time"], - .input-group-lg input[type="datetime-local"], - .input-group-lg input[type="month"] { - line-height: 46px; - } -} - -.form-group { - margin-bottom: 15px; -} - -.radio, -.checkbox { - position: relative; - display: block; - margin-top: 10px; - margin-bottom: 10px; -} - -.radio label, -.checkbox label { - min-height: 20px; - padding-left: 20px; - margin-bottom: 0; - font-weight: normal; - cursor: pointer; -} - -.radio input[type="radio"], -.radio-inline input[type="radio"], -.checkbox input[type="checkbox"], -.checkbox-inline input[type="checkbox"] { - position: absolute; - margin-top: 4px \9; - margin-left: -20px; -} - -.radio + .radio, -.checkbox + .checkbox { - margin-top: -5px; -} - -.radio-inline, -.checkbox-inline { - position: relative; - display: inline-block; - padding-left: 20px; - margin-bottom: 0; - font-weight: normal; - vertical-align: middle; - cursor: pointer; -} - -.radio-inline + .radio-inline, -.checkbox-inline + .checkbox-inline { - margin-top: 0; - margin-left: 10px; -} - -input[type="radio"][disabled], -input[type="checkbox"][disabled], -input[type="radio"].disabled, -input[type="checkbox"].disabled, -fieldset[disabled] input[type="radio"], -fieldset[disabled] input[type="checkbox"] { - cursor: not-allowed; -} - -.radio-inline.disabled, -.checkbox-inline.disabled, -fieldset[disabled] .radio-inline, -fieldset[disabled] .checkbox-inline { - cursor: not-allowed; -} - -.radio.disabled label, -.checkbox.disabled label, -fieldset[disabled] .radio label, -fieldset[disabled] .checkbox label { - cursor: not-allowed; -} - -.form-control-static { - min-height: 34px; - padding-top: 7px; - padding-bottom: 7px; - margin-bottom: 0; -} - -.form-control-static.input-lg, -.form-control-static.input-sm { - padding-right: 0; - padding-left: 0; -} - -.input-sm { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} - -select.input-sm { - height: 30px; - line-height: 30px; -} - -textarea.input-sm, -select[multiple].input-sm { - height: auto; -} - -.form-group-sm .form-control { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} - -.form-group-sm select.form-control { - height: 30px; - line-height: 30px; -} - -.form-group-sm textarea.form-control, -.form-group-sm select[multiple].form-control { - height: auto; -} - -.form-group-sm .form-control-static { - height: 30px; - min-height: 32px; - padding: 6px 10px; - font-size: 12px; - line-height: 1.5; -} - -.input-lg { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} - -select.input-lg { - height: 46px; - line-height: 46px; -} - -textarea.input-lg, -select[multiple].input-lg { - height: auto; -} - -.form-group-lg .form-control { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} - -.form-group-lg select.form-control { - height: 46px; - line-height: 46px; -} - -.form-group-lg textarea.form-control, -.form-group-lg select[multiple].form-control { - height: auto; -} - -.form-group-lg .form-control-static { - height: 46px; - min-height: 38px; - padding: 11px 16px; - font-size: 18px; - line-height: 1.3333333; -} - -.has-feedback { - position: relative; -} - -.has-feedback .form-control { - padding-right: 42.5px; -} - -.form-control-feedback { - position: absolute; - top: 0; - right: 0; - z-index: 2; - display: block; - width: 34px; - height: 34px; - line-height: 34px; - text-align: center; - pointer-events: none; -} - -.input-lg + .form-control-feedback, -.input-group-lg + .form-control-feedback, -.form-group-lg .form-control + .form-control-feedback { - width: 46px; - height: 46px; - line-height: 46px; -} - -.input-sm + .form-control-feedback, -.input-group-sm + .form-control-feedback, -.form-group-sm .form-control + .form-control-feedback { - width: 30px; - height: 30px; - line-height: 30px; -} - -.has-success .help-block, -.has-success .control-label, -.has-success .radio, -.has-success .checkbox, -.has-success .radio-inline, -.has-success .checkbox-inline, -.has-success.radio label, -.has-success.checkbox label, -.has-success.radio-inline label, -.has-success.checkbox-inline label { - color: #3c763d; -} - -.has-success .form-control { - border-color: #3c763d; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} - -.has-success .form-control:focus { - border-color: #2b542c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; -} - -.has-success .input-group-addon { - color: #3c763d; - background-color: #dff0d8; - border-color: #3c763d; -} - -.has-success .form-control-feedback { - color: #3c763d; -} - -.has-warning .help-block, -.has-warning .control-label, -.has-warning .radio, -.has-warning .checkbox, -.has-warning .radio-inline, -.has-warning .checkbox-inline, -.has-warning.radio label, -.has-warning.checkbox label, -.has-warning.radio-inline label, -.has-warning.checkbox-inline label { - color: #8a6d3b; -} - -.has-warning .form-control { - border-color: #8a6d3b; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} - -.has-warning .form-control:focus { - border-color: #66512c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; -} - -.has-warning .input-group-addon { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #8a6d3b; -} - -.has-warning .form-control-feedback { - color: #8a6d3b; -} - -.has-error .help-block, -.has-error .control-label, -.has-error .radio, -.has-error .checkbox, -.has-error .radio-inline, -.has-error .checkbox-inline, -.has-error.radio label, -.has-error.checkbox label, -.has-error.radio-inline label, -.has-error.checkbox-inline label { - color: #a94442; -} - -.has-error .form-control { - border-color: #a94442; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} - -.has-error .form-control:focus { - border-color: #843534; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; -} - -.has-error .input-group-addon { - color: #a94442; - background-color: #f2dede; - border-color: #a94442; -} - -.has-error .form-control-feedback { - color: #a94442; -} - -.has-feedback label ~ .form-control-feedback { - top: 25px; -} - -.has-feedback label.sr-only ~ .form-control-feedback { - top: 0; -} - -.help-block { - display: block; - margin-top: 5px; - margin-bottom: 10px; - color: #737373; -} - -@media (min-width: 768px) { - .form-inline .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - - .form-inline .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - - .form-inline .form-control-static { - display: inline-block; - } - - .form-inline .input-group { - display: inline-table; - vertical-align: middle; - } - - .form-inline .input-group .input-group-addon, - .form-inline .input-group .input-group-btn, - .form-inline .input-group .form-control { - width: auto; - } - - .form-inline .input-group > .form-control { - width: 100%; - } - - .form-inline .control-label { - margin-bottom: 0; - vertical-align: middle; - } - - .form-inline .radio, - .form-inline .checkbox { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle; - } - - .form-inline .radio label, - .form-inline .checkbox label { - padding-left: 0; - } - - .form-inline .radio input[type="radio"], - .form-inline .checkbox input[type="checkbox"] { - position: relative; - margin-left: 0; - } - - .form-inline .has-feedback .form-control-feedback { - top: 0; - } -} - -.form-horizontal .radio, -.form-horizontal .checkbox, -.form-horizontal .radio-inline, -.form-horizontal .checkbox-inline { - padding-top: 7px; - margin-top: 0; - margin-bottom: 0; -} - -.form-horizontal .radio, -.form-horizontal .checkbox { - min-height: 27px; -} - -.form-horizontal .form-group { - margin-right: -15px; - margin-left: -15px; -} - -@media (min-width: 768px) { - .form-horizontal .control-label { - padding-top: 7px; - margin-bottom: 0; - text-align: right; - } -} - -.form-horizontal .has-feedback .form-control-feedback { - right: 15px; -} - -@media (min-width: 768px) { - .form-horizontal .form-group-lg .control-label { - padding-top: 11px; - font-size: 18px; - } -} - -@media (min-width: 768px) { - .form-horizontal .form-group-sm .control-label { - padding-top: 6px; - font-size: 12px; - } -} - -.btn { - display: inline-block; - padding: 6px 12px; - margin-bottom: 0; - font-size: 14px; - font-weight: normal; - line-height: 1.42857143; - text-align: center; - white-space: nowrap; - vertical-align: middle; - -ms-touch-action: manipulation; - touch-action: manipulation; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-image: none; - border: 1px solid transparent; - border-radius: 4px; -} - -.btn:focus, -.btn:active:focus, -.btn.active:focus, -.btn.focus, -.btn:active.focus, -.btn.active.focus { - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -.btn:hover, -.btn:focus, -.btn.focus { - color: #333; - text-decoration: none; -} - -.btn:active, -.btn.active { - background-image: none; - outline: 0; - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); -} - -.btn.disabled, -.btn[disabled], -fieldset[disabled] .btn { - cursor: not-allowed; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - box-shadow: none; - opacity: .65; -} - -a.btn.disabled, -fieldset[disabled] a.btn { - pointer-events: none; -} - -.btn-default { - color: #333; - background-color: #fff; - border-color: #ccc; -} - -.btn-default:focus, -.btn-default.focus { - color: #333; - background-color: #e6e6e6; - border-color: #8c8c8c; -} - -.btn-default:hover { - color: #333; - background-color: #e6e6e6; - border-color: #adadad; -} - -.btn-default:active, -.btn-default.active, -.open > .dropdown-toggle.btn-default { - color: #333; - background-color: #e6e6e6; - border-color: #adadad; -} - -.btn-default:active:hover, -.btn-default.active:hover, -.open > .dropdown-toggle.btn-default:hover, -.btn-default:active:focus, -.btn-default.active:focus, -.open > .dropdown-toggle.btn-default:focus, -.btn-default:active.focus, -.btn-default.active.focus, -.open > .dropdown-toggle.btn-default.focus { - color: #333; - background-color: #d4d4d4; - border-color: #8c8c8c; -} - -.btn-default:active, -.btn-default.active, -.open > .dropdown-toggle.btn-default { - background-image: none; -} - -.btn-default.disabled:hover, -.btn-default[disabled]:hover, -fieldset[disabled] .btn-default:hover, -.btn-default.disabled:focus, -.btn-default[disabled]:focus, -fieldset[disabled] .btn-default:focus, -.btn-default.disabled.focus, -.btn-default[disabled].focus, -fieldset[disabled] .btn-default.focus { - background-color: #fff; - border-color: #ccc; -} - -.btn-default .badge { - color: #fff; - background-color: #333; -} - -.btn-primary { - color: #fff; - background-color: #337ab7; - border-color: #2e6da4; -} - -.btn-primary:focus, -.btn-primary.focus { - color: #fff; - background-color: #286090; - border-color: #122b40; -} - -.btn-primary:hover { - color: #fff; - background-color: #286090; - border-color: #204d74; -} - -.btn-primary:active, -.btn-primary.active, -.open > .dropdown-toggle.btn-primary { - color: #fff; - background-color: #286090; - border-color: #204d74; -} - -.btn-primary:active:hover, -.btn-primary.active:hover, -.open > .dropdown-toggle.btn-primary:hover, -.btn-primary:active:focus, -.btn-primary.active:focus, -.open > .dropdown-toggle.btn-primary:focus, -.btn-primary:active.focus, -.btn-primary.active.focus, -.open > .dropdown-toggle.btn-primary.focus { - color: #fff; - background-color: #204d74; - border-color: #122b40; -} - -.btn-primary:active, -.btn-primary.active, -.open > .dropdown-toggle.btn-primary { - background-image: none; -} - -.btn-primary.disabled:hover, -.btn-primary[disabled]:hover, -fieldset[disabled] .btn-primary:hover, -.btn-primary.disabled:focus, -.btn-primary[disabled]:focus, -fieldset[disabled] .btn-primary:focus, -.btn-primary.disabled.focus, -.btn-primary[disabled].focus, -fieldset[disabled] .btn-primary.focus { - background-color: #337ab7; - border-color: #2e6da4; -} - -.btn-primary .badge { - color: #337ab7; - background-color: #fff; -} - -.btn-success { - color: #fff; - background-color: #5cb85c; - border-color: #4cae4c; -} - -.btn-success:focus, -.btn-success.focus { - color: #fff; - background-color: #449d44; - border-color: #255625; -} - -.btn-success:hover { - color: #fff; - background-color: #449d44; - border-color: #398439; -} - -.btn-success:active, -.btn-success.active, -.open > .dropdown-toggle.btn-success { - color: #fff; - background-color: #449d44; - border-color: #398439; -} - -.btn-success:active:hover, -.btn-success.active:hover, -.open > .dropdown-toggle.btn-success:hover, -.btn-success:active:focus, -.btn-success.active:focus, -.open > .dropdown-toggle.btn-success:focus, -.btn-success:active.focus, -.btn-success.active.focus, -.open > .dropdown-toggle.btn-success.focus { - color: #fff; - background-color: #398439; - border-color: #255625; -} - -.btn-success:active, -.btn-success.active, -.open > .dropdown-toggle.btn-success { - background-image: none; -} - -.btn-success.disabled:hover, -.btn-success[disabled]:hover, -fieldset[disabled] .btn-success:hover, -.btn-success.disabled:focus, -.btn-success[disabled]:focus, -fieldset[disabled] .btn-success:focus, -.btn-success.disabled.focus, -.btn-success[disabled].focus, -fieldset[disabled] .btn-success.focus { - background-color: #5cb85c; - border-color: #4cae4c; -} - -.btn-success .badge { - color: #5cb85c; - background-color: #fff; -} - -.btn-info { - color: #fff; - background-color: #5bc0de; - border-color: #46b8da; -} - -.btn-info:focus, -.btn-info.focus { - color: #fff; - background-color: #31b0d5; - border-color: #1b6d85; -} - -.btn-info:hover { - color: #fff; - background-color: #31b0d5; - border-color: #269abc; -} - -.btn-info:active, -.btn-info.active, -.open > .dropdown-toggle.btn-info { - color: #fff; - background-color: #31b0d5; - border-color: #269abc; -} - -.btn-info:active:hover, -.btn-info.active:hover, -.open > .dropdown-toggle.btn-info:hover, -.btn-info:active:focus, -.btn-info.active:focus, -.open > .dropdown-toggle.btn-info:focus, -.btn-info:active.focus, -.btn-info.active.focus, -.open > .dropdown-toggle.btn-info.focus { - color: #fff; - background-color: #269abc; - border-color: #1b6d85; -} - -.btn-info:active, -.btn-info.active, -.open > .dropdown-toggle.btn-info { - background-image: none; -} - -.btn-info.disabled:hover, -.btn-info[disabled]:hover, -fieldset[disabled] .btn-info:hover, -.btn-info.disabled:focus, -.btn-info[disabled]:focus, -fieldset[disabled] .btn-info:focus, -.btn-info.disabled.focus, -.btn-info[disabled].focus, -fieldset[disabled] .btn-info.focus { - background-color: #5bc0de; - border-color: #46b8da; -} - -.btn-info .badge { - color: #5bc0de; - background-color: #fff; -} - -.btn-warning { - color: #fff; - background-color: #f0ad4e; - border-color: #eea236; -} - -.btn-warning:focus, -.btn-warning.focus { - color: #fff; - background-color: #ec971f; - border-color: #985f0d; -} - -.btn-warning:hover { - color: #fff; - background-color: #ec971f; - border-color: #d58512; -} - -.btn-warning:active, -.btn-warning.active, -.open > .dropdown-toggle.btn-warning { - color: #fff; - background-color: #ec971f; - border-color: #d58512; -} - -.btn-warning:active:hover, -.btn-warning.active:hover, -.open > .dropdown-toggle.btn-warning:hover, -.btn-warning:active:focus, -.btn-warning.active:focus, -.open > .dropdown-toggle.btn-warning:focus, -.btn-warning:active.focus, -.btn-warning.active.focus, -.open > .dropdown-toggle.btn-warning.focus { - color: #fff; - background-color: #d58512; - border-color: #985f0d; -} - -.btn-warning:active, -.btn-warning.active, -.open > .dropdown-toggle.btn-warning { - background-image: none; -} - -.btn-warning.disabled:hover, -.btn-warning[disabled]:hover, -fieldset[disabled] .btn-warning:hover, -.btn-warning.disabled:focus, -.btn-warning[disabled]:focus, -fieldset[disabled] .btn-warning:focus, -.btn-warning.disabled.focus, -.btn-warning[disabled].focus, -fieldset[disabled] .btn-warning.focus { - background-color: #f0ad4e; - border-color: #eea236; -} - -.btn-warning .badge { - color: #f0ad4e; - background-color: #fff; -} - -.btn-danger { - color: #fff; - background-color: #d9534f; - border-color: #d43f3a; -} - -.btn-danger:focus, -.btn-danger.focus { - color: #fff; - background-color: #c9302c; - border-color: #761c19; -} - -.btn-danger:hover { - color: #fff; - background-color: #c9302c; - border-color: #ac2925; -} - -.btn-danger:active, -.btn-danger.active, -.open > .dropdown-toggle.btn-danger { - color: #fff; - background-color: #c9302c; - border-color: #ac2925; -} - -.btn-danger:active:hover, -.btn-danger.active:hover, -.open > .dropdown-toggle.btn-danger:hover, -.btn-danger:active:focus, -.btn-danger.active:focus, -.open > .dropdown-toggle.btn-danger:focus, -.btn-danger:active.focus, -.btn-danger.active.focus, -.open > .dropdown-toggle.btn-danger.focus { - color: #fff; - background-color: #ac2925; - border-color: #761c19; -} - -.btn-danger:active, -.btn-danger.active, -.open > .dropdown-toggle.btn-danger { - background-image: none; -} - -.btn-danger.disabled:hover, -.btn-danger[disabled]:hover, -fieldset[disabled] .btn-danger:hover, -.btn-danger.disabled:focus, -.btn-danger[disabled]:focus, -fieldset[disabled] .btn-danger:focus, -.btn-danger.disabled.focus, -.btn-danger[disabled].focus, -fieldset[disabled] .btn-danger.focus { - background-color: #d9534f; - border-color: #d43f3a; -} - -.btn-danger .badge { - color: #d9534f; - background-color: #fff; -} - -.btn-link { - font-weight: normal; - color: #337ab7; - border-radius: 0; -} - -.btn-link, -.btn-link:active, -.btn-link.active, -.btn-link[disabled], -fieldset[disabled] .btn-link { - background-color: transparent; - -webkit-box-shadow: none; - box-shadow: none; -} - -.btn-link, -.btn-link:hover, -.btn-link:focus, -.btn-link:active { - border-color: transparent; -} - -.btn-link:hover, -.btn-link:focus { - color: #23527c; - text-decoration: underline; - background-color: transparent; -} - -.btn-link[disabled]:hover, -fieldset[disabled] .btn-link:hover, -.btn-link[disabled]:focus, -fieldset[disabled] .btn-link:focus { - color: #777; - text-decoration: none; -} - -.btn-lg, -.btn-group-lg > .btn { - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} - -.btn-sm, -.btn-group-sm > .btn { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} - -.btn-xs, -.btn-group-xs > .btn { - padding: 1px 5px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} - -.btn-block { - display: block; - width: 100%; -} - -.btn-block + .btn-block { - margin-top: 5px; -} - -input[type="submit"].btn-block, -input[type="reset"].btn-block, -input[type="button"].btn-block { - width: 100%; -} - -.fade { - opacity: 0; - -webkit-transition: opacity .15s linear; - -o-transition: opacity .15s linear; - transition: opacity .15s linear; -} - -.fade.in { - opacity: 1; -} - -.collapse { - display: none; -} - -.collapse.in { - display: block; -} - -tr.collapse.in { - display: table-row; -} - -tbody.collapse.in { - display: table-row-group; -} - -.collapsing { - position: relative; - height: 0; - overflow: hidden; - -webkit-transition-timing-function: ease; - -o-transition-timing-function: ease; - transition-timing-function: ease; - -webkit-transition-duration: .35s; - -o-transition-duration: .35s; - transition-duration: .35s; - -webkit-transition-property: height, visibility; - -o-transition-property: height, visibility; - transition-property: height, visibility; -} - -.caret { - display: inline-block; - width: 0; - height: 0; - margin-left: 2px; - vertical-align: middle; - border-top: 4px dashed; - border-top: 4px solid \9; - border-right: 4px solid transparent; - border-left: 4px solid transparent; -} - -.dropup, -.dropdown { - position: relative; -} - -.dropdown-toggle:focus { - outline: 0; -} - -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - min-width: 160px; - padding: 5px 0; - margin: 2px 0 0; - font-size: 14px; - text-align: left; - list-style: none; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, .15); - border-radius: 4px; - -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); - box-shadow: 0 6px 12px rgba(0, 0, 0, .175); -} - -.dropdown-menu.pull-right { - right: 0; - left: auto; -} - -.dropdown-menu .divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5; -} - -.dropdown-menu > li > a { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 1.42857143; - color: #333; - white-space: nowrap; -} - -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus { - color: #262626; - text-decoration: none; - background-color: #f5f5f5; -} - -.dropdown-menu > .active > a, -.dropdown-menu > .active > a:hover, -.dropdown-menu > .active > a:focus { - color: #fff; - text-decoration: none; - background-color: #337ab7; - outline: 0; -} - -.dropdown-menu > .disabled > a, -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - color: #777; -} - -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - text-decoration: none; - cursor: not-allowed; - background-color: transparent; - background-image: none; - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.open > .dropdown-menu { - display: block; -} - -.open > a { - outline: 0; -} - -.dropdown-menu-right { - right: 0; - left: auto; -} - -.dropdown-menu-left { - right: auto; - left: 0; -} - -.dropdown-header { - display: block; - padding: 3px 20px; - font-size: 12px; - line-height: 1.42857143; - color: #777; - white-space: nowrap; -} - -.dropdown-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 990; -} - -.pull-right > .dropdown-menu { - right: 0; - left: auto; -} - -.dropup .caret, -.navbar-fixed-bottom .dropdown .caret { - content: ""; - border-top: 0; - border-bottom: 4px dashed; - border-bottom: 4px solid \9; -} - -.dropup .dropdown-menu, -.navbar-fixed-bottom .dropdown .dropdown-menu { - top: auto; - bottom: 100%; - margin-bottom: 2px; -} - -@media (min-width: 768px) { - .navbar-right .dropdown-menu { - right: 0; - left: auto; - } - - .navbar-right .dropdown-menu-left { - right: auto; - left: 0; - } -} - -.btn-group, -.btn-group-vertical { - position: relative; - display: inline-block; - vertical-align: middle; -} - -.btn-group > .btn, -.btn-group-vertical > .btn { - position: relative; - float: left; -} - -.btn-group > .btn:hover, -.btn-group-vertical > .btn:hover, -.btn-group > .btn:focus, -.btn-group-vertical > .btn:focus, -.btn-group > .btn:active, -.btn-group-vertical > .btn:active, -.btn-group > .btn.active, -.btn-group-vertical > .btn.active { - z-index: 2; -} - -.btn-group .btn + .btn, -.btn-group .btn + .btn-group, -.btn-group .btn-group + .btn, -.btn-group .btn-group + .btn-group { - margin-left: -1px; -} - -.btn-toolbar { - margin-left: -5px; -} - -.btn-toolbar .btn, -.btn-toolbar .btn-group, -.btn-toolbar .input-group { - float: left; -} - -.btn-toolbar > .btn, -.btn-toolbar > .btn-group, -.btn-toolbar > .input-group { - margin-left: 5px; -} - -.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { - border-radius: 0; -} - -.btn-group > .btn:first-child { - margin-left: 0; -} - -.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.btn-group > .btn:last-child:not(:first-child), -.btn-group > .dropdown-toggle:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.btn-group > .btn-group { - float: left; -} - -.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; -} - -.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, -.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.btn-group .dropdown-toggle:active, -.btn-group.open .dropdown-toggle { - outline: 0; -} - -.btn-group > .btn + .dropdown-toggle { - padding-right: 8px; - padding-left: 8px; -} - -.btn-group > .btn-lg + .dropdown-toggle { - padding-right: 12px; - padding-left: 12px; -} - -.btn-group.open .dropdown-toggle { - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); -} - -.btn-group.open .dropdown-toggle.btn-link { - -webkit-box-shadow: none; - box-shadow: none; -} - -.btn .caret { - margin-left: 0; -} - -.btn-lg .caret { - border-width: 5px 5px 0; - border-bottom-width: 0; -} - -.dropup .btn-lg .caret { - border-width: 0 5px 5px; -} - -.btn-group-vertical > .btn, -.btn-group-vertical > .btn-group, -.btn-group-vertical > .btn-group > .btn { - display: block; - float: none; - width: 100%; - max-width: 100%; -} - -.btn-group-vertical > .btn-group > .btn { - float: none; -} - -.btn-group-vertical > .btn + .btn, -.btn-group-vertical > .btn + .btn-group, -.btn-group-vertical > .btn-group + .btn, -.btn-group-vertical > .btn-group + .btn-group { - margin-top: -1px; - margin-left: 0; -} - -.btn-group-vertical > .btn:not(:first-child):not(:last-child) { - border-radius: 0; -} - -.btn-group-vertical > .btn:first-child:not(:last-child) { - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} - -.btn-group-vertical > .btn:last-child:not(:first-child) { - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; -} - -.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; -} - -.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, -.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} - -.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.btn-group-justified { - display: table; - width: 100%; - table-layout: fixed; - border-collapse: separate; -} - -.btn-group-justified > .btn, -.btn-group-justified > .btn-group { - display: table-cell; - float: none; - width: 1%; -} - -.btn-group-justified > .btn-group .btn { - width: 100%; -} - -.btn-group-justified > .btn-group .dropdown-menu { - left: auto; -} - -[data-toggle="buttons"] > .btn input[type="radio"], -[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], -[data-toggle="buttons"] > .btn input[type="checkbox"], -[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { - position: absolute; - clip: rect(0, 0, 0, 0); - pointer-events: none; -} - -.input-group { - position: relative; - display: table; - border-collapse: separate; -} - -.input-group[class*="col-"] { - float: none; - padding-right: 0; - padding-left: 0; -} - -.input-group .form-control { - position: relative; - z-index: 2; - float: left; - width: 100%; - margin-bottom: 0; -} - -.input-group .form-control:focus { - z-index: 3; -} - -.input-group-lg > .form-control, -.input-group-lg > .input-group-addon, -.input-group-lg > .input-group-btn > .btn { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} - -select.input-group-lg > .form-control, -select.input-group-lg > .input-group-addon, -select.input-group-lg > .input-group-btn > .btn { - height: 46px; - line-height: 46px; -} - -textarea.input-group-lg > .form-control, -textarea.input-group-lg > .input-group-addon, -textarea.input-group-lg > .input-group-btn > .btn, -select[multiple].input-group-lg > .form-control, -select[multiple].input-group-lg > .input-group-addon, -select[multiple].input-group-lg > .input-group-btn > .btn { - height: auto; -} - -.input-group-sm > .form-control, -.input-group-sm > .input-group-addon, -.input-group-sm > .input-group-btn > .btn { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} - -select.input-group-sm > .form-control, -select.input-group-sm > .input-group-addon, -select.input-group-sm > .input-group-btn > .btn { - height: 30px; - line-height: 30px; -} - -textarea.input-group-sm > .form-control, -textarea.input-group-sm > .input-group-addon, -textarea.input-group-sm > .input-group-btn > .btn, -select[multiple].input-group-sm > .form-control, -select[multiple].input-group-sm > .input-group-addon, -select[multiple].input-group-sm > .input-group-btn > .btn { - height: auto; -} - -.input-group-addon, -.input-group-btn, -.input-group .form-control { - display: table-cell; -} - -.input-group-addon:not(:first-child):not(:last-child), -.input-group-btn:not(:first-child):not(:last-child), -.input-group .form-control:not(:first-child):not(:last-child) { - border-radius: 0; -} - -.input-group-addon, -.input-group-btn { - width: 1%; - white-space: nowrap; - vertical-align: middle; -} - -.input-group-addon { - padding: 6px 12px; - font-size: 14px; - font-weight: normal; - line-height: 1; - color: #555; - text-align: center; - background-color: #eee; - border: 1px solid #ccc; - border-radius: 4px; -} - -.input-group-addon.input-sm { - padding: 5px 10px; - font-size: 12px; - border-radius: 3px; -} - -.input-group-addon.input-lg { - padding: 10px 16px; - font-size: 18px; - border-radius: 6px; -} - -.input-group-addon input[type="radio"], -.input-group-addon input[type="checkbox"] { - margin-top: 0; -} - -.input-group .form-control:first-child, -.input-group-addon:first-child, -.input-group-btn:first-child > .btn, -.input-group-btn:first-child > .btn-group > .btn, -.input-group-btn:first-child > .dropdown-toggle, -.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), -.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.input-group-addon:first-child { - border-right: 0; -} - -.input-group .form-control:last-child, -.input-group-addon:last-child, -.input-group-btn:last-child > .btn, -.input-group-btn:last-child > .btn-group > .btn, -.input-group-btn:last-child > .dropdown-toggle, -.input-group-btn:first-child > .btn:not(:first-child), -.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.input-group-addon:last-child { - border-left: 0; -} - -.input-group-btn { - position: relative; - font-size: 0; - white-space: nowrap; -} - -.input-group-btn > .btn { - position: relative; -} - -.input-group-btn > .btn + .btn { - margin-left: -1px; -} - -.input-group-btn > .btn:hover, -.input-group-btn > .btn:focus, -.input-group-btn > .btn:active { - z-index: 2; -} - -.input-group-btn:first-child > .btn, -.input-group-btn:first-child > .btn-group { - margin-right: -1px; -} - -.input-group-btn:last-child > .btn, -.input-group-btn:last-child > .btn-group { - z-index: 2; - margin-left: -1px; -} - -.nav { - padding-left: 0; - margin-bottom: 0; - list-style: none; -} - -.nav > li { - position: relative; - display: block; -} - -.nav > li > a { - position: relative; - display: block; - padding: 10px 15px; -} - -.nav > li > a:hover, -.nav > li > a:focus { - text-decoration: none; - background-color: #eee; -} - -.nav > li.disabled > a { - color: #777; -} - -.nav > li.disabled > a:hover, -.nav > li.disabled > a:focus { - color: #777; - text-decoration: none; - cursor: not-allowed; - background-color: transparent; -} - -.nav .open > a, -.nav .open > a:hover, -.nav .open > a:focus { - background-color: #eee; - border-color: #337ab7; -} - -.nav .nav-divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5; -} - -.nav > li > a > img { - max-width: none; -} - -.nav-tabs { - border-bottom: 1px solid #ddd; -} - -.nav-tabs > li { - float: left; - margin-bottom: -1px; -} - -.nav-tabs > li > a { - margin-right: 2px; - line-height: 1.42857143; - border: 1px solid transparent; - border-radius: 4px 4px 0 0; -} - -.nav-tabs > li > a:hover { - border-color: #eee #eee #ddd; -} - -.nav-tabs > li.active > a, -.nav-tabs > li.active > a:hover, -.nav-tabs > li.active > a:focus { - color: #555; - cursor: default; - background-color: #fff; - border: 1px solid #ddd; - border-bottom-color: transparent; -} - -.nav-tabs.nav-justified { - width: 100%; - border-bottom: 0; -} - -.nav-tabs.nav-justified > li { - float: none; -} - -.nav-tabs.nav-justified > li > a { - margin-bottom: 5px; - text-align: center; -} - -.nav-tabs.nav-justified > .dropdown .dropdown-menu { - top: auto; - left: auto; -} - -@media (min-width: 768px) { - .nav-tabs.nav-justified > li { - display: table-cell; - width: 1%; - } - - .nav-tabs.nav-justified > li > a { - margin-bottom: 0; - } -} - -.nav-tabs.nav-justified > li > a { - margin-right: 0; - border-radius: 4px; -} - -.nav-tabs.nav-justified > .active > a, -.nav-tabs.nav-justified > .active > a:hover, -.nav-tabs.nav-justified > .active > a:focus { - border: 1px solid #ddd; -} - -@media (min-width: 768px) { - .nav-tabs.nav-justified > li > a { - border-bottom: 1px solid #ddd; - border-radius: 4px 4px 0 0; - } - - .nav-tabs.nav-justified > .active > a, - .nav-tabs.nav-justified > .active > a:hover, - .nav-tabs.nav-justified > .active > a:focus { - border-bottom-color: #fff; - } -} - -.nav-pills > li { - float: left; -} - -.nav-pills > li > a { - border-radius: 4px; -} - -.nav-pills > li + li { - margin-left: 2px; -} - -.nav-pills > li.active > a, -.nav-pills > li.active > a:hover, -.nav-pills > li.active > a:focus { - color: #fff; - background-color: #337ab7; -} - -.nav-stacked > li { - float: none; -} - -.nav-stacked > li + li { - margin-top: 2px; - margin-left: 0; -} - -.nav-justified { - width: 100%; -} - -.nav-justified > li { - float: none; -} - -.nav-justified > li > a { - margin-bottom: 5px; - text-align: center; -} - -.nav-justified > .dropdown .dropdown-menu { - top: auto; - left: auto; -} - -@media (min-width: 768px) { - .nav-justified > li { - display: table-cell; - width: 1%; - } - - .nav-justified > li > a { - margin-bottom: 0; - } -} - -.nav-tabs-justified { - border-bottom: 0; -} - -.nav-tabs-justified > li > a { - margin-right: 0; - border-radius: 4px; -} - -.nav-tabs-justified > .active > a, -.nav-tabs-justified > .active > a:hover, -.nav-tabs-justified > .active > a:focus { - border: 1px solid #ddd; -} - -@media (min-width: 768px) { - .nav-tabs-justified > li > a { - border-bottom: 1px solid #ddd; - border-radius: 4px 4px 0 0; - } - - .nav-tabs-justified > .active > a, - .nav-tabs-justified > .active > a:hover, - .nav-tabs-justified > .active > a:focus { - border-bottom-color: #fff; - } -} - -.tab-content > .tab-pane { - display: none; -} - -.tab-content > .active { - display: block; -} - -.nav-tabs .dropdown-menu { - margin-top: -1px; - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.navbar { - position: relative; - min-height: 50px; - margin-bottom: 20px; - border: 1px solid transparent; -} - -@media (min-width: 768px) { - .navbar { - border-radius: 4px; - } -} - -@media (min-width: 768px) { - .navbar-header { - float: left; - } -} - -.navbar-collapse { - padding-right: 15px; - padding-left: 15px; - overflow-x: visible; - -webkit-overflow-scrolling: touch; - border-top: 1px solid transparent; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); -} - -.navbar-collapse.in { - overflow-y: auto; -} - -@media (min-width: 768px) { - .navbar-collapse { - width: auto; - border-top: 0; - -webkit-box-shadow: none; - box-shadow: none; - } - - .navbar-collapse.collapse { - display: block !important; - height: auto !important; - padding-bottom: 0; - overflow: visible !important; - } - - .navbar-collapse.in { - overflow-y: visible; - } - - .navbar-fixed-top .navbar-collapse, - .navbar-static-top .navbar-collapse, - .navbar-fixed-bottom .navbar-collapse { - padding-right: 0; - padding-left: 0; - } -} - -.navbar-fixed-top .navbar-collapse, -.navbar-fixed-bottom .navbar-collapse { - max-height: 340px; -} - -@media (max-device-width: 480px) and (orientation: landscape) { - .navbar-fixed-top .navbar-collapse, - .navbar-fixed-bottom .navbar-collapse { - max-height: 200px; - } -} - -.container > .navbar-header, -.container-fluid > .navbar-header, -.container > .navbar-collapse, -.container-fluid > .navbar-collapse { - margin-right: -15px; - margin-left: -15px; -} - -@media (min-width: 768px) { - .container > .navbar-header, - .container-fluid > .navbar-header, - .container > .navbar-collapse, - .container-fluid > .navbar-collapse { - margin-right: 0; - margin-left: 0; - } -} - -.navbar-static-top { - z-index: 1000; - border-width: 0 0 1px; -} - -@media (min-width: 768px) { - .navbar-static-top { - border-radius: 0; - } -} - -.navbar-fixed-top, -.navbar-fixed-bottom { - position: fixed; - right: 0; - left: 0; - z-index: 1030; -} - -@media (min-width: 768px) { - .navbar-fixed-top, - .navbar-fixed-bottom { - border-radius: 0; - } -} - -.navbar-fixed-top { - top: 0; - border-width: 0 0 1px; -} - -.navbar-fixed-bottom { - bottom: 0; - margin-bottom: 0; - border-width: 1px 0 0; -} - -.navbar-brand { - float: left; - height: 50px; - padding: 15px 15px; - font-size: 18px; - line-height: 20px; -} - -.navbar-brand:hover, -.navbar-brand:focus { - text-decoration: none; -} - -.navbar-brand > img { - display: block; -} - -@media (min-width: 768px) { - .navbar > .container .navbar-brand, - .navbar > .container-fluid .navbar-brand { - margin-left: -15px; - } -} - -.navbar-toggle { - position: relative; - float: right; - padding: 9px 10px; - margin-top: 8px; - margin-right: 15px; - margin-bottom: 8px; - background-color: transparent; - background-image: none; - border: 1px solid transparent; - border-radius: 4px; -} - -.navbar-toggle:focus { - outline: 0; -} - -.navbar-toggle .icon-bar { - display: block; - width: 22px; - height: 2px; - border-radius: 1px; -} - -.navbar-toggle .icon-bar + .icon-bar { - margin-top: 4px; -} - -@media (min-width: 768px) { - .navbar-toggle { - display: none; - } -} - -.navbar-nav { - margin: 7.5px -15px; -} - -.navbar-nav > li > a { - padding-top: 10px; - padding-bottom: 10px; - line-height: 20px; -} - -@media (max-width: 767px) { - .navbar-nav .open .dropdown-menu { - position: static; - float: none; - width: auto; - margin-top: 0; - background-color: transparent; - border: 0; - -webkit-box-shadow: none; - box-shadow: none; - } - - .navbar-nav .open .dropdown-menu > li > a, - .navbar-nav .open .dropdown-menu .dropdown-header { - padding: 5px 15px 5px 25px; - } - - .navbar-nav .open .dropdown-menu > li > a { - line-height: 20px; - } - - .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-nav .open .dropdown-menu > li > a:focus { - background-image: none; - } -} - -@media (min-width: 768px) { - .navbar-nav { - float: left; - margin: 0; - } - - .navbar-nav > li { - float: left; - } - - .navbar-nav > li > a { - padding-top: 15px; - padding-bottom: 15px; - } -} - -.navbar-form { - padding: 10px 15px; - margin-top: 8px; - margin-right: -15px; - margin-bottom: 8px; - margin-left: -15px; - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); -} - -@media (min-width: 768px) { - .navbar-form .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - - .navbar-form .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - - .navbar-form .form-control-static { - display: inline-block; - } - - .navbar-form .input-group { - display: inline-table; - vertical-align: middle; - } - - .navbar-form .input-group .input-group-addon, - .navbar-form .input-group .input-group-btn, - .navbar-form .input-group .form-control { - width: auto; - } - - .navbar-form .input-group > .form-control { - width: 100%; - } - - .navbar-form .control-label { - margin-bottom: 0; - vertical-align: middle; - } - - .navbar-form .radio, - .navbar-form .checkbox { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle; - } - - .navbar-form .radio label, - .navbar-form .checkbox label { - padding-left: 0; - } - - .navbar-form .radio input[type="radio"], - .navbar-form .checkbox input[type="checkbox"] { - position: relative; - margin-left: 0; - } - - .navbar-form .has-feedback .form-control-feedback { - top: 0; - } -} - -@media (max-width: 767px) { - .navbar-form .form-group { - margin-bottom: 5px; - } - - .navbar-form .form-group:last-child { - margin-bottom: 0; - } -} - -@media (min-width: 768px) { - .navbar-form { - width: auto; - padding-top: 0; - padding-bottom: 0; - margin-right: 0; - margin-left: 0; - border: 0; - -webkit-box-shadow: none; - box-shadow: none; - } -} - -.navbar-nav > li > .dropdown-menu { - margin-top: 0; - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { - margin-bottom: 0; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} - -.navbar-btn { - margin-top: 8px; - margin-bottom: 8px; -} - -.navbar-btn.btn-sm { - margin-top: 10px; - margin-bottom: 10px; -} - -.navbar-btn.btn-xs { - margin-top: 14px; - margin-bottom: 14px; -} - -.navbar-text { - margin-top: 15px; - margin-bottom: 15px; -} - -@media (min-width: 768px) { - .navbar-text { - float: left; - margin-right: 15px; - margin-left: 15px; - } -} - -@media (min-width: 768px) { - .navbar-left { - float: left !important; - } - - .navbar-right { - float: right !important; - margin-right: -15px; - } - - .navbar-right ~ .navbar-right { - margin-right: 0; - } -} - -.navbar-default { - background-color: #f8f8f8; - border-color: #e7e7e7; -} - -.navbar-default .navbar-brand { - color: #777; -} - -.navbar-default .navbar-brand:hover, -.navbar-default .navbar-brand:focus { - color: #5e5e5e; - background-color: transparent; -} - -.navbar-default .navbar-text { - color: #777; -} - -.navbar-default .navbar-nav > li > a { - color: #777; -} - -.navbar-default .navbar-nav > li > a:hover, -.navbar-default .navbar-nav > li > a:focus { - color: #333; - background-color: transparent; -} - -.navbar-default .navbar-nav > .active > a, -.navbar-default .navbar-nav > .active > a:hover, -.navbar-default .navbar-nav > .active > a:focus { - color: #555; - background-color: #e7e7e7; -} - -.navbar-default .navbar-nav > .disabled > a, -.navbar-default .navbar-nav > .disabled > a:hover, -.navbar-default .navbar-nav > .disabled > a:focus { - color: #ccc; - background-color: transparent; -} - -.navbar-default .navbar-toggle { - border-color: #ddd; -} - -.navbar-default .navbar-toggle:hover, -.navbar-default .navbar-toggle:focus { - background-color: #ddd; -} - -.navbar-default .navbar-toggle .icon-bar { - background-color: #888; -} - -.navbar-default .navbar-collapse, -.navbar-default .navbar-form { - border-color: #e7e7e7; -} - -.navbar-default .navbar-nav > .open > a, -.navbar-default .navbar-nav > .open > a:hover, -.navbar-default .navbar-nav > .open > a:focus { - color: #555; - background-color: #e7e7e7; -} - -@media (max-width: 767px) { - .navbar-default .navbar-nav .open .dropdown-menu > li > a { - color: #777; - } - - .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { - color: #333; - background-color: transparent; - } - - .navbar-default .navbar-nav .open .dropdown-menu > .active > a, - .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #555; - background-color: #e7e7e7; - } - - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #ccc; - background-color: transparent; - } -} - -.navbar-default .navbar-link { - color: #777; -} - -.navbar-default .navbar-link:hover { - color: #333; -} - -.navbar-default .btn-link { - color: #777; -} - -.navbar-default .btn-link:hover, -.navbar-default .btn-link:focus { - color: #333; -} - -.navbar-default .btn-link[disabled]:hover, -fieldset[disabled] .navbar-default .btn-link:hover, -.navbar-default .btn-link[disabled]:focus, -fieldset[disabled] .navbar-default .btn-link:focus { - color: #ccc; -} - -.navbar-inverse { - background-color: #222; - border-color: #080808; -} - -.navbar-inverse .navbar-brand { - color: #9d9d9d; -} - -.navbar-inverse .navbar-brand:hover, -.navbar-inverse .navbar-brand:focus { - color: #fff; - background-color: transparent; -} - -.navbar-inverse .navbar-text { - color: #9d9d9d; -} - -.navbar-inverse .navbar-nav > li > a { - color: #9d9d9d; -} - -.navbar-inverse .navbar-nav > li > a:hover, -.navbar-inverse .navbar-nav > li > a:focus { - color: #fff; - background-color: transparent; -} - -.navbar-inverse .navbar-nav > .active > a, -.navbar-inverse .navbar-nav > .active > a:hover, -.navbar-inverse .navbar-nav > .active > a:focus { - color: #fff; - background-color: #080808; -} - -.navbar-inverse .navbar-nav > .disabled > a, -.navbar-inverse .navbar-nav > .disabled > a:hover, -.navbar-inverse .navbar-nav > .disabled > a:focus { - color: #444; - background-color: transparent; -} - -.navbar-inverse .navbar-toggle { - border-color: #333; -} - -.navbar-inverse .navbar-toggle:hover, -.navbar-inverse .navbar-toggle:focus { - background-color: #333; -} - -.navbar-inverse .navbar-toggle .icon-bar { - background-color: #fff; -} - -.navbar-inverse .navbar-collapse, -.navbar-inverse .navbar-form { - border-color: #101010; -} - -.navbar-inverse .navbar-nav > .open > a, -.navbar-inverse .navbar-nav > .open > a:hover, -.navbar-inverse .navbar-nav > .open > a:focus { - color: #fff; - background-color: #080808; -} - -@media (max-width: 767px) { - .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { - border-color: #080808; - } - - .navbar-inverse .navbar-nav .open .dropdown-menu .divider { - background-color: #080808; - } - - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { - color: #9d9d9d; - } - - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { - color: #fff; - background-color: transparent; - } - - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #fff; - background-color: #080808; - } - - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #444; - background-color: transparent; - } -} - -.navbar-inverse .navbar-link { - color: #9d9d9d; -} - -.navbar-inverse .navbar-link:hover { - color: #fff; -} - -.navbar-inverse .btn-link { - color: #9d9d9d; -} - -.navbar-inverse .btn-link:hover, -.navbar-inverse .btn-link:focus { - color: #fff; -} - -.navbar-inverse .btn-link[disabled]:hover, -fieldset[disabled] .navbar-inverse .btn-link:hover, -.navbar-inverse .btn-link[disabled]:focus, -fieldset[disabled] .navbar-inverse .btn-link:focus { - color: #444; -} - -.breadcrumb { - padding: 8px 15px; - margin-bottom: 20px; - list-style: none; - background-color: #f5f5f5; - border-radius: 4px; -} - -.breadcrumb > li { - display: inline-block; -} - -.breadcrumb > li + li:before { - padding: 0 5px; - color: #ccc; - content: "/\00a0"; -} - -.breadcrumb > .active { - color: #777; -} - -.pagination { - display: inline-block; - padding-left: 0; - margin: 20px 0; - border-radius: 4px; -} - -.pagination > li { - display: inline; -} - -.pagination > li > a, -.pagination > li > span { - position: relative; - float: left; - padding: 6px 12px; - margin-left: -1px; - line-height: 1.42857143; - color: #337ab7; - text-decoration: none; - background-color: #fff; - border: 1px solid #ddd; -} - -.pagination > li:first-child > a, -.pagination > li:first-child > span { - margin-left: 0; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; -} - -.pagination > li:last-child > a, -.pagination > li:last-child > span { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; -} - -.pagination > li > a:hover, -.pagination > li > span:hover, -.pagination > li > a:focus, -.pagination > li > span:focus { - z-index: 2; - color: #23527c; - background-color: #eee; - border-color: #ddd; -} - -.pagination > .active > a, -.pagination > .active > span, -.pagination > .active > a:hover, -.pagination > .active > span:hover, -.pagination > .active > a:focus, -.pagination > .active > span:focus { - z-index: 3; - color: #fff; - cursor: default; - background-color: #337ab7; - border-color: #337ab7; -} - -.pagination > .disabled > span, -.pagination > .disabled > span:hover, -.pagination > .disabled > span:focus, -.pagination > .disabled > a, -.pagination > .disabled > a:hover, -.pagination > .disabled > a:focus { - color: #777; - cursor: not-allowed; - background-color: #fff; - border-color: #ddd; -} - -.pagination-lg > li > a, -.pagination-lg > li > span { - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; -} - -.pagination-lg > li:first-child > a, -.pagination-lg > li:first-child > span { - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; -} - -.pagination-lg > li:last-child > a, -.pagination-lg > li:last-child > span { - border-top-right-radius: 6px; - border-bottom-right-radius: 6px; -} - -.pagination-sm > li > a, -.pagination-sm > li > span { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; -} - -.pagination-sm > li:first-child > a, -.pagination-sm > li:first-child > span { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; -} - -.pagination-sm > li:last-child > a, -.pagination-sm > li:last-child > span { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; -} - -.pager { - padding-left: 0; - margin: 20px 0; - text-align: center; - list-style: none; -} - -.pager li { - display: inline; -} - -.pager li > a, -.pager li > span { - display: inline-block; - padding: 5px 14px; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 15px; -} - -.pager li > a:hover, -.pager li > a:focus { - text-decoration: none; - background-color: #eee; -} - -.pager .next > a, -.pager .next > span { - float: right; -} - -.pager .previous > a, -.pager .previous > span { - float: left; -} - -.pager .disabled > a, -.pager .disabled > a:hover, -.pager .disabled > a:focus, -.pager .disabled > span { - color: #777; - cursor: not-allowed; - background-color: #fff; -} - -.label { - display: inline; - padding: .2em .6em .3em; - font-size: 75%; - font-weight: bold; - line-height: 1; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: .25em; -} - -a.label:hover, -a.label:focus { - color: #fff; - text-decoration: none; - cursor: pointer; -} - -.label:empty { - display: none; -} - -.btn .label { - position: relative; - top: -1px; -} - -.label-default { - background-color: #777; -} - -.label-default[href]:hover, -.label-default[href]:focus { - background-color: #5e5e5e; -} - -.label-primary { - background-color: #337ab7; -} - -.label-primary[href]:hover, -.label-primary[href]:focus { - background-color: #286090; -} - -.label-success { - background-color: #5cb85c; -} - -.label-success[href]:hover, -.label-success[href]:focus { - background-color: #449d44; -} - -.label-info { - background-color: #5bc0de; -} - -.label-info[href]:hover, -.label-info[href]:focus { - background-color: #31b0d5; -} - -.label-warning { - background-color: #f0ad4e; -} - -.label-warning[href]:hover, -.label-warning[href]:focus { - background-color: #ec971f; -} - -.label-danger { - background-color: #d9534f; -} - -.label-danger[href]:hover, -.label-danger[href]:focus { - background-color: #c9302c; -} - -.badge { - display: inline-block; - min-width: 10px; - padding: 3px 7px; - font-size: 12px; - font-weight: bold; - line-height: 1; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: middle; - background-color: #777; - border-radius: 10px; -} - -.badge:empty { - display: none; -} - -.btn .badge { - position: relative; - top: -1px; -} - -.btn-xs .badge, -.btn-group-xs > .btn .badge { - top: 0; - padding: 1px 5px; -} - -a.badge:hover, -a.badge:focus { - color: #fff; - text-decoration: none; - cursor: pointer; -} - -.list-group-item.active > .badge, -.nav-pills > .active > a > .badge { - color: #337ab7; - background-color: #fff; -} - -.list-group-item > .badge { - float: right; -} - -.list-group-item > .badge + .badge { - margin-right: 5px; -} - -.nav-pills > li > a > .badge { - margin-left: 3px; -} - -.jumbotron { - padding-top: 30px; - padding-bottom: 30px; - margin-bottom: 30px; - color: inherit; - background-color: #eee; -} - -.jumbotron h1, -.jumbotron .h1 { - color: inherit; -} - -.jumbotron p { - margin-bottom: 15px; - font-size: 21px; - font-weight: 200; -} - -.jumbotron > hr { - border-top-color: #d5d5d5; -} - -.container .jumbotron, -.container-fluid .jumbotron { - padding-right: 15px; - padding-left: 15px; - border-radius: 6px; -} - -.jumbotron .container { - max-width: 100%; -} - -@media screen and (min-width: 768px) { - .jumbotron { - padding-top: 48px; - padding-bottom: 48px; - } - - .container .jumbotron, - .container-fluid .jumbotron { - padding-right: 60px; - padding-left: 60px; - } - - .jumbotron h1, - .jumbotron .h1 { - font-size: 63px; - } -} - -.thumbnail { - display: block; - padding: 4px; - margin-bottom: 20px; - line-height: 1.42857143; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 4px; - -webkit-transition: border .2s ease-in-out; - -o-transition: border .2s ease-in-out; - transition: border .2s ease-in-out; -} - -.thumbnail > img, -.thumbnail a > img { - margin-right: auto; - margin-left: auto; -} - -a.thumbnail:hover, -a.thumbnail:focus, -a.thumbnail.active { - border-color: #337ab7; -} - -.thumbnail .caption { - padding: 9px; - color: #333; -} - -.alert { - padding: 15px; - margin-bottom: 20px; - border: 1px solid transparent; - border-radius: 4px; -} - -.alert h4 { - margin-top: 0; - color: inherit; -} - -.alert .alert-link { - font-weight: bold; -} - -.alert > p, -.alert > ul { - margin-bottom: 0; -} - -.alert > p + p { - margin-top: 5px; -} - -.alert-dismissable, -.alert-dismissible { - padding-right: 35px; -} - -.alert-dismissable .close, -.alert-dismissible .close { - position: relative; - top: -2px; - right: -21px; - color: inherit; -} - -.alert-success { - color: #3c763d; - background-color: #dff0d8; - border-color: #d6e9c6; -} - -.alert-success hr { - border-top-color: #c9e2b3; -} - -.alert-success .alert-link { - color: #2b542c; -} - -.alert-info { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1; -} - -.alert-info hr { - border-top-color: #a6e1ec; -} - -.alert-info .alert-link { - color: #245269; -} - -.alert-warning { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc; -} - -.alert-warning hr { - border-top-color: #f7e1b5; -} - -.alert-warning .alert-link { - color: #66512c; -} - -.alert-danger { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1; -} - -.alert-danger hr { - border-top-color: #e4b9c0; -} - -.alert-danger .alert-link { - color: #843534; -} - -@-webkit-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -@-o-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -@keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -.progress { - height: 20px; - margin-bottom: 20px; - overflow: hidden; - background-color: #f5f5f5; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); -} - -.progress-bar { - float: left; - width: 0; - height: 100%; - font-size: 12px; - line-height: 20px; - color: #fff; - text-align: center; - background-color: #337ab7; - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); - -webkit-transition: width .6s ease; - -o-transition: width .6s ease; - transition: width .6s ease; -} - -.progress-striped .progress-bar, -.progress-bar-striped { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - -webkit-background-size: 40px 40px; - background-size: 40px 40px; -} - -.progress.active .progress-bar, -.progress-bar.active { - -webkit-animation: progress-bar-stripes 2s linear infinite; - -o-animation: progress-bar-stripes 2s linear infinite; - animation: progress-bar-stripes 2s linear infinite; -} - -.progress-bar-success { - background-color: #5cb85c; -} - -.progress-striped .progress-bar-success { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} - -.progress-bar-info { - background-color: #5bc0de; -} - -.progress-striped .progress-bar-info { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} - -.progress-bar-warning { - background-color: #f0ad4e; -} - -.progress-striped .progress-bar-warning { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} - -.progress-bar-danger { - background-color: #d9534f; -} - -.progress-striped .progress-bar-danger { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} - -.media { - margin-top: 15px; -} - -.media:first-child { - margin-top: 0; -} - -.media, -.media-body { - overflow: hidden; - zoom: 1; -} - -.media-body { - width: 10000px; -} - -.media-object { - display: block; -} - -.media-object.img-thumbnail { - max-width: none; -} - -.media-right, -.media > .pull-right { - padding-left: 10px; -} - -.media-left, -.media > .pull-left { - padding-right: 10px; -} - -.media-left, -.media-right, -.media-body { - display: table-cell; - vertical-align: top; -} - -.media-middle { - vertical-align: middle; -} - -.media-bottom { - vertical-align: bottom; -} - -.media-heading { - margin-top: 0; - margin-bottom: 5px; -} - -.media-list { - padding-left: 0; - list-style: none; -} - -.list-group { - padding-left: 0; - margin-bottom: 20px; -} - -.list-group-item { - position: relative; - display: block; - padding: 10px 15px; - margin-bottom: -1px; - background-color: #fff; - border: 1px solid #ddd; -} - -.list-group-item:first-child { - border-top-left-radius: 4px; - border-top-right-radius: 4px; -} - -.list-group-item:last-child { - margin-bottom: 0; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; -} - -a.list-group-item, -button.list-group-item { - color: #555; -} - -a.list-group-item .list-group-item-heading, -button.list-group-item .list-group-item-heading { - color: #333; -} - -a.list-group-item:hover, -button.list-group-item:hover, -a.list-group-item:focus, -button.list-group-item:focus { - color: #555; - text-decoration: none; - background-color: #f5f5f5; -} - -button.list-group-item { - width: 100%; - text-align: left; -} - -.list-group-item.disabled, -.list-group-item.disabled:hover, -.list-group-item.disabled:focus { - color: #777; - cursor: not-allowed; - background-color: #eee; -} - -.list-group-item.disabled .list-group-item-heading, -.list-group-item.disabled:hover .list-group-item-heading, -.list-group-item.disabled:focus .list-group-item-heading { - color: inherit; -} - -.list-group-item.disabled .list-group-item-text, -.list-group-item.disabled:hover .list-group-item-text, -.list-group-item.disabled:focus .list-group-item-text { - color: #777; -} - -.list-group-item.active, -.list-group-item.active:hover, -.list-group-item.active:focus { - z-index: 2; - color: #fff; - background-color: #337ab7; - border-color: #337ab7; -} - -.list-group-item.active .list-group-item-heading, -.list-group-item.active:hover .list-group-item-heading, -.list-group-item.active:focus .list-group-item-heading, -.list-group-item.active .list-group-item-heading > small, -.list-group-item.active:hover .list-group-item-heading > small, -.list-group-item.active:focus .list-group-item-heading > small, -.list-group-item.active .list-group-item-heading > .small, -.list-group-item.active:hover .list-group-item-heading > .small, -.list-group-item.active:focus .list-group-item-heading > .small { - color: inherit; -} - -.list-group-item.active .list-group-item-text, -.list-group-item.active:hover .list-group-item-text, -.list-group-item.active:focus .list-group-item-text { - color: #c7ddef; -} - -.list-group-item-success { - color: #3c763d; - background-color: #dff0d8; -} - -a.list-group-item-success, -button.list-group-item-success { - color: #3c763d; -} - -a.list-group-item-success .list-group-item-heading, -button.list-group-item-success .list-group-item-heading { - color: inherit; -} - -a.list-group-item-success:hover, -button.list-group-item-success:hover, -a.list-group-item-success:focus, -button.list-group-item-success:focus { - color: #3c763d; - background-color: #d0e9c6; -} - -a.list-group-item-success.active, -button.list-group-item-success.active, -a.list-group-item-success.active:hover, -button.list-group-item-success.active:hover, -a.list-group-item-success.active:focus, -button.list-group-item-success.active:focus { - color: #fff; - background-color: #3c763d; - border-color: #3c763d; -} - -.list-group-item-info { - color: #31708f; - background-color: #d9edf7; -} - -a.list-group-item-info, -button.list-group-item-info { - color: #31708f; -} - -a.list-group-item-info .list-group-item-heading, -button.list-group-item-info .list-group-item-heading { - color: inherit; -} - -a.list-group-item-info:hover, -button.list-group-item-info:hover, -a.list-group-item-info:focus, -button.list-group-item-info:focus { - color: #31708f; - background-color: #c4e3f3; -} - -a.list-group-item-info.active, -button.list-group-item-info.active, -a.list-group-item-info.active:hover, -button.list-group-item-info.active:hover, -a.list-group-item-info.active:focus, -button.list-group-item-info.active:focus { - color: #fff; - background-color: #31708f; - border-color: #31708f; -} - -.list-group-item-warning { - color: #8a6d3b; - background-color: #fcf8e3; -} - -a.list-group-item-warning, -button.list-group-item-warning { - color: #8a6d3b; -} - -a.list-group-item-warning .list-group-item-heading, -button.list-group-item-warning .list-group-item-heading { - color: inherit; -} - -a.list-group-item-warning:hover, -button.list-group-item-warning:hover, -a.list-group-item-warning:focus, -button.list-group-item-warning:focus { - color: #8a6d3b; - background-color: #faf2cc; -} - -a.list-group-item-warning.active, -button.list-group-item-warning.active, -a.list-group-item-warning.active:hover, -button.list-group-item-warning.active:hover, -a.list-group-item-warning.active:focus, -button.list-group-item-warning.active:focus { - color: #fff; - background-color: #8a6d3b; - border-color: #8a6d3b; -} - -.list-group-item-danger { - color: #a94442; - background-color: #f2dede; -} - -a.list-group-item-danger, -button.list-group-item-danger { - color: #a94442; -} - -a.list-group-item-danger .list-group-item-heading, -button.list-group-item-danger .list-group-item-heading { - color: inherit; -} - -a.list-group-item-danger:hover, -button.list-group-item-danger:hover, -a.list-group-item-danger:focus, -button.list-group-item-danger:focus { - color: #a94442; - background-color: #ebcccc; -} - -a.list-group-item-danger.active, -button.list-group-item-danger.active, -a.list-group-item-danger.active:hover, -button.list-group-item-danger.active:hover, -a.list-group-item-danger.active:focus, -button.list-group-item-danger.active:focus { - color: #fff; - background-color: #a94442; - border-color: #a94442; -} - -.list-group-item-heading { - margin-top: 0; - margin-bottom: 5px; -} - -.list-group-item-text { - margin-bottom: 0; - line-height: 1.3; -} - -.panel { - margin-bottom: 20px; - background-color: #fff; - border: 1px solid transparent; - border-radius: 4px; - -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); - box-shadow: 0 1px 1px rgba(0, 0, 0, .05); -} - -.panel-body { - padding: 15px; -} - -.panel-heading { - padding: 10px 15px; - border-bottom: 1px solid transparent; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} - -.panel-heading > .dropdown .dropdown-toggle { - color: inherit; -} - -.panel-title { - margin-top: 0; - margin-bottom: 0; - font-size: 16px; - color: inherit; -} - -.panel-title > a, -.panel-title > small, -.panel-title > .small, -.panel-title > small > a, -.panel-title > .small > a { - color: inherit; -} - -.panel-footer { - padding: 10px 15px; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} - -.panel > .list-group, -.panel > .panel-collapse > .list-group { - margin-bottom: 0; -} - -.panel > .list-group .list-group-item, -.panel > .panel-collapse > .list-group .list-group-item { - border-width: 1px 0; - border-radius: 0; -} - -.panel > .list-group:first-child .list-group-item:first-child, -.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { - border-top: 0; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} - -.panel > .list-group:last-child .list-group-item:last-child, -.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { - border-bottom: 0; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} - -.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.panel-heading + .list-group .list-group-item:first-child { - border-top-width: 0; -} - -.list-group + .panel-footer { - border-top-width: 0; -} - -.panel > .table, -.panel > .table-responsive > .table, -.panel > .panel-collapse > .table { - margin-bottom: 0; -} - -.panel > .table caption, -.panel > .table-responsive > .table caption, -.panel > .panel-collapse > .table caption { - padding-right: 15px; - padding-left: 15px; -} - -.panel > .table:first-child, -.panel > .table-responsive:first-child > .table:first-child { - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} - -.panel > .table:first-child > thead:first-child > tr:first-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, -.panel > .table:first-child > tbody:first-child > tr:first-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} - -.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, -.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, -.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, -.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { - border-top-left-radius: 3px; -} - -.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, -.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, -.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, -.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { - border-top-right-radius: 3px; -} - -.panel > .table:last-child, -.panel > .table-responsive:last-child > .table:last-child { - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} - -.panel > .table:last-child > tbody:last-child > tr:last-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} - -.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, -.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { - border-bottom-left-radius: 3px; -} - -.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, -.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { - border-bottom-right-radius: 3px; -} - -.panel > .panel-body + .table, -.panel > .panel-body + .table-responsive, -.panel > .table + .panel-body, -.panel > .table-responsive + .panel-body { - border-top: 1px solid #ddd; -} - -.panel > .table > tbody:first-child > tr:first-child th, -.panel > .table > tbody:first-child > tr:first-child td { - border-top: 0; -} - -.panel > .table-bordered, -.panel > .table-responsive > .table-bordered { - border: 0; -} - -.panel > .table-bordered > thead > tr > th:first-child, -.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, -.panel > .table-bordered > tbody > tr > th:first-child, -.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, -.panel > .table-bordered > tfoot > tr > th:first-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, -.panel > .table-bordered > thead > tr > td:first-child, -.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, -.panel > .table-bordered > tbody > tr > td:first-child, -.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, -.panel > .table-bordered > tfoot > tr > td:first-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { - border-left: 0; -} - -.panel > .table-bordered > thead > tr > th:last-child, -.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, -.panel > .table-bordered > tbody > tr > th:last-child, -.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, -.panel > .table-bordered > tfoot > tr > th:last-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, -.panel > .table-bordered > thead > tr > td:last-child, -.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, -.panel > .table-bordered > tbody > tr > td:last-child, -.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, -.panel > .table-bordered > tfoot > tr > td:last-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { - border-right: 0; -} - -.panel > .table-bordered > thead > tr:first-child > td, -.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, -.panel > .table-bordered > tbody > tr:first-child > td, -.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, -.panel > .table-bordered > thead > tr:first-child > th, -.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, -.panel > .table-bordered > tbody > tr:first-child > th, -.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { - border-bottom: 0; -} - -.panel > .table-bordered > tbody > tr:last-child > td, -.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, -.panel > .table-bordered > tfoot > tr:last-child > td, -.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, -.panel > .table-bordered > tbody > tr:last-child > th, -.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, -.panel > .table-bordered > tfoot > tr:last-child > th, -.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { - border-bottom: 0; -} - -.panel > .table-responsive { - margin-bottom: 0; - border: 0; -} - -.panel-group { - margin-bottom: 20px; -} - -.panel-group .panel { - margin-bottom: 0; - border-radius: 4px; -} - -.panel-group .panel + .panel { - margin-top: 5px; -} - -.panel-group .panel-heading { - border-bottom: 0; -} - -.panel-group .panel-heading + .panel-collapse > .panel-body, -.panel-group .panel-heading + .panel-collapse > .list-group { - border-top: 1px solid #ddd; -} - -.panel-group .panel-footer { - border-top: 0; -} - -.panel-group .panel-footer + .panel-collapse .panel-body { - border-bottom: 1px solid #ddd; -} - -.panel-default { - border-color: #ddd; -} - -.panel-default > .panel-heading { - color: #333; - background-color: #f5f5f5; - border-color: #ddd; -} - -.panel-default > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #ddd; -} - -.panel-default > .panel-heading .badge { - color: #f5f5f5; - background-color: #333; -} - -.panel-default > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #ddd; -} - -.panel-primary { - border-color: #337ab7; -} - -.panel-primary > .panel-heading { - color: #fff; - background-color: #337ab7; - border-color: #337ab7; -} - -.panel-primary > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #337ab7; -} - -.panel-primary > .panel-heading .badge { - color: #337ab7; - background-color: #fff; -} - -.panel-primary > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #337ab7; -} - -.panel-success { - border-color: #d6e9c6; -} - -.panel-success > .panel-heading { - color: #3c763d; - background-color: #dff0d8; - border-color: #d6e9c6; -} - -.panel-success > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #d6e9c6; -} - -.panel-success > .panel-heading .badge { - color: #dff0d8; - background-color: #3c763d; -} - -.panel-success > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #d6e9c6; -} - -.panel-info { - border-color: #bce8f1; -} - -.panel-info > .panel-heading { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1; -} - -.panel-info > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #bce8f1; -} - -.panel-info > .panel-heading .badge { - color: #d9edf7; - background-color: #31708f; -} - -.panel-info > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #bce8f1; -} - -.panel-warning { - border-color: #faebcc; -} - -.panel-warning > .panel-heading { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc; -} - -.panel-warning > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #faebcc; -} - -.panel-warning > .panel-heading .badge { - color: #fcf8e3; - background-color: #8a6d3b; -} - -.panel-warning > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #faebcc; -} - -.panel-danger { - border-color: #ebccd1; -} - -.panel-danger > .panel-heading { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1; -} - -.panel-danger > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #ebccd1; -} - -.panel-danger > .panel-heading .badge { - color: #f2dede; - background-color: #a94442; -} - -.panel-danger > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #ebccd1; -} - -.embed-responsive { - position: relative; - display: block; - height: 0; - padding: 0; - overflow: hidden; -} - -.embed-responsive .embed-responsive-item, -.embed-responsive iframe, -.embed-responsive embed, -.embed-responsive object, -.embed-responsive video { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - border: 0; -} - -.embed-responsive-16by9 { - padding-bottom: 56.25%; -} - -.embed-responsive-4by3 { - padding-bottom: 75%; -} - -.well { - min-height: 20px; - padding: 19px; - margin-bottom: 20px; - background-color: #f5f5f5; - border: 1px solid #e3e3e3; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); -} - -.well blockquote { - border-color: #ddd; - border-color: rgba(0, 0, 0, .15); -} - -.well-lg { - padding: 24px; - border-radius: 6px; -} - -.well-sm { - padding: 9px; - border-radius: 3px; -} - -.close { - float: right; - font-size: 21px; - font-weight: bold; - line-height: 1; - color: #000; - text-shadow: 0 1px 0 #fff; - filter: alpha(opacity=20); - opacity: .2; -} - -.close:hover, -.close:focus { - color: #000; - text-decoration: none; - cursor: pointer; - filter: alpha(opacity=50); - opacity: .5; -} - -button.close { - -webkit-appearance: none; - padding: 0; - cursor: pointer; - background: transparent; - border: 0; -} - -.modal-open { - overflow: hidden; -} - -.modal { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1050; - display: none; - overflow: hidden; - -webkit-overflow-scrolling: touch; - outline: 0; -} - -.modal.fade .modal-dialog { - -webkit-transition: -webkit-transform .3s ease-out; - -o-transition: -o-transform .3s ease-out; - transition: transform .3s ease-out; - -webkit-transform: translate(0, -25%); - -ms-transform: translate(0, -25%); - -o-transform: translate(0, -25%); - transform: translate(0, -25%); -} - -.modal.in .modal-dialog { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); -} - -.modal-open .modal { - overflow-x: hidden; - overflow-y: auto; -} - -.modal-dialog { - position: relative; - width: auto; - margin: 10px; -} - -.modal-content { - position: relative; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #999; - border: 1px solid rgba(0, 0, 0, .2); - border-radius: 6px; - outline: 0; - -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); - box-shadow: 0 3px 9px rgba(0, 0, 0, .5); -} - -.modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1040; - background-color: #000; -} - -.modal-backdrop.fade { - filter: alpha(opacity=0); - opacity: 0; -} - -.modal-backdrop.in { - filter: alpha(opacity=50); - opacity: .5; -} - -.modal-header { - padding: 15px; - border-bottom: 1px solid #e5e5e5; -} - -.modal-header .close { - margin-top: -2px; -} - -.modal-title { - margin: 0; - line-height: 1.42857143; -} - -.modal-body { - position: relative; - padding: 15px; -} - -.modal-footer { - padding: 15px; - text-align: right; - border-top: 1px solid #e5e5e5; -} - -.modal-footer .btn + .btn { - margin-bottom: 0; - margin-left: 5px; -} - -.modal-footer .btn-group .btn + .btn { - margin-left: -1px; -} - -.modal-footer .btn-block + .btn-block { - margin-left: 0; -} - -.modal-scrollbar-measure { - position: absolute; - top: -9999px; - width: 50px; - height: 50px; - overflow: scroll; -} - -@media (min-width: 768px) { - .modal-dialog { - width: 600px; - margin: 30px auto; - } - - .modal-content { - -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); - box-shadow: 0 5px 15px rgba(0, 0, 0, .5); - } - - .modal-sm { - width: 300px; - } -} - -@media (min-width: 992px) { - .modal-lg { - width: 900px; - } -} - -.tooltip { - position: absolute; - z-index: 1070; - display: block; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 12px; - font-style: normal; - font-weight: normal; - line-height: 1.42857143; - text-align: left; - text-align: start; - text-decoration: none; - text-shadow: none; - text-transform: none; - letter-spacing: normal; - word-break: normal; - word-spacing: normal; - word-wrap: normal; - white-space: normal; - filter: alpha(opacity=0); - opacity: 0; - - line-break: auto; -} - -.tooltip.in { - filter: alpha(opacity=90); - opacity: .9; -} - -.tooltip.top { - padding: 5px 0; - margin-top: -3px; -} - -.tooltip.right { - padding: 0 5px; - margin-left: 3px; -} - -.tooltip.bottom { - padding: 5px 0; - margin-top: 3px; -} - -.tooltip.left { - padding: 0 5px; - margin-left: -3px; -} - -.tooltip-inner { - max-width: 200px; - padding: 3px 8px; - color: #fff; - text-align: center; - background-color: #000; - border-radius: 4px; -} - -.tooltip-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} - -.tooltip.top .tooltip-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-width: 5px 5px 0; - border-top-color: #000; -} - -.tooltip.top-left .tooltip-arrow { - right: 5px; - bottom: 0; - margin-bottom: -5px; - border-width: 5px 5px 0; - border-top-color: #000; -} - -.tooltip.top-right .tooltip-arrow { - bottom: 0; - left: 5px; - margin-bottom: -5px; - border-width: 5px 5px 0; - border-top-color: #000; -} - -.tooltip.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-width: 5px 5px 5px 0; - border-right-color: #000; -} - -.tooltip.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-width: 5px 0 5px 5px; - border-left-color: #000; -} - -.tooltip.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} - -.tooltip.bottom-left .tooltip-arrow { - top: 0; - right: 5px; - margin-top: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} - -.tooltip.bottom-right .tooltip-arrow { - top: 0; - left: 5px; - margin-top: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} - -.popover { - position: absolute; - top: 0; - left: 0; - z-index: 1060; - display: none; - max-width: 276px; - padding: 1px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - font-style: normal; - font-weight: normal; - line-height: 1.42857143; - text-align: left; - text-align: start; - text-decoration: none; - text-shadow: none; - text-transform: none; - letter-spacing: normal; - word-break: normal; - word-spacing: normal; - word-wrap: normal; - white-space: normal; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, .2); - border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); - box-shadow: 0 5px 10px rgba(0, 0, 0, .2); - - line-break: auto; -} - -.popover.top { - margin-top: -10px; -} - -.popover.right { - margin-left: 10px; -} - -.popover.bottom { - margin-top: 10px; -} - -.popover.left { - margin-left: -10px; -} - -.popover-title { - padding: 8px 14px; - margin: 0; - font-size: 14px; - background-color: #f7f7f7; - border-bottom: 1px solid #ebebeb; - border-radius: 5px 5px 0 0; -} - -.popover-content { - padding: 9px 14px; -} - -.popover > .arrow, -.popover > .arrow:after { - position: absolute; - display: block; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} - -.popover > .arrow { - border-width: 11px; -} - -.popover > .arrow:after { - content: ""; - border-width: 10px; -} - -.popover.top > .arrow { - bottom: -11px; - left: 50%; - margin-left: -11px; - border-top-color: #999; - border-top-color: rgba(0, 0, 0, .25); - border-bottom-width: 0; -} - -.popover.top > .arrow:after { - bottom: 1px; - margin-left: -10px; - content: " "; - border-top-color: #fff; - border-bottom-width: 0; -} - -.popover.right > .arrow { - top: 50%; - left: -11px; - margin-top: -11px; - border-right-color: #999; - border-right-color: rgba(0, 0, 0, .25); - border-left-width: 0; -} - -.popover.right > .arrow:after { - bottom: -10px; - left: 1px; - content: " "; - border-right-color: #fff; - border-left-width: 0; -} - -.popover.bottom > .arrow { - top: -11px; - left: 50%; - margin-left: -11px; - border-top-width: 0; - border-bottom-color: #999; - border-bottom-color: rgba(0, 0, 0, .25); -} - -.popover.bottom > .arrow:after { - top: 1px; - margin-left: -10px; - content: " "; - border-top-width: 0; - border-bottom-color: #fff; -} - -.popover.left > .arrow { - top: 50%; - right: -11px; - margin-top: -11px; - border-right-width: 0; - border-left-color: #999; - border-left-color: rgba(0, 0, 0, .25); -} - -.popover.left > .arrow:after { - right: 1px; - bottom: -10px; - content: " "; - border-right-width: 0; - border-left-color: #fff; -} - -.carousel { - position: relative; -} - -.carousel-inner { - position: relative; - width: 100%; - overflow: hidden; -} - -.carousel-inner > .item { - position: relative; - display: none; - -webkit-transition: .6s ease-in-out left; - -o-transition: .6s ease-in-out left; - transition: .6s ease-in-out left; -} - -.carousel-inner > .item > img, -.carousel-inner > .item > a > img { - line-height: 1; -} - -@media all and (transform-3d), (-webkit-transform-3d) { - .carousel-inner > .item { - -webkit-transition: -webkit-transform .6s ease-in-out; - -o-transition: -o-transform .6s ease-in-out; - transition: transform .6s ease-in-out; - - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - -webkit-perspective: 1000px; - perspective: 1000px; - } - - .carousel-inner > .item.next, - .carousel-inner > .item.active.right { - left: 0; - -webkit-transform: translate3d(100%, 0, 0); - transform: translate3d(100%, 0, 0); - } - - .carousel-inner > .item.prev, - .carousel-inner > .item.active.left { - left: 0; - -webkit-transform: translate3d(-100%, 0, 0); - transform: translate3d(-100%, 0, 0); - } - - .carousel-inner > .item.next.left, - .carousel-inner > .item.prev.right, - .carousel-inner > .item.active { - left: 0; - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } -} - -.carousel-inner > .active, -.carousel-inner > .next, -.carousel-inner > .prev { - display: block; -} - -.carousel-inner > .active { - left: 0; -} - -.carousel-inner > .next, -.carousel-inner > .prev { - position: absolute; - top: 0; - width: 100%; -} - -.carousel-inner > .next { - left: 100%; -} - -.carousel-inner > .prev { - left: -100%; -} - -.carousel-inner > .next.left, -.carousel-inner > .prev.right { - left: 0; -} - -.carousel-inner > .active.left { - left: -100%; -} - -.carousel-inner > .active.right { - left: 100%; -} - -.carousel-control { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 15%; - font-size: 20px; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, .6); - background-color: rgba(0, 0, 0, 0); - filter: alpha(opacity=50); - opacity: .5; -} - -.carousel-control.left { - background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); - background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); - background-repeat: repeat-x; -} - -.carousel-control.right { - right: 0; - left: auto; - background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); - background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); - background-repeat: repeat-x; -} - -.carousel-control:hover, -.carousel-control:focus { - color: #fff; - text-decoration: none; - filter: alpha(opacity=90); - outline: 0; - opacity: .9; -} - -.carousel-control .icon-prev, -.carousel-control .icon-next, -.carousel-control .glyphicon-chevron-left, -.carousel-control .glyphicon-chevron-right { - position: absolute; - top: 50%; - z-index: 5; - display: inline-block; - margin-top: -10px; -} - -.carousel-control .icon-prev, -.carousel-control .glyphicon-chevron-left { - left: 50%; - margin-left: -10px; -} - -.carousel-control .icon-next, -.carousel-control .glyphicon-chevron-right { - right: 50%; - margin-right: -10px; -} - -.carousel-control .icon-prev, -.carousel-control .icon-next { - width: 20px; - height: 20px; - font-family: serif; - line-height: 1; -} - -.carousel-control .icon-prev:before { - content: '\2039'; -} - -.carousel-control .icon-next:before { - content: '\203a'; -} - -.carousel-indicators { - position: absolute; - bottom: 10px; - left: 50%; - z-index: 15; - width: 60%; - padding-left: 0; - margin-left: -30%; - text-align: center; - list-style: none; -} - -.carousel-indicators li { - display: inline-block; - width: 10px; - height: 10px; - margin: 1px; - text-indent: -999px; - cursor: pointer; - background-color: #000 \9; - background-color: rgba(0, 0, 0, 0); - border: 1px solid #fff; - border-radius: 10px; -} - -.carousel-indicators .active { - width: 12px; - height: 12px; - margin: 0; - background-color: #fff; -} - -.carousel-caption { - position: absolute; - right: 15%; - bottom: 20px; - left: 15%; - z-index: 10; - padding-top: 20px; - padding-bottom: 20px; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, .6); -} - -.carousel-caption .btn { - text-shadow: none; -} - -@media screen and (min-width: 768px) { - .carousel-control .glyphicon-chevron-left, - .carousel-control .glyphicon-chevron-right, - .carousel-control .icon-prev, - .carousel-control .icon-next { - width: 30px; - height: 30px; - margin-top: -10px; - font-size: 30px; - } - - .carousel-control .glyphicon-chevron-left, - .carousel-control .icon-prev { - margin-left: -10px; - } - - .carousel-control .glyphicon-chevron-right, - .carousel-control .icon-next { - margin-right: -10px; - } - - .carousel-caption { - right: 20%; - left: 20%; - padding-bottom: 30px; - } - - .carousel-indicators { - bottom: 20px; - } -} - -.clearfix:before, -.clearfix:after, -.dl-horizontal dd:before, -.dl-horizontal dd:after, -.container:before, -.container:after, -.container-fluid:before, -.container-fluid:after, -.row:before, -.row:after, -.form-horizontal .form-group:before, -.form-horizontal .form-group:after, -.btn-toolbar:before, -.btn-toolbar:after, -.btn-group-vertical > .btn-group:before, -.btn-group-vertical > .btn-group:after, -.nav:before, -.nav:after, -.navbar:before, -.navbar:after, -.navbar-header:before, -.navbar-header:after, -.navbar-collapse:before, -.navbar-collapse:after, -.pager:before, -.pager:after, -.panel-body:before, -.panel-body:after, -.modal-header:before, -.modal-header:after, -.modal-footer:before, -.modal-footer:after { - display: table; - content: " "; -} - -.clearfix:after, -.dl-horizontal dd:after, -.container:after, -.container-fluid:after, -.row:after, -.form-horizontal .form-group:after, -.btn-toolbar:after, -.btn-group-vertical > .btn-group:after, -.nav:after, -.navbar:after, -.navbar-header:after, -.navbar-collapse:after, -.pager:after, -.panel-body:after, -.modal-header:after, -.modal-footer:after { - clear: both; -} - -.center-block { - display: block; - margin-right: auto; - margin-left: auto; -} - -.pull-right { - float: right !important; -} - -.pull-left { - float: left !important; -} - -.hide { - display: none !important; -} - -.show { - display: block !important; -} - -.invisible { - visibility: hidden; -} - -.text-hide { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; -} - -.hidden { - display: none !important; -} - -.affix { - position: fixed; -} - -@-ms-viewport { - width: device-width; -} - -.visible-xs, -.visible-sm, -.visible-md, -.visible-lg { - display: none !important; -} - -.visible-xs-block, -.visible-xs-inline, -.visible-xs-inline-block, -.visible-sm-block, -.visible-sm-inline, -.visible-sm-inline-block, -.visible-md-block, -.visible-md-inline, -.visible-md-inline-block, -.visible-lg-block, -.visible-lg-inline, -.visible-lg-inline-block { - display: none !important; -} - -@media (max-width: 767px) { - .visible-xs { - display: block !important; - } - - table.visible-xs { - display: table !important; - } - - tr.visible-xs { - display: table-row !important; - } - - th.visible-xs, - td.visible-xs { - display: table-cell !important; - } -} - -@media (max-width: 767px) { - .visible-xs-block { - display: block !important; - } -} - -@media (max-width: 767px) { - .visible-xs-inline { - display: inline !important; - } -} - -@media (max-width: 767px) { - .visible-xs-inline-block { - display: inline-block !important; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm { - display: block !important; - } - - table.visible-sm { - display: table !important; - } - - tr.visible-sm { - display: table-row !important; - } - - th.visible-sm, - td.visible-sm { - display: table-cell !important; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-block { - display: block !important; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-inline { - display: inline !important; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-inline-block { - display: inline-block !important; - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md { - display: block !important; - } - - table.visible-md { - display: table !important; - } - - tr.visible-md { - display: table-row !important; - } - - th.visible-md, - td.visible-md { - display: table-cell !important; - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-block { - display: block !important; - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-inline { - display: inline !important; - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-inline-block { - display: inline-block !important; - } -} - -@media (min-width: 1200px) { - .visible-lg { - display: block !important; - } - - table.visible-lg { - display: table !important; - } - - tr.visible-lg { - display: table-row !important; - } - - th.visible-lg, - td.visible-lg { - display: table-cell !important; - } -} - -@media (min-width: 1200px) { - .visible-lg-block { - display: block !important; - } -} - -@media (min-width: 1200px) { - .visible-lg-inline { - display: inline !important; - } -} - -@media (min-width: 1200px) { - .visible-lg-inline-block { - display: inline-block !important; - } -} - -@media (max-width: 767px) { - .hidden-xs { - display: none !important; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .hidden-sm { - display: none !important; - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .hidden-md { - display: none !important; - } -} - -@media (min-width: 1200px) { - .hidden-lg { - display: none !important; - } -} - -.visible-print { - display: none !important; -} - -@media print { - .visible-print { - display: block !important; - } - - table.visible-print { - display: table !important; - } - - tr.visible-print { - display: table-row !important; - } - - th.visible-print, - td.visible-print { - display: table-cell !important; - } -} - -.visible-print-block { - display: none !important; -} - -@media print { - .visible-print-block { - display: block !important; - } -} - -.visible-print-inline { - display: none !important; -} - -@media print { - .visible-print-inline { - display: inline !important; - } -} - -.visible-print-inline-block { - display: none !important; -} - -@media print { - .visible-print-inline-block { - display: inline-block !important; - } -} - -@media print { - .hidden-print { - display: none !important; - } -} - -/*# sourceMappingURL=bootstrap.css.map */ diff --git a/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap.css.map b/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap.css.map deleted file mode 100644 index 7314b4ef..00000000 --- a/spring-cloud-learn/tx-manager/src/main/resources/static/static/bootstrap/css/bootstrap.css.map +++ /dev/null @@ -1,142 +0,0 @@ -{ - "version": 3, - "sources": [ - "bootstrap.css", - "less/normalize.less", - "less/print.less", - "less/glyphicons.less", - "less/scaffolding.less", - "less/mixins/vendor-prefixes.less", - "less/mixins/tab-focus.less", - "less/mixins/image.less", - "less/type.less", - "less/mixins/text-emphasis.less", - "less/mixins/background-variant.less", - "less/mixins/text-overflow.less", - "less/code.less", - "less/grid.less", - "less/mixins/grid.less", - "less/mixins/grid-framework.less", - "less/tables.less", - "less/mixins/table-row.less", - "less/forms.less", - "less/mixins/forms.less", - "less/buttons.less", - "less/mixins/buttons.less", - "less/mixins/opacity.less", - "less/component-animations.less", - "less/dropdowns.less", - "less/mixins/nav-divider.less", - "less/mixins/reset-filter.less", - "less/button-groups.less", - "less/mixins/border-radius.less", - "less/input-groups.less", - "less/navs.less", - "less/navbar.less", - "less/mixins/nav-vertical-align.less", - "less/utilities.less", - "less/breadcrumbs.less", - "less/pagination.less", - "less/mixins/pagination.less", - "less/pager.less", - "less/labels.less", - "less/mixins/labels.less", - "less/badges.less", - "less/jumbotron.less", - "less/thumbnails.less", - "less/alerts.less", - "less/mixins/alerts.less", - "less/progress-bars.less", - "less/mixins/gradients.less", - "less/mixins/progress-bar.less", - "less/media.less", - "less/list-group.less", - "less/mixins/list-group.less", - "less/panels.less", - "less/mixins/panels.less", - "less/responsive-embed.less", - "less/wells.less", - "less/close.less", - "less/modals.less", - "less/tooltip.less", - "less/mixins/reset-text.less", - "less/popovers.less", - "less/carousel.less", - "less/mixins/clearfix.less", - "less/mixins/center-block.less", - "less/mixins/hide-text.less", - "less/responsive-utilities.less", - "less/mixins/responsive-visibility.less" - ], - "names": [], - "mappings": "AAAA;;;;GAIG;AACH,4EAA4E;ACG5E;EACE,wBAAA;EACA,2BAAA;EACA,+BAAA;CDDD;ACQD;EACE,UAAA;CDND;ACmBD;;;;;;;;;;;;;EAaE,eAAA;CDjBD;ACyBD;;;;EAIE,sBAAA;EACA,yBAAA;CDvBD;AC+BD;EACE,cAAA;EACA,UAAA;CD7BD;ACqCD;;EAEE,cAAA;CDnCD;AC6CD;EACE,8BAAA;CD3CD;ACmDD;;EAEE,WAAA;CDjDD;AC2DD;EACE,0BAAA;CDzDD;ACgED;;EAEE,kBAAA;CD9DD;ACqED;EACE,mBAAA;CDnED;AC2ED;EACE,eAAA;EACA,iBAAA;CDzED;ACgFD;EACE,iBAAA;EACA,YAAA;CD9ED;ACqFD;EACE,eAAA;CDnFD;AC0FD;;EAEE,eAAA;EACA,eAAA;EACA,mBAAA;EACA,yBAAA;CDxFD;AC2FD;EACE,YAAA;CDzFD;AC4FD;EACE,gBAAA;CD1FD;ACoGD;EACE,UAAA;CDlGD;ACyGD;EACE,iBAAA;CDvGD;ACiHD;EACE,iBAAA;CD/GD;ACsHD;EACE,gCAAA;KAAA,6BAAA;UAAA,wBAAA;EACA,UAAA;CDpHD;AC2HD;EACE,eAAA;CDzHD;ACgID;;;;EAIE,kCAAA;EACA,eAAA;CD9HD;ACgJD;;;;;EAKE,eAAA;EACA,cAAA;EACA,UAAA;CD9ID;ACqJD;EACE,kBAAA;CDnJD;AC6JD;;EAEE,qBAAA;CD3JD;ACsKD;;;;EAIE,2BAAA;EACA,gBAAA;CDpKD;AC2KD;;EAEE,gBAAA;CDzKD;ACgLD;;EAEE,UAAA;EACA,WAAA;CD9KD;ACsLD;EACE,oBAAA;CDpLD;AC+LD;;EAEE,+BAAA;KAAA,4BAAA;UAAA,uBAAA;EACA,WAAA;CD7LD;ACsMD;;EAEE,aAAA;CDpMD;AC4MD;EACE,8BAAA;EACA,gCAAA;KAAA,6BAAA;UAAA,wBAAA;CD1MD;ACmND;;EAEE,yBAAA;CDjND;ACwND;EACE,0BAAA;EACA,cAAA;EACA,+BAAA;CDtND;AC8ND;EACE,UAAA;EACA,WAAA;CD5ND;ACmOD;EACE,eAAA;CDjOD;ACyOD;EACE,kBAAA;CDvOD;ACiPD;EACE,0BAAA;EACA,kBAAA;CD/OD;ACkPD;;EAEE,WAAA;CDhPD;AACD,qFAAqF;AElFrF;EA7FI;;;IAGI,mCAAA;IACA,uBAAA;IACA,oCAAA;YAAA,4BAAA;IACA,6BAAA;GFkLL;EE/KC;;IAEI,2BAAA;GFiLL;EE9KC;IACI,6BAAA;GFgLL;EE7KC;IACI,8BAAA;GF+KL;EE1KC;;IAEI,YAAA;GF4KL;EEzKC;;IAEI,uBAAA;IACA,yBAAA;GF2KL;EExKC;IACI,4BAAA;GF0KL;EEvKC;;IAEI,yBAAA;GFyKL;EEtKC;IACI,2BAAA;GFwKL;EErKC;;;IAGI,WAAA;IACA,UAAA;GFuKL;EEpKC;;IAEI,wBAAA;GFsKL;EEhKC;IACI,cAAA;GFkKL;EEhKC;;IAGQ,kCAAA;GFiKT;EE9JC;IACI,uBAAA;GFgKL;EE7JC;IACI,qCAAA;GF+JL;EEhKC;;IAKQ,kCAAA;GF+JT;EE5JC;;IAGQ,kCAAA;GF6JT;CACF;AGnPD;EACE,oCAAA;EACA,sDAAA;EACA,gYAAA;CHqPD;AG7OD;EACE,mBAAA;EACA,SAAA;EACA,sBAAA;EACA,oCAAA;EACA,mBAAA;EACA,oBAAA;EACA,eAAA;EACA,oCAAA;EACA,mCAAA;CH+OD;AG3OmC;EAAW,iBAAA;CH8O9C;AG7OmC;EAAW,iBAAA;CHgP9C;AG9OmC;;EAAW,iBAAA;CHkP9C;AGjPmC;EAAW,iBAAA;CHoP9C;AGnPmC;EAAW,iBAAA;CHsP9C;AGrPmC;EAAW,iBAAA;CHwP9C;AGvPmC;EAAW,iBAAA;CH0P9C;AGzPmC;EAAW,iBAAA;CH4P9C;AG3PmC;EAAW,iBAAA;CH8P9C;AG7PmC;EAAW,iBAAA;CHgQ9C;AG/PmC;EAAW,iBAAA;CHkQ9C;AGjQmC;EAAW,iBAAA;CHoQ9C;AGnQmC;EAAW,iBAAA;CHsQ9C;AGrQmC;EAAW,iBAAA;CHwQ9C;AGvQmC;EAAW,iBAAA;CH0Q9C;AGzQmC;EAAW,iBAAA;CH4Q9C;AG3QmC;EAAW,iBAAA;CH8Q9C;AG7QmC;EAAW,iBAAA;CHgR9C;AG/QmC;EAAW,iBAAA;CHkR9C;AGjRmC;EAAW,iBAAA;CHoR9C;AGnRmC;EAAW,iBAAA;CHsR9C;AGrRmC;EAAW,iBAAA;CHwR9C;AGvRmC;EAAW,iBAAA;CH0R9C;AGzRmC;EAAW,iBAAA;CH4R9C;AG3RmC;EAAW,iBAAA;CH8R9C;AG7RmC;EAAW,iBAAA;CHgS9C;AG/RmC;EAAW,iBAAA;CHkS9C;AGjSmC;EAAW,iBAAA;CHoS9C;AGnSmC;EAAW,iBAAA;CHsS9C;AGrSmC;EAAW,iBAAA;CHwS9C;AGvSmC;EAAW,iBAAA;CH0S9C;AGzSmC;EAAW,iBAAA;CH4S9C;AG3SmC;EAAW,iBAAA;CH8S9C;AG7SmC;EAAW,iBAAA;CHgT9C;AG/SmC;EAAW,iBAAA;CHkT9C;AGjTmC;EAAW,iBAAA;CHoT9C;AGnTmC;EAAW,iBAAA;CHsT9C;AGrTmC;EAAW,iBAAA;CHwT9C;AGvTmC;EAAW,iBAAA;CH0T9C;AGzTmC;EAAW,iBAAA;CH4T9C;AG3TmC;EAAW,iBAAA;CH8T9C;AG7TmC;EAAW,iBAAA;CHgU9C;AG/TmC;EAAW,iBAAA;CHkU9C;AGjUmC;EAAW,iBAAA;CHoU9C;AGnUmC;EAAW,iBAAA;CHsU9C;AGrUmC;EAAW,iBAAA;CHwU9C;AGvUmC;EAAW,iBAAA;CH0U9C;AGzUmC;EAAW,iBAAA;CH4U9C;AG3UmC;EAAW,iBAAA;CH8U9C;AG7UmC;EAAW,iBAAA;CHgV9C;AG/UmC;EAAW,iBAAA;CHkV9C;AGjVmC;EAAW,iBAAA;CHoV9C;AGnVmC;EAAW,iBAAA;CHsV9C;AGrVmC;EAAW,iBAAA;CHwV9C;AGvVmC;EAAW,iBAAA;CH0V9C;AGzVmC;EAAW,iBAAA;CH4V9C;AG3VmC;EAAW,iBAAA;CH8V9C;AG7VmC;EAAW,iBAAA;CHgW9C;AG/VmC;EAAW,iBAAA;CHkW9C;AGjWmC;EAAW,iBAAA;CHoW9C;AGnWmC;EAAW,iBAAA;CHsW9C;AGrWmC;EAAW,iBAAA;CHwW9C;AGvWmC;EAAW,iBAAA;CH0W9C;AGzWmC;EAAW,iBAAA;CH4W9C;AG3WmC;EAAW,iBAAA;CH8W9C;AG7WmC;EAAW,iBAAA;CHgX9C;AG/WmC;EAAW,iBAAA;CHkX9C;AGjXmC;EAAW,iBAAA;CHoX9C;AGnXmC;EAAW,iBAAA;CHsX9C;AGrXmC;EAAW,iBAAA;CHwX9C;AGvXmC;EAAW,iBAAA;CH0X9C;AGzXmC;EAAW,iBAAA;CH4X9C;AG3XmC;EAAW,iBAAA;CH8X9C;AG7XmC;EAAW,iBAAA;CHgY9C;AG/XmC;EAAW,iBAAA;CHkY9C;AGjYmC;EAAW,iBAAA;CHoY9C;AGnYmC;EAAW,iBAAA;CHsY9C;AGrYmC;EAAW,iBAAA;CHwY9C;AGvYmC;EAAW,iBAAA;CH0Y9C;AGzYmC;EAAW,iBAAA;CH4Y9C;AG3YmC;EAAW,iBAAA;CH8Y9C;AG7YmC;EAAW,iBAAA;CHgZ9C;AG/YmC;EAAW,iBAAA;CHkZ9C;AGjZmC;EAAW,iBAAA;CHoZ9C;AGnZmC;EAAW,iBAAA;CHsZ9C;AGrZmC;EAAW,iBAAA;CHwZ9C;AGvZmC;EAAW,iBAAA;CH0Z9C;AGzZmC;EAAW,iBAAA;CH4Z9C;AG3ZmC;EAAW,iBAAA;CH8Z9C;AG7ZmC;EAAW,iBAAA;CHga9C;AG/ZmC;EAAW,iBAAA;CHka9C;AGjamC;EAAW,iBAAA;CHoa9C;AGnamC;EAAW,iBAAA;CHsa9C;AGramC;EAAW,iBAAA;CHwa9C;AGvamC;EAAW,iBAAA;CH0a9C;AGzamC;EAAW,iBAAA;CH4a9C;AG3amC;EAAW,iBAAA;CH8a9C;AG7amC;EAAW,iBAAA;CHgb9C;AG/amC;EAAW,iBAAA;CHkb9C;AGjbmC;EAAW,iBAAA;CHob9C;AGnbmC;EAAW,iBAAA;CHsb9C;AGrbmC;EAAW,iBAAA;CHwb9C;AGvbmC;EAAW,iBAAA;CH0b9C;AGzbmC;EAAW,iBAAA;CH4b9C;AG3bmC;EAAW,iBAAA;CH8b9C;AG7bmC;EAAW,iBAAA;CHgc9C;AG/bmC;EAAW,iBAAA;CHkc9C;AGjcmC;EAAW,iBAAA;CHoc9C;AGncmC;EAAW,iBAAA;CHsc9C;AGrcmC;EAAW,iBAAA;CHwc9C;AGvcmC;EAAW,iBAAA;CH0c9C;AGzcmC;EAAW,iBAAA;CH4c9C;AG3cmC;EAAW,iBAAA;CH8c9C;AG7cmC;EAAW,iBAAA;CHgd9C;AG/cmC;EAAW,iBAAA;CHkd9C;AGjdmC;EAAW,iBAAA;CHod9C;AGndmC;EAAW,iBAAA;CHsd9C;AGrdmC;EAAW,iBAAA;CHwd9C;AGvdmC;EAAW,iBAAA;CH0d9C;AGzdmC;EAAW,iBAAA;CH4d9C;AG3dmC;EAAW,iBAAA;CH8d9C;AG7dmC;EAAW,iBAAA;CHge9C;AG/dmC;EAAW,iBAAA;CHke9C;AGjemC;EAAW,iBAAA;CHoe9C;AGnemC;EAAW,iBAAA;CHse9C;AGremC;EAAW,iBAAA;CHwe9C;AGvemC;EAAW,iBAAA;CH0e9C;AGzemC;EAAW,iBAAA;CH4e9C;AG3emC;EAAW,iBAAA;CH8e9C;AG7emC;EAAW,iBAAA;CHgf9C;AG/emC;EAAW,iBAAA;CHkf9C;AGjfmC;EAAW,iBAAA;CHof9C;AGnfmC;EAAW,iBAAA;CHsf9C;AGrfmC;EAAW,iBAAA;CHwf9C;AGvfmC;EAAW,iBAAA;CH0f9C;AGzfmC;EAAW,iBAAA;CH4f9C;AG3fmC;EAAW,iBAAA;CH8f9C;AG7fmC;EAAW,iBAAA;CHggB9C;AG/fmC;EAAW,iBAAA;CHkgB9C;AGjgBmC;EAAW,iBAAA;CHogB9C;AGngBmC;EAAW,iBAAA;CHsgB9C;AGrgBmC;EAAW,iBAAA;CHwgB9C;AGvgBmC;EAAW,iBAAA;CH0gB9C;AGzgBmC;EAAW,iBAAA;CH4gB9C;AG3gBmC;EAAW,iBAAA;CH8gB9C;AG7gBmC;EAAW,iBAAA;CHghB9C;AG/gBmC;EAAW,iBAAA;CHkhB9C;AGjhBmC;EAAW,iBAAA;CHohB9C;AGnhBmC;EAAW,iBAAA;CHshB9C;AGrhBmC;EAAW,iBAAA;CHwhB9C;AGvhBmC;EAAW,iBAAA;CH0hB9C;AGzhBmC;EAAW,iBAAA;CH4hB9C;AG3hBmC;EAAW,iBAAA;CH8hB9C;AG7hBmC;EAAW,iBAAA;CHgiB9C;AG/hBmC;EAAW,iBAAA;CHkiB9C;AGjiBmC;EAAW,iBAAA;CHoiB9C;AGniBmC;EAAW,iBAAA;CHsiB9C;AGriBmC;EAAW,iBAAA;CHwiB9C;AGviBmC;EAAW,iBAAA;CH0iB9C;AGziBmC;EAAW,iBAAA;CH4iB9C;AG3iBmC;EAAW,iBAAA;CH8iB9C;AG7iBmC;EAAW,iBAAA;CHgjB9C;AG/iBmC;EAAW,iBAAA;CHkjB9C;AGjjBmC;EAAW,iBAAA;CHojB9C;AGnjBmC;EAAW,iBAAA;CHsjB9C;AGrjBmC;EAAW,iBAAA;CHwjB9C;AGvjBmC;EAAW,iBAAA;CH0jB9C;AGzjBmC;EAAW,iBAAA;CH4jB9C;AG3jBmC;EAAW,iBAAA;CH8jB9C;AG7jBmC;EAAW,iBAAA;CHgkB9C;AG/jBmC;EAAW,iBAAA;CHkkB9C;AGjkBmC;EAAW,iBAAA;CHokB9C;AGnkBmC;EAAW,iBAAA;CHskB9C;AGrkBmC;EAAW,iBAAA;CHwkB9C;AGvkBmC;EAAW,iBAAA;CH0kB9C;AGzkBmC;EAAW,iBAAA;CH4kB9C;AG3kBmC;EAAW,iBAAA;CH8kB9C;AG7kBmC;EAAW,iBAAA;CHglB9C;AG/kBmC;EAAW,iBAAA;CHklB9C;AGjlBmC;EAAW,iBAAA;CHolB9C;AGnlBmC;EAAW,iBAAA;CHslB9C;AGrlBmC;EAAW,iBAAA;CHwlB9C;AGvlBmC;EAAW,iBAAA;CH0lB9C;AGzlBmC;EAAW,iBAAA;CH4lB9C;AG3lBmC;EAAW,iBAAA;CH8lB9C;AG7lBmC;EAAW,iBAAA;CHgmB9C;AG/lBmC;EAAW,iBAAA;CHkmB9C;AGjmBmC;EAAW,iBAAA;CHomB9C;AGnmBmC;EAAW,iBAAA;CHsmB9C;AGrmBmC;EAAW,iBAAA;CHwmB9C;AGvmBmC;EAAW,iBAAA;CH0mB9C;AGzmBmC;EAAW,iBAAA;CH4mB9C;AG3mBmC;EAAW,iBAAA;CH8mB9C;AG7mBmC;EAAW,iBAAA;CHgnB9C;AG/mBmC;EAAW,iBAAA;CHknB9C;AGjnBmC;EAAW,iBAAA;CHonB9C;AGnnBmC;EAAW,iBAAA;CHsnB9C;AGrnBmC;EAAW,iBAAA;CHwnB9C;AGvnBmC;EAAW,iBAAA;CH0nB9C;AGznBmC;EAAW,iBAAA;CH4nB9C;AG3nBmC;EAAW,iBAAA;CH8nB9C;AG7nBmC;EAAW,iBAAA;CHgoB9C;AG/nBmC;EAAW,iBAAA;CHkoB9C;AGjoBmC;EAAW,iBAAA;CHooB9C;AGnoBmC;EAAW,iBAAA;CHsoB9C;AGroBmC;EAAW,iBAAA;CHwoB9C;AG/nBmC;EAAW,iBAAA;CHkoB9C;AGjoBmC;EAAW,iBAAA;CHooB9C;AGnoBmC;EAAW,iBAAA;CHsoB9C;AGroBmC;EAAW,iBAAA;CHwoB9C;AGvoBmC;EAAW,iBAAA;CH0oB9C;AGzoBmC;EAAW,iBAAA;CH4oB9C;AG3oBmC;EAAW,iBAAA;CH8oB9C;AG7oBmC;EAAW,iBAAA;CHgpB9C;AG/oBmC;EAAW,iBAAA;CHkpB9C;AGjpBmC;EAAW,iBAAA;CHopB9C;AGnpBmC;EAAW,iBAAA;CHspB9C;AGrpBmC;EAAW,iBAAA;CHwpB9C;AGvpBmC;EAAW,iBAAA;CH0pB9C;AGzpBmC;EAAW,iBAAA;CH4pB9C;AG3pBmC;EAAW,iBAAA;CH8pB9C;AG7pBmC;EAAW,iBAAA;CHgqB9C;AG/pBmC;EAAW,iBAAA;CHkqB9C;AGjqBmC;EAAW,iBAAA;CHoqB9C;AGnqBmC;EAAW,iBAAA;CHsqB9C;AGrqBmC;EAAW,iBAAA;CHwqB9C;AGvqBmC;EAAW,iBAAA;CH0qB9C;AGzqBmC;EAAW,iBAAA;CH4qB9C;AG3qBmC;EAAW,iBAAA;CH8qB9C;AG7qBmC;EAAW,iBAAA;CHgrB9C;AG/qBmC;EAAW,iBAAA;CHkrB9C;AGjrBmC;EAAW,iBAAA;CHorB9C;AGnrBmC;EAAW,iBAAA;CHsrB9C;AGrrBmC;EAAW,iBAAA;CHwrB9C;AGvrBmC;EAAW,iBAAA;CH0rB9C;AGzrBmC;EAAW,iBAAA;CH4rB9C;AG3rBmC;EAAW,iBAAA;CH8rB9C;AG7rBmC;EAAW,iBAAA;CHgsB9C;AG/rBmC;EAAW,iBAAA;CHksB9C;AGjsBmC;EAAW,iBAAA;CHosB9C;AGnsBmC;EAAW,iBAAA;CHssB9C;AGrsBmC;EAAW,iBAAA;CHwsB9C;AGvsBmC;EAAW,iBAAA;CH0sB9C;AGzsBmC;EAAW,iBAAA;CH4sB9C;AG3sBmC;EAAW,iBAAA;CH8sB9C;AG7sBmC;EAAW,iBAAA;CHgtB9C;AG/sBmC;EAAW,iBAAA;CHktB9C;AGjtBmC;EAAW,iBAAA;CHotB9C;AGntBmC;EAAW,iBAAA;CHstB9C;AGrtBmC;EAAW,iBAAA;CHwtB9C;AGvtBmC;EAAW,iBAAA;CH0tB9C;AGztBmC;EAAW,iBAAA;CH4tB9C;AG3tBmC;EAAW,iBAAA;CH8tB9C;AG7tBmC;EAAW,iBAAA;CHguB9C;AG/tBmC;EAAW,iBAAA;CHkuB9C;AGjuBmC;EAAW,iBAAA;CHouB9C;AGnuBmC;EAAW,iBAAA;CHsuB9C;AGruBmC;EAAW,iBAAA;CHwuB9C;AGvuBmC;EAAW,iBAAA;CH0uB9C;AGzuBmC;EAAW,iBAAA;CH4uB9C;AG3uBmC;EAAW,iBAAA;CH8uB9C;AG7uBmC;EAAW,iBAAA;CHgvB9C;AIthCD;ECgEE,+BAAA;EACG,4BAAA;EACK,uBAAA;CLy9BT;AIxhCD;;EC6DE,+BAAA;EACG,4BAAA;EACK,uBAAA;CL+9BT;AIthCD;EACE,gBAAA;EACA,8CAAA;CJwhCD;AIrhCD;EACE,4DAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,uBAAA;CJuhCD;AInhCD;;;;EAIE,qBAAA;EACA,mBAAA;EACA,qBAAA;CJqhCD;AI/gCD;EACE,eAAA;EACA,sBAAA;CJihCD;AI/gCC;;EAEE,eAAA;EACA,2BAAA;CJihCH;AI9gCC;EEnDA,2CAAA;EACA,qBAAA;CNokCD;AIvgCD;EACE,UAAA;CJygCD;AIngCD;EACE,uBAAA;CJqgCD;AIjgCD;;;;;EGvEE,eAAA;EACA,gBAAA;EACA,aAAA;CP+kCD;AIrgCD;EACE,mBAAA;CJugCD;AIjgCD;EACE,aAAA;EACA,wBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;EC6FA,yCAAA;EACK,oCAAA;EACG,iCAAA;EEvLR,sBAAA;EACA,gBAAA;EACA,aAAA;CP+lCD;AIjgCD;EACE,mBAAA;CJmgCD;AI7/BD;EACE,iBAAA;EACA,oBAAA;EACA,UAAA;EACA,8BAAA;CJ+/BD;AIv/BD;EACE,mBAAA;EACA,WAAA;EACA,YAAA;EACA,aAAA;EACA,WAAA;EACA,iBAAA;EACA,uBAAA;EACA,UAAA;CJy/BD;AIj/BC;;EAEE,iBAAA;EACA,YAAA;EACA,aAAA;EACA,UAAA;EACA,kBAAA;EACA,WAAA;CJm/BH;AIx+BD;EACE,gBAAA;CJ0+BD;AQjoCD;;;;;;;;;;;;EAEE,qBAAA;EACA,iBAAA;EACA,iBAAA;EACA,eAAA;CR6oCD;AQlpCD;;;;;;;;;;;;;;;;;;;;;;;;EASI,oBAAA;EACA,eAAA;EACA,eAAA;CRmqCH;AQ/pCD;;;;;;EAGE,iBAAA;EACA,oBAAA;CRoqCD;AQxqCD;;;;;;;;;;;;EAQI,eAAA;CR8qCH;AQ3qCD;;;;;;EAGE,iBAAA;EACA,oBAAA;CRgrCD;AQprCD;;;;;;;;;;;;EAQI,eAAA;CR0rCH;AQtrCD;;EAAU,gBAAA;CR0rCT;AQzrCD;;EAAU,gBAAA;CR6rCT;AQ5rCD;;EAAU,gBAAA;CRgsCT;AQ/rCD;;EAAU,gBAAA;CRmsCT;AQlsCD;;EAAU,gBAAA;CRssCT;AQrsCD;;EAAU,gBAAA;CRysCT;AQnsCD;EACE,iBAAA;CRqsCD;AQlsCD;EACE,oBAAA;EACA,gBAAA;EACA,iBAAA;EACA,iBAAA;CRosCD;AQ/rCD;EAwOA;IA1OI,gBAAA;GRqsCD;CACF;AQ7rCD;;EAEE,eAAA;CR+rCD;AQ5rCD;;EAEE,0BAAA;EACA,cAAA;CR8rCD;AQ1rCD;EAAuB,iBAAA;CR6rCtB;AQ5rCD;EAAuB,kBAAA;CR+rCtB;AQ9rCD;EAAuB,mBAAA;CRisCtB;AQhsCD;EAAuB,oBAAA;CRmsCtB;AQlsCD;EAAuB,oBAAA;CRqsCtB;AQlsCD;EAAuB,0BAAA;CRqsCtB;AQpsCD;EAAuB,0BAAA;CRusCtB;AQtsCD;EAAuB,2BAAA;CRysCtB;AQtsCD;EACE,eAAA;CRwsCD;AQtsCD;ECrGE,eAAA;CT8yCD;AS7yCC;;EAEE,eAAA;CT+yCH;AQ1sCD;ECxGE,eAAA;CTqzCD;ASpzCC;;EAEE,eAAA;CTszCH;AQ9sCD;EC3GE,eAAA;CT4zCD;AS3zCC;;EAEE,eAAA;CT6zCH;AQltCD;EC9GE,eAAA;CTm0CD;ASl0CC;;EAEE,eAAA;CTo0CH;AQttCD;ECjHE,eAAA;CT00CD;ASz0CC;;EAEE,eAAA;CT20CH;AQttCD;EAGE,YAAA;EE3HA,0BAAA;CVk1CD;AUj1CC;;EAEE,0BAAA;CVm1CH;AQxtCD;EE9HE,0BAAA;CVy1CD;AUx1CC;;EAEE,0BAAA;CV01CH;AQ5tCD;EEjIE,0BAAA;CVg2CD;AU/1CC;;EAEE,0BAAA;CVi2CH;AQhuCD;EEpIE,0BAAA;CVu2CD;AUt2CC;;EAEE,0BAAA;CVw2CH;AQpuCD;EEvIE,0BAAA;CV82CD;AU72CC;;EAEE,0BAAA;CV+2CH;AQnuCD;EACE,oBAAA;EACA,oBAAA;EACA,iCAAA;CRquCD;AQ7tCD;;EAEE,cAAA;EACA,oBAAA;CR+tCD;AQluCD;;;;EAMI,iBAAA;CRkuCH;AQ3tCD;EACE,gBAAA;EACA,iBAAA;CR6tCD;AQztCD;EALE,gBAAA;EACA,iBAAA;EAMA,kBAAA;CR4tCD;AQ9tCD;EAKI,sBAAA;EACA,kBAAA;EACA,mBAAA;CR4tCH;AQvtCD;EACE,cAAA;EACA,oBAAA;CRytCD;AQvtCD;;EAEE,wBAAA;CRytCD;AQvtCD;EACE,kBAAA;CRytCD;AQvtCD;EACE,eAAA;CRytCD;AQhsCD;EA6EA;IAvFM,YAAA;IACA,aAAA;IACA,YAAA;IACA,kBAAA;IGtNJ,iBAAA;IACA,wBAAA;IACA,oBAAA;GXq6CC;EQ7nCH;IAhFM,mBAAA;GRgtCH;CACF;AQvsCD;;EAGE,aAAA;EACA,kCAAA;CRwsCD;AQtsCD;EACE,eAAA;EA9IqB,0BAAA;CRu1CtB;AQpsCD;EACE,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,+BAAA;CRssCD;AQjsCG;;;EACE,iBAAA;CRqsCL;AQ/sCD;;;EAmBI,eAAA;EACA,eAAA;EACA,wBAAA;EACA,eAAA;CRisCH;AQ/rCG;;;EACE,uBAAA;CRmsCL;AQ3rCD;;EAEE,oBAAA;EACA,gBAAA;EACA,gCAAA;EACA,eAAA;EACA,kBAAA;CR6rCD;AQvrCG;;;;;;EAAW,YAAA;CR+rCd;AQ9rCG;;;;;;EACE,uBAAA;CRqsCL;AQ/rCD;EACE,oBAAA;EACA,mBAAA;EACA,wBAAA;CRisCD;AYv+CD;;;;EAIE,+DAAA;CZy+CD;AYr+CD;EACE,iBAAA;EACA,eAAA;EACA,eAAA;EACA,0BAAA;EACA,mBAAA;CZu+CD;AYn+CD;EACE,iBAAA;EACA,eAAA;EACA,YAAA;EACA,uBAAA;EACA,mBAAA;EACA,uDAAA;UAAA,+CAAA;CZq+CD;AY3+CD;EASI,WAAA;EACA,gBAAA;EACA,kBAAA;EACA,yBAAA;UAAA,iBAAA;CZq+CH;AYh+CD;EACE,eAAA;EACA,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,wBAAA;EACA,sBAAA;EACA,sBAAA;EACA,eAAA;EACA,0BAAA;EACA,uBAAA;EACA,mBAAA;CZk+CD;AY7+CD;EAeI,WAAA;EACA,mBAAA;EACA,eAAA;EACA,sBAAA;EACA,8BAAA;EACA,iBAAA;CZi+CH;AY59CD;EACE,kBAAA;EACA,mBAAA;CZ89CD;AaxhDD;ECHE,mBAAA;EACA,kBAAA;EACA,mBAAA;EACA,oBAAA;Cd8hDD;AaxhDC;EAqEF;IAvEI,aAAA;Gb8hDD;CACF;Aa1hDC;EAkEF;IApEI,aAAA;GbgiDD;CACF;Aa5hDD;EA+DA;IAjEI,cAAA;GbkiDD;CACF;AazhDD;ECvBE,mBAAA;EACA,kBAAA;EACA,mBAAA;EACA,oBAAA;CdmjDD;AathDD;ECvBE,mBAAA;EACA,oBAAA;CdgjDD;AehjDG;EACE,mBAAA;EAEA,gBAAA;EAEA,mBAAA;EACA,oBAAA;CfgjDL;AehiDG;EACE,YAAA;CfkiDL;Ae3hDC;EACE,YAAA;Cf6hDH;Ae9hDC;EACE,oBAAA;CfgiDH;AejiDC;EACE,oBAAA;CfmiDH;AepiDC;EACE,WAAA;CfsiDH;AeviDC;EACE,oBAAA;CfyiDH;Ae1iDC;EACE,oBAAA;Cf4iDH;Ae7iDC;EACE,WAAA;Cf+iDH;AehjDC;EACE,oBAAA;CfkjDH;AenjDC;EACE,oBAAA;CfqjDH;AetjDC;EACE,WAAA;CfwjDH;AezjDC;EACE,oBAAA;Cf2jDH;Ae5jDC;EACE,mBAAA;Cf8jDH;AehjDC;EACE,YAAA;CfkjDH;AenjDC;EACE,oBAAA;CfqjDH;AetjDC;EACE,oBAAA;CfwjDH;AezjDC;EACE,WAAA;Cf2jDH;Ae5jDC;EACE,oBAAA;Cf8jDH;Ae/jDC;EACE,oBAAA;CfikDH;AelkDC;EACE,WAAA;CfokDH;AerkDC;EACE,oBAAA;CfukDH;AexkDC;EACE,oBAAA;Cf0kDH;Ae3kDC;EACE,WAAA;Cf6kDH;Ae9kDC;EACE,oBAAA;CfglDH;AejlDC;EACE,mBAAA;CfmlDH;Ae/kDC;EACE,YAAA;CfilDH;AejmDC;EACE,WAAA;CfmmDH;AepmDC;EACE,mBAAA;CfsmDH;AevmDC;EACE,mBAAA;CfymDH;Ae1mDC;EACE,UAAA;Cf4mDH;Ae7mDC;EACE,mBAAA;Cf+mDH;AehnDC;EACE,mBAAA;CfknDH;AennDC;EACE,UAAA;CfqnDH;AetnDC;EACE,mBAAA;CfwnDH;AeznDC;EACE,mBAAA;Cf2nDH;Ae5nDC;EACE,UAAA;Cf8nDH;Ae/nDC;EACE,mBAAA;CfioDH;AeloDC;EACE,kBAAA;CfooDH;AehoDC;EACE,WAAA;CfkoDH;AepnDC;EACE,kBAAA;CfsnDH;AevnDC;EACE,0BAAA;CfynDH;Ae1nDC;EACE,0BAAA;Cf4nDH;Ae7nDC;EACE,iBAAA;Cf+nDH;AehoDC;EACE,0BAAA;CfkoDH;AenoDC;EACE,0BAAA;CfqoDH;AetoDC;EACE,iBAAA;CfwoDH;AezoDC;EACE,0BAAA;Cf2oDH;Ae5oDC;EACE,0BAAA;Cf8oDH;Ae/oDC;EACE,iBAAA;CfipDH;AelpDC;EACE,0BAAA;CfopDH;AerpDC;EACE,yBAAA;CfupDH;AexpDC;EACE,gBAAA;Cf0pDH;Aa1pDD;EElCI;IACE,YAAA;Gf+rDH;EexrDD;IACE,YAAA;Gf0rDD;Ee3rDD;IACE,oBAAA;Gf6rDD;Ee9rDD;IACE,oBAAA;GfgsDD;EejsDD;IACE,WAAA;GfmsDD;EepsDD;IACE,oBAAA;GfssDD;EevsDD;IACE,oBAAA;GfysDD;Ee1sDD;IACE,WAAA;Gf4sDD;Ee7sDD;IACE,oBAAA;Gf+sDD;EehtDD;IACE,oBAAA;GfktDD;EentDD;IACE,WAAA;GfqtDD;EettDD;IACE,oBAAA;GfwtDD;EeztDD;IACE,mBAAA;Gf2tDD;Ee7sDD;IACE,YAAA;Gf+sDD;EehtDD;IACE,oBAAA;GfktDD;EentDD;IACE,oBAAA;GfqtDD;EettDD;IACE,WAAA;GfwtDD;EeztDD;IACE,oBAAA;Gf2tDD;Ee5tDD;IACE,oBAAA;Gf8tDD;Ee/tDD;IACE,WAAA;GfiuDD;EeluDD;IACE,oBAAA;GfouDD;EeruDD;IACE,oBAAA;GfuuDD;EexuDD;IACE,WAAA;Gf0uDD;Ee3uDD;IACE,oBAAA;Gf6uDD;Ee9uDD;IACE,mBAAA;GfgvDD;Ee5uDD;IACE,YAAA;Gf8uDD;Ee9vDD;IACE,WAAA;GfgwDD;EejwDD;IACE,mBAAA;GfmwDD;EepwDD;IACE,mBAAA;GfswDD;EevwDD;IACE,UAAA;GfywDD;Ee1wDD;IACE,mBAAA;Gf4wDD;Ee7wDD;IACE,mBAAA;Gf+wDD;EehxDD;IACE,UAAA;GfkxDD;EenxDD;IACE,mBAAA;GfqxDD;EetxDD;IACE,mBAAA;GfwxDD;EezxDD;IACE,UAAA;Gf2xDD;Ee5xDD;IACE,mBAAA;Gf8xDD;Ee/xDD;IACE,kBAAA;GfiyDD;Ee7xDD;IACE,WAAA;Gf+xDD;EejxDD;IACE,kBAAA;GfmxDD;EepxDD;IACE,0BAAA;GfsxDD;EevxDD;IACE,0BAAA;GfyxDD;Ee1xDD;IACE,iBAAA;Gf4xDD;Ee7xDD;IACE,0BAAA;Gf+xDD;EehyDD;IACE,0BAAA;GfkyDD;EenyDD;IACE,iBAAA;GfqyDD;EetyDD;IACE,0BAAA;GfwyDD;EezyDD;IACE,0BAAA;Gf2yDD;Ee5yDD;IACE,iBAAA;Gf8yDD;Ee/yDD;IACE,0BAAA;GfizDD;EelzDD;IACE,yBAAA;GfozDD;EerzDD;IACE,gBAAA;GfuzDD;CACF;Aa/yDD;EE3CI;IACE,YAAA;Gf61DH;Eet1DD;IACE,YAAA;Gfw1DD;Eez1DD;IACE,oBAAA;Gf21DD;Ee51DD;IACE,oBAAA;Gf81DD;Ee/1DD;IACE,WAAA;Gfi2DD;Eel2DD;IACE,oBAAA;Gfo2DD;Eer2DD;IACE,oBAAA;Gfu2DD;Eex2DD;IACE,WAAA;Gf02DD;Ee32DD;IACE,oBAAA;Gf62DD;Ee92DD;IACE,oBAAA;Gfg3DD;Eej3DD;IACE,WAAA;Gfm3DD;Eep3DD;IACE,oBAAA;Gfs3DD;Eev3DD;IACE,mBAAA;Gfy3DD;Ee32DD;IACE,YAAA;Gf62DD;Ee92DD;IACE,oBAAA;Gfg3DD;Eej3DD;IACE,oBAAA;Gfm3DD;Eep3DD;IACE,WAAA;Gfs3DD;Eev3DD;IACE,oBAAA;Gfy3DD;Ee13DD;IACE,oBAAA;Gf43DD;Ee73DD;IACE,WAAA;Gf+3DD;Eeh4DD;IACE,oBAAA;Gfk4DD;Een4DD;IACE,oBAAA;Gfq4DD;Eet4DD;IACE,WAAA;Gfw4DD;Eez4DD;IACE,oBAAA;Gf24DD;Ee54DD;IACE,mBAAA;Gf84DD;Ee14DD;IACE,YAAA;Gf44DD;Ee55DD;IACE,WAAA;Gf85DD;Ee/5DD;IACE,mBAAA;Gfi6DD;Eel6DD;IACE,mBAAA;Gfo6DD;Eer6DD;IACE,UAAA;Gfu6DD;Eex6DD;IACE,mBAAA;Gf06DD;Ee36DD;IACE,mBAAA;Gf66DD;Ee96DD;IACE,UAAA;Gfg7DD;Eej7DD;IACE,mBAAA;Gfm7DD;Eep7DD;IACE,mBAAA;Gfs7DD;Eev7DD;IACE,UAAA;Gfy7DD;Ee17DD;IACE,mBAAA;Gf47DD;Ee77DD;IACE,kBAAA;Gf+7DD;Ee37DD;IACE,WAAA;Gf67DD;Ee/6DD;IACE,kBAAA;Gfi7DD;Eel7DD;IACE,0BAAA;Gfo7DD;Eer7DD;IACE,0BAAA;Gfu7DD;Eex7DD;IACE,iBAAA;Gf07DD;Ee37DD;IACE,0BAAA;Gf67DD;Ee97DD;IACE,0BAAA;Gfg8DD;Eej8DD;IACE,iBAAA;Gfm8DD;Eep8DD;IACE,0BAAA;Gfs8DD;Eev8DD;IACE,0BAAA;Gfy8DD;Ee18DD;IACE,iBAAA;Gf48DD;Ee78DD;IACE,0BAAA;Gf+8DD;Eeh9DD;IACE,yBAAA;Gfk9DD;Een9DD;IACE,gBAAA;Gfq9DD;CACF;Aa18DD;EE9CI;IACE,YAAA;Gf2/DH;Eep/DD;IACE,YAAA;Gfs/DD;Eev/DD;IACE,oBAAA;Gfy/DD;Ee1/DD;IACE,oBAAA;Gf4/DD;Ee7/DD;IACE,WAAA;Gf+/DD;EehgED;IACE,oBAAA;GfkgED;EengED;IACE,oBAAA;GfqgED;EetgED;IACE,WAAA;GfwgED;EezgED;IACE,oBAAA;Gf2gED;Ee5gED;IACE,oBAAA;Gf8gED;Ee/gED;IACE,WAAA;GfihED;EelhED;IACE,oBAAA;GfohED;EerhED;IACE,mBAAA;GfuhED;EezgED;IACE,YAAA;Gf2gED;Ee5gED;IACE,oBAAA;Gf8gED;Ee/gED;IACE,oBAAA;GfihED;EelhED;IACE,WAAA;GfohED;EerhED;IACE,oBAAA;GfuhED;EexhED;IACE,oBAAA;Gf0hED;Ee3hED;IACE,WAAA;Gf6hED;Ee9hED;IACE,oBAAA;GfgiED;EejiED;IACE,oBAAA;GfmiED;EepiED;IACE,WAAA;GfsiED;EeviED;IACE,oBAAA;GfyiED;Ee1iED;IACE,mBAAA;Gf4iED;EexiED;IACE,YAAA;Gf0iED;Ee1jED;IACE,WAAA;Gf4jED;Ee7jED;IACE,mBAAA;Gf+jED;EehkED;IACE,mBAAA;GfkkED;EenkED;IACE,UAAA;GfqkED;EetkED;IACE,mBAAA;GfwkED;EezkED;IACE,mBAAA;Gf2kED;Ee5kED;IACE,UAAA;Gf8kED;Ee/kED;IACE,mBAAA;GfilED;EellED;IACE,mBAAA;GfolED;EerlED;IACE,UAAA;GfulED;EexlED;IACE,mBAAA;Gf0lED;Ee3lED;IACE,kBAAA;Gf6lED;EezlED;IACE,WAAA;Gf2lED;Ee7kED;IACE,kBAAA;Gf+kED;EehlED;IACE,0BAAA;GfklED;EenlED;IACE,0BAAA;GfqlED;EetlED;IACE,iBAAA;GfwlED;EezlED;IACE,0BAAA;Gf2lED;Ee5lED;IACE,0BAAA;Gf8lED;Ee/lED;IACE,iBAAA;GfimED;EelmED;IACE,0BAAA;GfomED;EermED;IACE,0BAAA;GfumED;EexmED;IACE,iBAAA;Gf0mED;Ee3mED;IACE,0BAAA;Gf6mED;Ee9mED;IACE,yBAAA;GfgnED;EejnED;IACE,gBAAA;GfmnED;CACF;AgBvrED;EACE,8BAAA;ChByrED;AgBvrED;EACE,iBAAA;EACA,oBAAA;EACA,eAAA;EACA,iBAAA;ChByrED;AgBvrED;EACE,iBAAA;ChByrED;AgBnrED;EACE,YAAA;EACA,gBAAA;EACA,oBAAA;ChBqrED;AgBxrED;;;;;;EAWQ,aAAA;EACA,wBAAA;EACA,oBAAA;EACA,2BAAA;ChBqrEP;AgBnsED;EAoBI,uBAAA;EACA,8BAAA;ChBkrEH;AgBvsED;;;;;;EA8BQ,cAAA;ChBirEP;AgB/sED;EAoCI,2BAAA;ChB8qEH;AgBltED;EAyCI,uBAAA;ChB4qEH;AgBrqED;;;;;;EAOQ,aAAA;ChBsqEP;AgB3pED;EACE,uBAAA;ChB6pED;AgB9pED;;;;;;EAQQ,uBAAA;ChB8pEP;AgBtqED;;EAeM,yBAAA;ChB2pEL;AgBjpED;EAEI,0BAAA;ChBkpEH;AgBzoED;EAEI,0BAAA;ChB0oEH;AgBjoED;EACE,iBAAA;EACA,YAAA;EACA,sBAAA;ChBmoED;AgB9nEG;;EACE,iBAAA;EACA,YAAA;EACA,oBAAA;ChBioEL;AiB7wEC;;;;;;;;;;;;EAOI,0BAAA;CjBoxEL;AiB9wEC;;;;;EAMI,0BAAA;CjB+wEL;AiBlyEC;;;;;;;;;;;;EAOI,0BAAA;CjByyEL;AiBnyEC;;;;;EAMI,0BAAA;CjBoyEL;AiBvzEC;;;;;;;;;;;;EAOI,0BAAA;CjB8zEL;AiBxzEC;;;;;EAMI,0BAAA;CjByzEL;AiB50EC;;;;;;;;;;;;EAOI,0BAAA;CjBm1EL;AiB70EC;;;;;EAMI,0BAAA;CjB80EL;AiBj2EC;;;;;;;;;;;;EAOI,0BAAA;CjBw2EL;AiBl2EC;;;;;EAMI,0BAAA;CjBm2EL;AgBjtED;EACE,iBAAA;EACA,kBAAA;ChBmtED;AgBtpED;EACA;IA3DI,YAAA;IACA,oBAAA;IACA,mBAAA;IACA,6CAAA;IACA,uBAAA;GhBotED;EgB7pEH;IAnDM,iBAAA;GhBmtEH;EgBhqEH;;;;;;IA1CY,oBAAA;GhBktET;EgBxqEH;IAlCM,UAAA;GhB6sEH;EgB3qEH;;;;;;IAzBY,eAAA;GhB4sET;EgBnrEH;;;;;;IArBY,gBAAA;GhBgtET;EgB3rEH;;;;IARY,iBAAA;GhBysET;CACF;AkBn6ED;EACE,WAAA;EACA,UAAA;EACA,UAAA;EAIA,aAAA;ClBk6ED;AkB/5ED;EACE,eAAA;EACA,YAAA;EACA,WAAA;EACA,oBAAA;EACA,gBAAA;EACA,qBAAA;EACA,eAAA;EACA,UAAA;EACA,iCAAA;ClBi6ED;AkB95ED;EACE,sBAAA;EACA,gBAAA;EACA,mBAAA;EACA,kBAAA;ClBg6ED;AkBr5ED;Eb4BE,+BAAA;EACG,4BAAA;EACK,uBAAA;CL43ET;AkBr5ED;;EAEE,gBAAA;EACA,mBAAA;EACA,oBAAA;ClBu5ED;AkBp5ED;EACE,eAAA;ClBs5ED;AkBl5ED;EACE,eAAA;EACA,YAAA;ClBo5ED;AkBh5ED;;EAEE,aAAA;ClBk5ED;AkB94ED;;;EZrEE,2CAAA;EACA,qBAAA;CNw9ED;AkB74ED;EACE,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;ClB+4ED;AkBr3ED;EACE,eAAA;EACA,YAAA;EACA,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,uBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;EbxDA,yDAAA;EACQ,iDAAA;EAyHR,uFAAA;EACK,0EAAA;EACG,uEAAA;CLwzET;AmBh8EC;EACE,sBAAA;EACA,WAAA;EdUF,uFAAA;EACQ,+EAAA;CLy7ET;AKx5EC;EACE,YAAA;EACA,WAAA;CL05EH;AKx5EC;EAA0B,YAAA;CL25E3B;AK15EC;EAAgC,YAAA;CL65EjC;AkBj4EC;EACE,UAAA;EACA,8BAAA;ClBm4EH;AkB33EC;;;EAGE,0BAAA;EACA,WAAA;ClB63EH;AkB13EC;;EAEE,oBAAA;ClB43EH;AkBx3EC;EACE,aAAA;ClB03EH;AkB92ED;EACE,yBAAA;ClBg3ED;AkBx0ED;EAtBI;;;;IACE,kBAAA;GlBo2EH;EkBj2EC;;;;;;;;IAEE,kBAAA;GlBy2EH;EkBt2EC;;;;;;;;IAEE,kBAAA;GlB82EH;CACF;AkBp2ED;EACE,oBAAA;ClBs2ED;AkB91ED;;EAEE,mBAAA;EACA,eAAA;EACA,iBAAA;EACA,oBAAA;ClBg2ED;AkBr2ED;;EAQI,iBAAA;EACA,mBAAA;EACA,iBAAA;EACA,oBAAA;EACA,gBAAA;ClBi2EH;AkB91ED;;;;EAIE,mBAAA;EACA,mBAAA;EACA,mBAAA;ClBg2ED;AkB71ED;;EAEE,iBAAA;ClB+1ED;AkB31ED;;EAEE,mBAAA;EACA,sBAAA;EACA,mBAAA;EACA,iBAAA;EACA,uBAAA;EACA,oBAAA;EACA,gBAAA;ClB61ED;AkB31ED;;EAEE,cAAA;EACA,kBAAA;ClB61ED;AkBp1EC;;;;;;EAGE,oBAAA;ClBy1EH;AkBn1EC;;;;EAEE,oBAAA;ClBu1EH;AkBj1EC;;;;EAGI,oBAAA;ClBo1EL;AkBz0ED;EAEE,iBAAA;EACA,oBAAA;EAEA,iBAAA;EACA,iBAAA;ClBy0ED;AkBv0EC;;EAEE,gBAAA;EACA,iBAAA;ClBy0EH;AkB5zED;ECnQE,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CnBkkFD;AmBhkFC;EACE,aAAA;EACA,kBAAA;CnBkkFH;AmB/jFC;;EAEE,aAAA;CnBikFH;AkBx0ED;EAEI,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;ClBy0EH;AkB/0ED;EASI,aAAA;EACA,kBAAA;ClBy0EH;AkBn1ED;;EAcI,aAAA;ClBy0EH;AkBv1ED;EAiBI,aAAA;EACA,iBAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;ClBy0EH;AkBr0ED;EC/RE,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CnBumFD;AmBrmFC;EACE,aAAA;EACA,kBAAA;CnBumFH;AmBpmFC;;EAEE,aAAA;CnBsmFH;AkBj1ED;EAEI,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;ClBk1EH;AkBx1ED;EASI,aAAA;EACA,kBAAA;ClBk1EH;AkB51ED;;EAcI,aAAA;ClBk1EH;AkBh2ED;EAiBI,aAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;ClBk1EH;AkBz0ED;EAEE,mBAAA;ClB00ED;AkB50ED;EAMI,sBAAA;ClBy0EH;AkBr0ED;EACE,mBAAA;EACA,OAAA;EACA,SAAA;EACA,WAAA;EACA,eAAA;EACA,YAAA;EACA,aAAA;EACA,kBAAA;EACA,mBAAA;EACA,qBAAA;ClBu0ED;AkBr0ED;;;EAGE,YAAA;EACA,aAAA;EACA,kBAAA;ClBu0ED;AkBr0ED;;;EAGE,YAAA;EACA,aAAA;EACA,kBAAA;ClBu0ED;AkBn0ED;;;;;;;;;;EC1ZI,eAAA;CnByuFH;AkB/0ED;ECtZI,sBAAA;Ed+CF,yDAAA;EACQ,iDAAA;CL0rFT;AmBxuFG;EACE,sBAAA;Ed4CJ,0EAAA;EACQ,kEAAA;CL+rFT;AkBz1ED;EC5YI,eAAA;EACA,sBAAA;EACA,0BAAA;CnBwuFH;AkB91ED;ECtYI,eAAA;CnBuuFH;AkB91ED;;;;;;;;;;EC7ZI,eAAA;CnBuwFH;AkB12ED;ECzZI,sBAAA;Ed+CF,yDAAA;EACQ,iDAAA;CLwtFT;AmBtwFG;EACE,sBAAA;Ed4CJ,0EAAA;EACQ,kEAAA;CL6tFT;AkBp3ED;EC/YI,eAAA;EACA,sBAAA;EACA,0BAAA;CnBswFH;AkBz3ED;ECzYI,eAAA;CnBqwFH;AkBz3ED;;;;;;;;;;EChaI,eAAA;CnBqyFH;AkBr4ED;EC5ZI,sBAAA;Ed+CF,yDAAA;EACQ,iDAAA;CLsvFT;AmBpyFG;EACE,sBAAA;Ed4CJ,0EAAA;EACQ,kEAAA;CL2vFT;AkB/4ED;EClZI,eAAA;EACA,sBAAA;EACA,0BAAA;CnBoyFH;AkBp5ED;EC5YI,eAAA;CnBmyFH;AkBh5EC;EACE,UAAA;ClBk5EH;AkBh5EC;EACE,OAAA;ClBk5EH;AkBx4ED;EACE,eAAA;EACA,gBAAA;EACA,oBAAA;EACA,eAAA;ClB04ED;AkBvzED;EAwEA;IAtIM,sBAAA;IACA,iBAAA;IACA,uBAAA;GlBy3EH;EkBrvEH;IA/HM,sBAAA;IACA,YAAA;IACA,uBAAA;GlBu3EH;EkB1vEH;IAxHM,sBAAA;GlBq3EH;EkB7vEH;IApHM,sBAAA;IACA,uBAAA;GlBo3EH;EkBjwEH;;;IA9GQ,YAAA;GlBo3EL;EkBtwEH;IAxGM,YAAA;GlBi3EH;EkBzwEH;IApGM,iBAAA;IACA,uBAAA;GlBg3EH;EkB7wEH;;IA5FM,sBAAA;IACA,cAAA;IACA,iBAAA;IACA,uBAAA;GlB62EH;EkBpxEH;;IAtFQ,gBAAA;GlB82EL;EkBxxEH;;IAjFM,mBAAA;IACA,eAAA;GlB62EH;EkB7xEH;IA3EM,OAAA;GlB22EH;CACF;AkBj2ED;;;;EASI,cAAA;EACA,iBAAA;EACA,iBAAA;ClB81EH;AkBz2ED;;EAiBI,iBAAA;ClB41EH;AkB72ED;EJthBE,mBAAA;EACA,oBAAA;Cds4FD;AkB10EC;EAyBF;IAnCM,kBAAA;IACA,iBAAA;IACA,iBAAA;GlBw1EH;CACF;AkBx3ED;EAwCI,YAAA;ClBm1EH;AkBr0EC;EAUF;IAdQ,kBAAA;IACA,gBAAA;GlB60EL;CACF;AkBn0EC;EAEF;IANQ,iBAAA;IACA,gBAAA;GlB20EL;CACF;AoBp6FD;EACE,sBAAA;EACA,iBAAA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,+BAAA;MAAA,2BAAA;EACA,gBAAA;EACA,uBAAA;EACA,8BAAA;EACA,oBAAA;EC0CA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,mBAAA;EhB+JA,0BAAA;EACG,uBAAA;EACC,sBAAA;EACI,kBAAA;CL+tFT;AoBv6FG;;;;;;EdnBF,2CAAA;EACA,qBAAA;CNk8FD;AoB16FC;;;EAGE,YAAA;EACA,sBAAA;CpB46FH;AoBz6FC;;EAEE,WAAA;EACA,uBAAA;Ef2BF,yDAAA;EACQ,iDAAA;CLi5FT;AoBz6FC;;;EAGE,oBAAA;EE7CF,cAAA;EAGA,0BAAA;EjB8DA,yBAAA;EACQ,iBAAA;CL05FT;AoBz6FG;;EAEE,qBAAA;CpB26FL;AoBl6FD;EC3DE,YAAA;EACA,uBAAA;EACA,mBAAA;CrBg+FD;AqB99FC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBg+FP;AqB99FC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBg+FP;AqB99FC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBg+FP;AqB99FG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBs+FT;AqBn+FC;;;EAGE,uBAAA;CrBq+FH;AqBh+FG;;;;;;;;;EAGE,uBAAA;EACI,mBAAA;CrBw+FT;AoBv9FD;ECZI,YAAA;EACA,uBAAA;CrBs+FH;AoBx9FD;EC9DE,YAAA;EACA,0BAAA;EACA,sBAAA;CrByhGD;AqBvhGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrByhGP;AqBvhGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrByhGP;AqBvhGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrByhGP;AqBvhGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB+hGT;AqB5hGC;;;EAGE,uBAAA;CrB8hGH;AqBzhGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrBiiGT;AoB7gGD;ECfI,eAAA;EACA,uBAAA;CrB+hGH;AoB7gGD;EClEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBklGD;AqBhlGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBklGP;AqBhlGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBklGP;AqBhlGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBklGP;AqBhlGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBwlGT;AqBrlGC;;;EAGE,uBAAA;CrBulGH;AqBllGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrB0lGT;AoBlkGD;ECnBI,eAAA;EACA,uBAAA;CrBwlGH;AoBlkGD;ECtEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB2oGD;AqBzoGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB2oGP;AqBzoGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB2oGP;AqBzoGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB2oGP;AqBzoGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBipGT;AqB9oGC;;;EAGE,uBAAA;CrBgpGH;AqB3oGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrBmpGT;AoBvnGD;ECvBI,eAAA;EACA,uBAAA;CrBipGH;AoBvnGD;EC1EE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBosGD;AqBlsGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBosGP;AqBlsGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBosGP;AqBlsGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBosGP;AqBlsGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB0sGT;AqBvsGC;;;EAGE,uBAAA;CrBysGH;AqBpsGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrB4sGT;AoB5qGD;EC3BI,eAAA;EACA,uBAAA;CrB0sGH;AoB5qGD;EC9EE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB6vGD;AqB3vGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB6vGP;AqB3vGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB6vGP;AqB3vGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB6vGP;AqB3vGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBmwGT;AqBhwGC;;;EAGE,uBAAA;CrBkwGH;AqB7vGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrBqwGT;AoBjuGD;EC/BI,eAAA;EACA,uBAAA;CrBmwGH;AoB5tGD;EACE,eAAA;EACA,oBAAA;EACA,iBAAA;CpB8tGD;AoB5tGC;;;;;EAKE,8BAAA;EfnCF,yBAAA;EACQ,iBAAA;CLkwGT;AoB7tGC;;;;EAIE,0BAAA;CpB+tGH;AoB7tGC;;EAEE,eAAA;EACA,2BAAA;EACA,8BAAA;CpB+tGH;AoB3tGG;;;;EAEE,eAAA;EACA,sBAAA;CpB+tGL;AoBttGD;;ECxEE,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CrBkyGD;AoBztGD;;EC5EE,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CrByyGD;AoB5tGD;;EChFE,iBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CrBgzGD;AoB3tGD;EACE,eAAA;EACA,YAAA;CpB6tGD;AoBztGD;EACE,gBAAA;CpB2tGD;AoBptGC;;;EACE,YAAA;CpBwtGH;AuBl3GD;EACE,WAAA;ElBoLA,yCAAA;EACK,oCAAA;EACG,iCAAA;CLisGT;AuBr3GC;EACE,WAAA;CvBu3GH;AuBn3GD;EACE,cAAA;CvBq3GD;AuBn3GC;EAAY,eAAA;CvBs3Gb;AuBr3GC;EAAY,mBAAA;CvBw3Gb;AuBv3GC;EAAY,yBAAA;CvB03Gb;AuBv3GD;EACE,mBAAA;EACA,UAAA;EACA,iBAAA;ElBuKA,gDAAA;EACQ,2CAAA;KAAA,wCAAA;EAOR,mCAAA;EACQ,8BAAA;KAAA,2BAAA;EAGR,yCAAA;EACQ,oCAAA;KAAA,iCAAA;CL2sGT;AwBr5GD;EACE,sBAAA;EACA,SAAA;EACA,UAAA;EACA,iBAAA;EACA,uBAAA;EACA,uBAAA;EACA,yBAAA;EACA,oCAAA;EACA,mCAAA;CxBu5GD;AwBn5GD;;EAEE,mBAAA;CxBq5GD;AwBj5GD;EACE,WAAA;CxBm5GD;AwB/4GD;EACE,mBAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,cAAA;EACA,YAAA;EACA,iBAAA;EACA,eAAA;EACA,gBAAA;EACA,iBAAA;EACA,gBAAA;EACA,iBAAA;EACA,uBAAA;EACA,uBAAA;EACA,sCAAA;EACA,mBAAA;EnBsBA,oDAAA;EACQ,4CAAA;EmBrBR,qCAAA;UAAA,6BAAA;CxBk5GD;AwB74GC;EACE,SAAA;EACA,WAAA;CxB+4GH;AwBx6GD;ECzBE,YAAA;EACA,cAAA;EACA,iBAAA;EACA,0BAAA;CzBo8GD;AwB96GD;EAmCI,eAAA;EACA,kBAAA;EACA,YAAA;EACA,oBAAA;EACA,wBAAA;EACA,eAAA;EACA,oBAAA;CxB84GH;AwBx4GC;;EAEE,sBAAA;EACA,eAAA;EACA,0BAAA;CxB04GH;AwBp4GC;;;EAGE,YAAA;EACA,sBAAA;EACA,WAAA;EACA,0BAAA;CxBs4GH;AwB73GC;;;EAGE,eAAA;CxB+3GH;AwB33GC;;EAEE,sBAAA;EACA,8BAAA;EACA,uBAAA;EE3GF,oEAAA;EF6GE,oBAAA;CxB63GH;AwBx3GD;EAGI,eAAA;CxBw3GH;AwB33GD;EAQI,WAAA;CxBs3GH;AwB92GD;EACE,WAAA;EACA,SAAA;CxBg3GD;AwBx2GD;EACE,QAAA;EACA,YAAA;CxB02GD;AwBt2GD;EACE,eAAA;EACA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,oBAAA;CxBw2GD;AwBp2GD;EACE,gBAAA;EACA,QAAA;EACA,SAAA;EACA,UAAA;EACA,OAAA;EACA,aAAA;CxBs2GD;AwBl2GD;EACE,SAAA;EACA,WAAA;CxBo2GD;AwB51GD;;EAII,cAAA;EACA,0BAAA;EACA,4BAAA;EACA,YAAA;CxB41GH;AwBn2GD;;EAWI,UAAA;EACA,aAAA;EACA,mBAAA;CxB41GH;AwBv0GD;EAXE;IApEA,WAAA;IACA,SAAA;GxB05GC;EwBv1GD;IA1DA,QAAA;IACA,YAAA;GxBo5GC;CACF;A2BpiHD;;EAEE,mBAAA;EACA,sBAAA;EACA,uBAAA;C3BsiHD;A2B1iHD;;EAMI,mBAAA;EACA,YAAA;C3BwiHH;A2BtiHG;;;;;;;;EAIE,WAAA;C3B4iHL;A2BtiHD;;;;EAKI,kBAAA;C3BuiHH;A2BliHD;EACE,kBAAA;C3BoiHD;A2BriHD;;;EAOI,YAAA;C3BmiHH;A2B1iHD;;;EAYI,iBAAA;C3BmiHH;A2B/hHD;EACE,iBAAA;C3BiiHD;A2B7hHD;EACE,eAAA;C3B+hHD;A2B9hHC;EClDA,8BAAA;EACG,2BAAA;C5BmlHJ;A2B7hHD;;EC/CE,6BAAA;EACG,0BAAA;C5BglHJ;A2B5hHD;EACE,YAAA;C3B8hHD;A2B5hHD;EACE,iBAAA;C3B8hHD;A2B5hHD;;ECnEE,8BAAA;EACG,2BAAA;C5BmmHJ;A2B3hHD;ECjEE,6BAAA;EACG,0BAAA;C5B+lHJ;A2B1hHD;;EAEE,WAAA;C3B4hHD;A2B3gHD;EACE,kBAAA;EACA,mBAAA;C3B6gHD;A2B3gHD;EACE,mBAAA;EACA,oBAAA;C3B6gHD;A2BxgHD;EtB/CE,yDAAA;EACQ,iDAAA;CL0jHT;A2BxgHC;EtBnDA,yBAAA;EACQ,iBAAA;CL8jHT;A2BrgHD;EACE,eAAA;C3BugHD;A2BpgHD;EACE,wBAAA;EACA,uBAAA;C3BsgHD;A2BngHD;EACE,wBAAA;C3BqgHD;A2B9/GD;;;EAII,eAAA;EACA,YAAA;EACA,YAAA;EACA,gBAAA;C3B+/GH;A2BtgHD;EAcM,YAAA;C3B2/GL;A2BzgHD;;;;EAsBI,iBAAA;EACA,eAAA;C3By/GH;A2Bp/GC;EACE,iBAAA;C3Bs/GH;A2Bp/GC;EC3KA,6BAAA;EACC,4BAAA;EAOD,8BAAA;EACC,6BAAA;C5B4pHF;A2Bt/GC;EC/KA,2BAAA;EACC,0BAAA;EAOD,gCAAA;EACC,+BAAA;C5BkqHF;A2Bv/GD;EACE,iBAAA;C3By/GD;A2Bv/GD;;EC/KE,8BAAA;EACC,6BAAA;C5B0qHF;A2Bt/GD;EC7LE,2BAAA;EACC,0BAAA;C5BsrHF;A2Bl/GD;EACE,eAAA;EACA,YAAA;EACA,oBAAA;EACA,0BAAA;C3Bo/GD;A2Bx/GD;;EAOI,YAAA;EACA,oBAAA;EACA,UAAA;C3Bq/GH;A2B9/GD;EAYI,YAAA;C3Bq/GH;A2BjgHD;EAgBI,WAAA;C3Bo/GH;A2Bn+GD;;;;EAKM,mBAAA;EACA,uBAAA;EACA,qBAAA;C3Bo+GL;A6B9sHD;EACE,mBAAA;EACA,eAAA;EACA,0BAAA;C7BgtHD;A6B7sHC;EACE,YAAA;EACA,gBAAA;EACA,iBAAA;C7B+sHH;A6BxtHD;EAeI,mBAAA;EACA,WAAA;EAKA,YAAA;EAEA,YAAA;EACA,iBAAA;C7BusHH;A6BrsHG;EACE,WAAA;C7BusHL;A6B7rHD;;;EV0BE,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CnBwqHD;AmBtqHC;;;EACE,aAAA;EACA,kBAAA;CnB0qHH;AmBvqHC;;;;;;EAEE,aAAA;CnB6qHH;A6B/sHD;;;EVqBE,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CnB+rHD;AmB7rHC;;;EACE,aAAA;EACA,kBAAA;CnBisHH;AmB9rHC;;;;;;EAEE,aAAA;CnBosHH;A6B7tHD;;;EAGE,oBAAA;C7B+tHD;A6B7tHC;;;EACE,iBAAA;C7BiuHH;A6B7tHD;;EAEE,UAAA;EACA,oBAAA;EACA,uBAAA;C7B+tHD;A6B1tHD;EACE,kBAAA;EACA,gBAAA;EACA,oBAAA;EACA,eAAA;EACA,eAAA;EACA,mBAAA;EACA,0BAAA;EACA,uBAAA;EACA,mBAAA;C7B4tHD;A6BztHC;EACE,kBAAA;EACA,gBAAA;EACA,mBAAA;C7B2tHH;A6BztHC;EACE,mBAAA;EACA,gBAAA;EACA,mBAAA;C7B2tHH;A6B/uHD;;EA0BI,cAAA;C7BytHH;A6BptHD;;;;;;;EDpGE,8BAAA;EACG,2BAAA;C5Bi0HJ;A6BrtHD;EACE,gBAAA;C7ButHD;A6BrtHD;;;;;;;EDxGE,6BAAA;EACG,0BAAA;C5Bs0HJ;A6BttHD;EACE,eAAA;C7BwtHD;A6BntHD;EACE,mBAAA;EAGA,aAAA;EACA,oBAAA;C7BmtHD;A6BxtHD;EAUI,mBAAA;C7BitHH;A6B3tHD;EAYM,kBAAA;C7BktHL;A6B/sHG;;;EAGE,WAAA;C7BitHL;A6B5sHC;;EAGI,mBAAA;C7B6sHL;A6B1sHC;;EAGI,WAAA;EACA,kBAAA;C7B2sHL;A8B12HD;EACE,iBAAA;EACA,gBAAA;EACA,iBAAA;C9B42HD;A8B/2HD;EAOI,mBAAA;EACA,eAAA;C9B22HH;A8Bn3HD;EAWM,mBAAA;EACA,eAAA;EACA,mBAAA;C9B22HL;A8B12HK;;EAEE,sBAAA;EACA,0BAAA;C9B42HP;A8Bv2HG;EACE,eAAA;C9By2HL;A8Bv2HK;;EAEE,eAAA;EACA,sBAAA;EACA,8BAAA;EACA,oBAAA;C9By2HP;A8Bl2HG;;;EAGE,0BAAA;EACA,sBAAA;C9Bo2HL;A8B74HD;ELHE,YAAA;EACA,cAAA;EACA,iBAAA;EACA,0BAAA;CzBm5HD;A8Bn5HD;EA0DI,gBAAA;C9B41HH;A8Bn1HD;EACE,8BAAA;C9Bq1HD;A8Bt1HD;EAGI,YAAA;EAEA,oBAAA;C9Bq1HH;A8B11HD;EASM,kBAAA;EACA,wBAAA;EACA,8BAAA;EACA,2BAAA;C9Bo1HL;A8Bn1HK;EACE,mCAAA;C9Bq1HP;A8B/0HK;;;EAGE,eAAA;EACA,uBAAA;EACA,uBAAA;EACA,iCAAA;EACA,gBAAA;C9Bi1HP;A8B50HC;EAqDA,YAAA;EA8BA,iBAAA;C9B6vHD;A8Bh1HC;EAwDE,YAAA;C9B2xHH;A8Bn1HC;EA0DI,mBAAA;EACA,mBAAA;C9B4xHL;A8Bv1HC;EAgEE,UAAA;EACA,WAAA;C9B0xHH;A8B9wHD;EA0DA;IAjEM,oBAAA;IACA,UAAA;G9ByxHH;E8BztHH;IA9DQ,iBAAA;G9B0xHL;CACF;A8Bp2HC;EAuFE,gBAAA;EACA,mBAAA;C9BgxHH;A8Bx2HC;;;EA8FE,uBAAA;C9B+wHH;A8BjwHD;EA2BA;IApCM,8BAAA;IACA,2BAAA;G9B8wHH;E8B3uHH;;;IA9BM,0BAAA;G9B8wHH;CACF;A8B/2HD;EAEI,YAAA;C9Bg3HH;A8Bl3HD;EAMM,mBAAA;C9B+2HL;A8Br3HD;EASM,iBAAA;C9B+2HL;A8B12HK;;;EAGE,YAAA;EACA,0BAAA;C9B42HP;A8Bp2HD;EAEI,YAAA;C9Bq2HH;A8Bv2HD;EAIM,gBAAA;EACA,eAAA;C9Bs2HL;A8B11HD;EACE,YAAA;C9B41HD;A8B71HD;EAII,YAAA;C9B41HH;A8Bh2HD;EAMM,mBAAA;EACA,mBAAA;C9B61HL;A8Bp2HD;EAYI,UAAA;EACA,WAAA;C9B21HH;A8B/0HD;EA0DA;IAjEM,oBAAA;IACA,UAAA;G9B01HH;E8B1xHH;IA9DQ,iBAAA;G9B21HL;CACF;A8Bn1HD;EACE,iBAAA;C9Bq1HD;A8Bt1HD;EAKI,gBAAA;EACA,mBAAA;C9Bo1HH;A8B11HD;;;EAYI,uBAAA;C9Bm1HH;A8Br0HD;EA2BA;IApCM,8BAAA;IACA,2BAAA;G9Bk1HH;E8B/yHH;;;IA9BM,0BAAA;G9Bk1HH;CACF;A8Bz0HD;EAEI,cAAA;C9B00HH;A8B50HD;EAKI,eAAA;C9B00HH;A8Bj0HD;EAEE,iBAAA;EF3OA,2BAAA;EACC,0BAAA;C5B8iIF;A+BxiID;EACE,mBAAA;EACA,iBAAA;EACA,oBAAA;EACA,8BAAA;C/B0iID;A+BliID;EA8nBA;IAhoBI,mBAAA;G/BwiID;CACF;A+BzhID;EAgnBA;IAlnBI,YAAA;G/B+hID;CACF;A+BjhID;EACE,oBAAA;EACA,oBAAA;EACA,mBAAA;EACA,kCAAA;EACA,2DAAA;UAAA,mDAAA;EAEA,kCAAA;C/BkhID;A+BhhIC;EACE,iBAAA;C/BkhIH;A+Bt/HD;EA6jBA;IArlBI,YAAA;IACA,cAAA;IACA,yBAAA;YAAA,iBAAA;G/BkhID;E+BhhIC;IACE,0BAAA;IACA,wBAAA;IACA,kBAAA;IACA,6BAAA;G/BkhIH;E+B/gIC;IACE,oBAAA;G/BihIH;E+B5gIC;;;IAGE,gBAAA;IACA,iBAAA;G/B8gIH;CACF;A+B1gID;;EAGI,kBAAA;C/B2gIH;A+BtgIC;EAmjBF;;IArjBM,kBAAA;G/B6gIH;CACF;A+BpgID;;;;EAII,oBAAA;EACA,mBAAA;C/BsgIH;A+BhgIC;EAgiBF;;;;IAniBM,gBAAA;IACA,eAAA;G/B0gIH;CACF;A+B9/HD;EACE,cAAA;EACA,sBAAA;C/BggID;A+B3/HD;EA8gBA;IAhhBI,iBAAA;G/BigID;CACF;A+B7/HD;;EAEE,gBAAA;EACA,SAAA;EACA,QAAA;EACA,cAAA;C/B+/HD;A+Bz/HD;EAggBA;;IAlgBI,iBAAA;G/BggID;CACF;A+B9/HD;EACE,OAAA;EACA,sBAAA;C/BggID;A+B9/HD;EACE,UAAA;EACA,iBAAA;EACA,sBAAA;C/BggID;A+B1/HD;EACE,YAAA;EACA,mBAAA;EACA,gBAAA;EACA,kBAAA;EACA,aAAA;C/B4/HD;A+B1/HC;;EAEE,sBAAA;C/B4/HH;A+BrgID;EAaI,eAAA;C/B2/HH;A+Bl/HD;EALI;;IAEE,mBAAA;G/B0/HH;CACF;A+Bh/HD;EACE,mBAAA;EACA,aAAA;EACA,mBAAA;EACA,kBAAA;EC9LA,gBAAA;EACA,mBAAA;ED+LA,8BAAA;EACA,uBAAA;EACA,8BAAA;EACA,mBAAA;C/Bm/HD;A+B/+HC;EACE,WAAA;C/Bi/HH;A+B//HD;EAmBI,eAAA;EACA,YAAA;EACA,YAAA;EACA,mBAAA;C/B++HH;A+BrgID;EAyBI,gBAAA;C/B++HH;A+Bz+HD;EAqbA;IAvbI,cAAA;G/B++HD;CACF;A+Bt+HD;EACE,oBAAA;C/Bw+HD;A+Bz+HD;EAII,kBAAA;EACA,qBAAA;EACA,kBAAA;C/Bw+HH;A+B58HC;EA2YF;IAjaM,iBAAA;IACA,YAAA;IACA,YAAA;IACA,cAAA;IACA,8BAAA;IACA,UAAA;IACA,yBAAA;YAAA,iBAAA;G/Bs+HH;E+B3kHH;;IAxZQ,2BAAA;G/Bu+HL;E+B/kHH;IArZQ,kBAAA;G/Bu+HL;E+Bt+HK;;IAEE,uBAAA;G/Bw+HP;CACF;A+Bt9HD;EA+XA;IA1YI,YAAA;IACA,UAAA;G/Bq+HD;E+B5lHH;IAtYM,YAAA;G/Bq+HH;E+B/lHH;IApYQ,kBAAA;IACA,qBAAA;G/Bs+HL;CACF;A+B39HD;EACE,mBAAA;EACA,oBAAA;EACA,mBAAA;EACA,kCAAA;EACA,qCAAA;E1B9NA,6FAAA;EACQ,qFAAA;E2B/DR,gBAAA;EACA,mBAAA;ChC4vID;AkBtuHD;EAwEA;IAtIM,sBAAA;IACA,iBAAA;IACA,uBAAA;GlBwyHH;EkBpqHH;IA/HM,sBAAA;IACA,YAAA;IACA,uBAAA;GlBsyHH;EkBzqHH;IAxHM,sBAAA;GlBoyHH;EkB5qHH;IApHM,sBAAA;IACA,uBAAA;GlBmyHH;EkBhrHH;;;IA9GQ,YAAA;GlBmyHL;EkBrrHH;IAxGM,YAAA;GlBgyHH;EkBxrHH;IApGM,iBAAA;IACA,uBAAA;GlB+xHH;EkB5rHH;;IA5FM,sBAAA;IACA,cAAA;IACA,iBAAA;IACA,uBAAA;GlB4xHH;EkBnsHH;;IAtFQ,gBAAA;GlB6xHL;EkBvsHH;;IAjFM,mBAAA;IACA,eAAA;GlB4xHH;EkB5sHH;IA3EM,OAAA;GlB0xHH;CACF;A+BpgIC;EAmWF;IAzWM,mBAAA;G/B8gIH;E+B5gIG;IACE,iBAAA;G/B8gIL;CACF;A+B7/HD;EAoVA;IA5VI,YAAA;IACA,UAAA;IACA,eAAA;IACA,gBAAA;IACA,eAAA;IACA,kBAAA;I1BzPF,yBAAA;IACQ,iBAAA;GLmwIP;CACF;A+BngID;EACE,cAAA;EHpUA,2BAAA;EACC,0BAAA;C5B00IF;A+BngID;EACE,iBAAA;EHzUA,6BAAA;EACC,4BAAA;EAOD,8BAAA;EACC,6BAAA;C5By0IF;A+B//HD;EChVE,gBAAA;EACA,mBAAA;ChCk1ID;A+BhgIC;ECnVA,iBAAA;EACA,oBAAA;ChCs1ID;A+BjgIC;ECtVA,iBAAA;EACA,oBAAA;ChC01ID;A+B3/HD;EChWE,iBAAA;EACA,oBAAA;ChC81ID;A+Bv/HD;EAsSA;IA1SI,YAAA;IACA,kBAAA;IACA,mBAAA;G/B+/HD;CACF;A+Bl+HD;EAhBE;IExWA,uBAAA;GjC81IC;E+Br/HD;IE5WA,wBAAA;IF8WE,oBAAA;G/Bu/HD;E+Bz/HD;IAKI,gBAAA;G/Bu/HH;CACF;A+B9+HD;EACE,0BAAA;EACA,sBAAA;C/Bg/HD;A+Bl/HD;EAKI,YAAA;C/Bg/HH;A+B/+HG;;EAEE,eAAA;EACA,8BAAA;C/Bi/HL;A+B1/HD;EAcI,YAAA;C/B++HH;A+B7/HD;EAmBM,YAAA;C/B6+HL;A+B3+HK;;EAEE,YAAA;EACA,8BAAA;C/B6+HP;A+Bz+HK;;;EAGE,YAAA;EACA,0BAAA;C/B2+HP;A+Bv+HK;;;EAGE,YAAA;EACA,8BAAA;C/By+HP;A+BjhID;EA8CI,mBAAA;C/Bs+HH;A+Br+HG;;EAEE,uBAAA;C/Bu+HL;A+BxhID;EAoDM,uBAAA;C/Bu+HL;A+B3hID;;EA0DI,sBAAA;C/Bq+HH;A+B99HK;;;EAGE,0BAAA;EACA,YAAA;C/Bg+HP;A+B/7HC;EAoKF;IA7LU,YAAA;G/B49HP;E+B39HO;;IAEE,YAAA;IACA,8BAAA;G/B69HT;E+Bz9HO;;;IAGE,YAAA;IACA,0BAAA;G/B29HT;E+Bv9HO;;;IAGE,YAAA;IACA,8BAAA;G/By9HT;CACF;A+B3jID;EA8GI,YAAA;C/Bg9HH;A+B/8HG;EACE,YAAA;C/Bi9HL;A+BjkID;EAqHI,YAAA;C/B+8HH;A+B98HG;;EAEE,YAAA;C/Bg9HL;A+B58HK;;;;EAEE,YAAA;C/Bg9HP;A+Bx8HD;EACE,uBAAA;EACA,sBAAA;C/B08HD;A+B58HD;EAKI,eAAA;C/B08HH;A+Bz8HG;;EAEE,YAAA;EACA,8BAAA;C/B28HL;A+Bp9HD;EAcI,eAAA;C/By8HH;A+Bv9HD;EAmBM,eAAA;C/Bu8HL;A+Br8HK;;EAEE,YAAA;EACA,8BAAA;C/Bu8HP;A+Bn8HK;;;EAGE,YAAA;EACA,0BAAA;C/Bq8HP;A+Bj8HK;;;EAGE,YAAA;EACA,8BAAA;C/Bm8HP;A+B3+HD;EA+CI,mBAAA;C/B+7HH;A+B97HG;;EAEE,uBAAA;C/Bg8HL;A+Bl/HD;EAqDM,uBAAA;C/Bg8HL;A+Br/HD;;EA2DI,sBAAA;C/B87HH;A+Bx7HK;;;EAGE,0BAAA;EACA,YAAA;C/B07HP;A+Bn5HC;EAwBF;IAvDU,sBAAA;G/Bs7HP;E+B/3HH;IApDU,0BAAA;G/Bs7HP;E+Bl4HH;IAjDU,eAAA;G/Bs7HP;E+Br7HO;;IAEE,YAAA;IACA,8BAAA;G/Bu7HT;E+Bn7HO;;;IAGE,YAAA;IACA,0BAAA;G/Bq7HT;E+Bj7HO;;;IAGE,YAAA;IACA,8BAAA;G/Bm7HT;CACF;A+B3hID;EA+GI,eAAA;C/B+6HH;A+B96HG;EACE,YAAA;C/Bg7HL;A+BjiID;EAsHI,eAAA;C/B86HH;A+B76HG;;EAEE,YAAA;C/B+6HL;A+B36HK;;;;EAEE,YAAA;C/B+6HP;AkCzjJD;EACE,kBAAA;EACA,oBAAA;EACA,iBAAA;EACA,0BAAA;EACA,mBAAA;ClC2jJD;AkChkJD;EAQI,sBAAA;ClC2jJH;AkCnkJD;EAWM,kBAAA;EACA,eAAA;EACA,YAAA;ClC2jJL;AkCxkJD;EAkBI,eAAA;ClCyjJH;AmC7kJD;EACE,sBAAA;EACA,gBAAA;EACA,eAAA;EACA,mBAAA;CnC+kJD;AmCnlJD;EAOI,gBAAA;CnC+kJH;AmCtlJD;;EAUM,mBAAA;EACA,YAAA;EACA,kBAAA;EACA,wBAAA;EACA,sBAAA;EACA,eAAA;EACA,uBAAA;EACA,uBAAA;EACA,kBAAA;CnCglJL;AmC9kJG;;EAGI,eAAA;EPXN,+BAAA;EACG,4BAAA;C5B2lJJ;AmC7kJG;;EPvBF,gCAAA;EACG,6BAAA;C5BwmJJ;AmCxkJG;;;;EAEE,WAAA;EACA,eAAA;EACA,0BAAA;EACA,mBAAA;CnC4kJL;AmCtkJG;;;;;;EAGE,WAAA;EACA,YAAA;EACA,0BAAA;EACA,sBAAA;EACA,gBAAA;CnC2kJL;AmCloJD;;;;;;EAkEM,eAAA;EACA,uBAAA;EACA,mBAAA;EACA,oBAAA;CnCwkJL;AmC/jJD;;EC3EM,mBAAA;EACA,gBAAA;EACA,uBAAA;CpC8oJL;AoC5oJG;;ERKF,+BAAA;EACG,4BAAA;C5B2oJJ;AoC3oJG;;ERTF,gCAAA;EACG,6BAAA;C5BwpJJ;AmC1kJD;;EChFM,kBAAA;EACA,gBAAA;EACA,iBAAA;CpC8pJL;AoC5pJG;;ERKF,+BAAA;EACG,4BAAA;C5B2pJJ;AoC3pJG;;ERTF,gCAAA;EACG,6BAAA;C5BwqJJ;AqC3qJD;EACE,gBAAA;EACA,eAAA;EACA,iBAAA;EACA,mBAAA;CrC6qJD;AqCjrJD;EAOI,gBAAA;CrC6qJH;AqCprJD;;EAUM,sBAAA;EACA,kBAAA;EACA,uBAAA;EACA,uBAAA;EACA,oBAAA;CrC8qJL;AqC5rJD;;EAmBM,sBAAA;EACA,0BAAA;CrC6qJL;AqCjsJD;;EA2BM,aAAA;CrC0qJL;AqCrsJD;;EAkCM,YAAA;CrCuqJL;AqCzsJD;;;;EA2CM,eAAA;EACA,uBAAA;EACA,oBAAA;CrCoqJL;AsCltJD;EACE,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,kBAAA;EACA,eAAA;EACA,YAAA;EACA,mBAAA;EACA,oBAAA;EACA,yBAAA;EACA,qBAAA;CtCotJD;AsChtJG;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;CtCktJL;AsC7sJC;EACE,cAAA;CtC+sJH;AsC3sJC;EACE,mBAAA;EACA,UAAA;CtC6sJH;AsCtsJD;ECtCE,0BAAA;CvC+uJD;AuC5uJG;;EAEE,0BAAA;CvC8uJL;AsCzsJD;EC1CE,0BAAA;CvCsvJD;AuCnvJG;;EAEE,0BAAA;CvCqvJL;AsC5sJD;EC9CE,0BAAA;CvC6vJD;AuC1vJG;;EAEE,0BAAA;CvC4vJL;AsC/sJD;EClDE,0BAAA;CvCowJD;AuCjwJG;;EAEE,0BAAA;CvCmwJL;AsCltJD;ECtDE,0BAAA;CvC2wJD;AuCxwJG;;EAEE,0BAAA;CvC0wJL;AsCrtJD;EC1DE,0BAAA;CvCkxJD;AuC/wJG;;EAEE,0BAAA;CvCixJL;AwCnxJD;EACE,sBAAA;EACA,gBAAA;EACA,iBAAA;EACA,gBAAA;EACA,kBAAA;EACA,YAAA;EACA,eAAA;EACA,uBAAA;EACA,oBAAA;EACA,mBAAA;EACA,0BAAA;EACA,oBAAA;CxCqxJD;AwClxJC;EACE,cAAA;CxCoxJH;AwChxJC;EACE,mBAAA;EACA,UAAA;CxCkxJH;AwC/wJC;;EAEE,OAAA;EACA,iBAAA;CxCixJH;AwC5wJG;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;CxC8wJL;AwCzwJC;;EAEE,eAAA;EACA,uBAAA;CxC2wJH;AwCxwJC;EACE,aAAA;CxC0wJH;AwCvwJC;EACE,kBAAA;CxCywJH;AwCtwJC;EACE,iBAAA;CxCwwJH;AyCl0JD;EACE,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,eAAA;EACA,0BAAA;CzCo0JD;AyCz0JD;;EASI,eAAA;CzCo0JH;AyC70JD;EAaI,oBAAA;EACA,gBAAA;EACA,iBAAA;CzCm0JH;AyCl1JD;EAmBI,0BAAA;CzCk0JH;AyC/zJC;;EAEE,mBAAA;EACA,mBAAA;EACA,oBAAA;CzCi0JH;AyC31JD;EA8BI,gBAAA;CzCg0JH;AyC9yJD;EACA;IAfI,kBAAA;IACA,qBAAA;GzCg0JD;EyC9zJC;;IAEE,mBAAA;IACA,oBAAA;GzCg0JH;EyCvzJH;;IAJM,gBAAA;GzC+zJH;CACF;A0C52JD;EACE,eAAA;EACA,aAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;ErCiLA,4CAAA;EACK,uCAAA;EACG,oCAAA;CL8rJT;A0Cx3JD;;EAaI,kBAAA;EACA,mBAAA;C1C+2JH;A0C32JC;;;EAGE,sBAAA;C1C62JH;A0Cl4JD;EA0BI,aAAA;EACA,eAAA;C1C22JH;A2Cp4JD;EACE,cAAA;EACA,oBAAA;EACA,8BAAA;EACA,mBAAA;C3Cs4JD;A2C14JD;EAQI,cAAA;EAEA,eAAA;C3Co4JH;A2C94JD;EAeI,kBAAA;C3Ck4JH;A2Cj5JD;;EAqBI,iBAAA;C3Cg4JH;A2Cr5JD;EAyBI,gBAAA;C3C+3JH;A2Cv3JD;;EAEE,oBAAA;C3Cy3JD;A2C33JD;;EAMI,mBAAA;EACA,UAAA;EACA,aAAA;EACA,eAAA;C3Cy3JH;A2Cj3JD;ECvDE,0BAAA;EACA,sBAAA;EACA,eAAA;C5C26JD;A2Ct3JD;EClDI,0BAAA;C5C26JH;A2Cz3JD;EC/CI,eAAA;C5C26JH;A2Cx3JD;EC3DE,0BAAA;EACA,sBAAA;EACA,eAAA;C5Cs7JD;A2C73JD;ECtDI,0BAAA;C5Cs7JH;A2Ch4JD;ECnDI,eAAA;C5Cs7JH;A2C/3JD;EC/DE,0BAAA;EACA,sBAAA;EACA,eAAA;C5Ci8JD;A2Cp4JD;EC1DI,0BAAA;C5Ci8JH;A2Cv4JD;ECvDI,eAAA;C5Ci8JH;A2Ct4JD;ECnEE,0BAAA;EACA,sBAAA;EACA,eAAA;C5C48JD;A2C34JD;EC9DI,0BAAA;C5C48JH;A2C94JD;EC3DI,eAAA;C5C48JH;A6C98JD;EACE;IAAQ,4BAAA;G7Ci9JP;E6Ch9JD;IAAQ,yBAAA;G7Cm9JP;CACF;A6Ch9JD;EACE;IAAQ,4BAAA;G7Cm9JP;E6Cl9JD;IAAQ,yBAAA;G7Cq9JP;CACF;A6Cx9JD;EACE;IAAQ,4BAAA;G7Cm9JP;E6Cl9JD;IAAQ,yBAAA;G7Cq9JP;CACF;A6C98JD;EACE,iBAAA;EACA,aAAA;EACA,oBAAA;EACA,0BAAA;EACA,mBAAA;ExCsCA,uDAAA;EACQ,+CAAA;CL26JT;A6C78JD;EACE,YAAA;EACA,UAAA;EACA,aAAA;EACA,gBAAA;EACA,kBAAA;EACA,YAAA;EACA,mBAAA;EACA,0BAAA;ExCyBA,uDAAA;EACQ,+CAAA;EAyHR,oCAAA;EACK,+BAAA;EACG,4BAAA;CL+zJT;A6C18JD;;ECCI,8MAAA;EACA,yMAAA;EACA,sMAAA;EDAF,mCAAA;UAAA,2BAAA;C7C88JD;A6Cv8JD;;ExC5CE,2DAAA;EACK,sDAAA;EACG,mDAAA;CLu/JT;A6Cp8JD;EErEE,0BAAA;C/C4gKD;A+CzgKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9C49JH;A6Cx8JD;EEzEE,0BAAA;C/CohKD;A+CjhKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9Co+JH;A6C58JD;EE7EE,0BAAA;C/C4hKD;A+CzhKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9C4+JH;A6Ch9JD;EEjFE,0BAAA;C/CoiKD;A+CjiKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9Co/JH;AgD5iKD;EAEE,iBAAA;ChD6iKD;AgD3iKC;EACE,cAAA;ChD6iKH;AgDziKD;;EAEE,QAAA;EACA,iBAAA;ChD2iKD;AgDxiKD;EACE,eAAA;ChD0iKD;AgDviKD;EACE,eAAA;ChDyiKD;AgDtiKC;EACE,gBAAA;ChDwiKH;AgDpiKD;;EAEE,mBAAA;ChDsiKD;AgDniKD;;EAEE,oBAAA;ChDqiKD;AgDliKD;;;EAGE,oBAAA;EACA,oBAAA;ChDoiKD;AgDjiKD;EACE,uBAAA;ChDmiKD;AgDhiKD;EACE,uBAAA;ChDkiKD;AgD9hKD;EACE,cAAA;EACA,mBAAA;ChDgiKD;AgD1hKD;EACE,gBAAA;EACA,iBAAA;ChD4hKD;AiDnlKD;EAEE,oBAAA;EACA,gBAAA;CjDolKD;AiD5kKD;EACE,mBAAA;EACA,eAAA;EACA,mBAAA;EAEA,oBAAA;EACA,uBAAA;EACA,uBAAA;CjD6kKD;AiD1kKC;ErB3BA,6BAAA;EACC,4BAAA;C5BwmKF;AiD3kKC;EACE,iBAAA;ErBvBF,gCAAA;EACC,+BAAA;C5BqmKF;AiDpkKD;;EAEE,YAAA;CjDskKD;AiDxkKD;;EAKI,YAAA;CjDukKH;AiDnkKC;;;;EAEE,sBAAA;EACA,YAAA;EACA,0BAAA;CjDukKH;AiDnkKD;EACE,YAAA;EACA,iBAAA;CjDqkKD;AiDhkKC;;;EAGE,0BAAA;EACA,eAAA;EACA,oBAAA;CjDkkKH;AiDvkKC;;;EASI,eAAA;CjDmkKL;AiD5kKC;;;EAYI,eAAA;CjDqkKL;AiDhkKC;;;EAGE,WAAA;EACA,YAAA;EACA,0BAAA;EACA,sBAAA;CjDkkKH;AiDxkKC;;;;;;;;;EAYI,eAAA;CjDukKL;AiDnlKC;;;EAeI,eAAA;CjDykKL;AkD3qKC;EACE,eAAA;EACA,0BAAA;ClD6qKH;AkD3qKG;;EAEE,eAAA;ClD6qKL;AkD/qKG;;EAKI,eAAA;ClD8qKP;AkD3qKK;;;;EAEE,eAAA;EACA,0BAAA;ClD+qKP;AkD7qKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClDkrKP;AkDxsKC;EACE,eAAA;EACA,0BAAA;ClD0sKH;AkDxsKG;;EAEE,eAAA;ClD0sKL;AkD5sKG;;EAKI,eAAA;ClD2sKP;AkDxsKK;;;;EAEE,eAAA;EACA,0BAAA;ClD4sKP;AkD1sKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClD+sKP;AkDruKC;EACE,eAAA;EACA,0BAAA;ClDuuKH;AkDruKG;;EAEE,eAAA;ClDuuKL;AkDzuKG;;EAKI,eAAA;ClDwuKP;AkDruKK;;;;EAEE,eAAA;EACA,0BAAA;ClDyuKP;AkDvuKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClD4uKP;AkDlwKC;EACE,eAAA;EACA,0BAAA;ClDowKH;AkDlwKG;;EAEE,eAAA;ClDowKL;AkDtwKG;;EAKI,eAAA;ClDqwKP;AkDlwKK;;;;EAEE,eAAA;EACA,0BAAA;ClDswKP;AkDpwKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClDywKP;AiDxqKD;EACE,cAAA;EACA,mBAAA;CjD0qKD;AiDxqKD;EACE,iBAAA;EACA,iBAAA;CjD0qKD;AmDpyKD;EACE,oBAAA;EACA,uBAAA;EACA,8BAAA;EACA,mBAAA;E9C0DA,kDAAA;EACQ,0CAAA;CL6uKT;AmDnyKD;EACE,cAAA;CnDqyKD;AmDhyKD;EACE,mBAAA;EACA,qCAAA;EvBpBA,6BAAA;EACC,4BAAA;C5BuzKF;AmDtyKD;EAMI,eAAA;CnDmyKH;AmD9xKD;EACE,cAAA;EACA,iBAAA;EACA,gBAAA;EACA,eAAA;CnDgyKD;AmDpyKD;;;;;EAWI,eAAA;CnDgyKH;AmD3xKD;EACE,mBAAA;EACA,0BAAA;EACA,2BAAA;EvBxCA,gCAAA;EACC,+BAAA;C5Bs0KF;AmDrxKD;;EAGI,iBAAA;CnDsxKH;AmDzxKD;;EAMM,oBAAA;EACA,iBAAA;CnDuxKL;AmDnxKG;;EAEI,cAAA;EvBvEN,6BAAA;EACC,4BAAA;C5B61KF;AmDjxKG;;EAEI,iBAAA;EvBvEN,gCAAA;EACC,+BAAA;C5B21KF;AmD1yKD;EvB1DE,2BAAA;EACC,0BAAA;C5Bu2KF;AmD7wKD;EAEI,oBAAA;CnD8wKH;AmD3wKD;EACE,oBAAA;CnD6wKD;AmDrwKD;;;EAII,iBAAA;CnDswKH;AmD1wKD;;;EAOM,mBAAA;EACA,oBAAA;CnDwwKL;AmDhxKD;;EvBzGE,6BAAA;EACC,4BAAA;C5B63KF;AmDrxKD;;;;EAmBQ,4BAAA;EACA,6BAAA;CnDwwKP;AmD5xKD;;;;;;;;EAwBU,4BAAA;CnD8wKT;AmDtyKD;;;;;;;;EA4BU,6BAAA;CnDoxKT;AmDhzKD;;EvBjGE,gCAAA;EACC,+BAAA;C5Bq5KF;AmDrzKD;;;;EAyCQ,+BAAA;EACA,gCAAA;CnDkxKP;AmD5zKD;;;;;;;;EA8CU,+BAAA;CnDwxKT;AmDt0KD;;;;;;;;EAkDU,gCAAA;CnD8xKT;AmDh1KD;;;;EA2DI,2BAAA;CnD2xKH;AmDt1KD;;EA+DI,cAAA;CnD2xKH;AmD11KD;;EAmEI,UAAA;CnD2xKH;AmD91KD;;;;;;;;;;;;EA0EU,eAAA;CnDkyKT;AmD52KD;;;;;;;;;;;;EA8EU,gBAAA;CnD4yKT;AmD13KD;;;;;;;;EAuFU,iBAAA;CnD6yKT;AmDp4KD;;;;;;;;EAgGU,iBAAA;CnD8yKT;AmD94KD;EAsGI,UAAA;EACA,iBAAA;CnD2yKH;AmDjyKD;EACE,oBAAA;CnDmyKD;AmDpyKD;EAKI,iBAAA;EACA,mBAAA;CnDkyKH;AmDxyKD;EASM,gBAAA;CnDkyKL;AmD3yKD;EAcI,iBAAA;CnDgyKH;AmD9yKD;;EAkBM,2BAAA;CnDgyKL;AmDlzKD;EAuBI,cAAA;CnD8xKH;AmDrzKD;EAyBM,8BAAA;CnD+xKL;AmDxxKD;EC1PE,mBAAA;CpDqhLD;AoDnhLC;EACE,eAAA;EACA,0BAAA;EACA,mBAAA;CpDqhLH;AoDxhLC;EAMI,uBAAA;CpDqhLL;AoD3hLC;EASI,eAAA;EACA,0BAAA;CpDqhLL;AoDlhLC;EAEI,0BAAA;CpDmhLL;AmDvyKD;EC7PE,sBAAA;CpDuiLD;AoDriLC;EACE,YAAA;EACA,0BAAA;EACA,sBAAA;CpDuiLH;AoD1iLC;EAMI,0BAAA;CpDuiLL;AoD7iLC;EASI,eAAA;EACA,uBAAA;CpDuiLL;AoDpiLC;EAEI,6BAAA;CpDqiLL;AmDtzKD;EChQE,sBAAA;CpDyjLD;AoDvjLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpDyjLH;AoD5jLC;EAMI,0BAAA;CpDyjLL;AoD/jLC;EASI,eAAA;EACA,0BAAA;CpDyjLL;AoDtjLC;EAEI,6BAAA;CpDujLL;AmDr0KD;ECnQE,sBAAA;CpD2kLD;AoDzkLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpD2kLH;AoD9kLC;EAMI,0BAAA;CpD2kLL;AoDjlLC;EASI,eAAA;EACA,0BAAA;CpD2kLL;AoDxkLC;EAEI,6BAAA;CpDykLL;AmDp1KD;ECtQE,sBAAA;CpD6lLD;AoD3lLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpD6lLH;AoDhmLC;EAMI,0BAAA;CpD6lLL;AoDnmLC;EASI,eAAA;EACA,0BAAA;CpD6lLL;AoD1lLC;EAEI,6BAAA;CpD2lLL;AmDn2KD;ECzQE,sBAAA;CpD+mLD;AoD7mLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpD+mLH;AoDlnLC;EAMI,0BAAA;CpD+mLL;AoDrnLC;EASI,eAAA;EACA,0BAAA;CpD+mLL;AoD5mLC;EAEI,6BAAA;CpD6mLL;AqD7nLD;EACE,mBAAA;EACA,eAAA;EACA,UAAA;EACA,WAAA;EACA,iBAAA;CrD+nLD;AqDpoLD;;;;;EAYI,mBAAA;EACA,OAAA;EACA,QAAA;EACA,UAAA;EACA,aAAA;EACA,YAAA;EACA,UAAA;CrD+nLH;AqD1nLD;EACE,uBAAA;CrD4nLD;AqDxnLD;EACE,oBAAA;CrD0nLD;AsDrpLD;EACE,iBAAA;EACA,cAAA;EACA,oBAAA;EACA,0BAAA;EACA,0BAAA;EACA,mBAAA;EjDwDA,wDAAA;EACQ,gDAAA;CLgmLT;AsD/pLD;EASI,mBAAA;EACA,kCAAA;CtDypLH;AsDppLD;EACE,cAAA;EACA,mBAAA;CtDspLD;AsDppLD;EACE,aAAA;EACA,mBAAA;CtDspLD;AuD5qLD;EACE,aAAA;EACA,gBAAA;EACA,kBAAA;EACA,eAAA;EACA,YAAA;EACA,0BAAA;EjCRA,aAAA;EAGA,0BAAA;CtBqrLD;AuD7qLC;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;EjCfF,aAAA;EAGA,0BAAA;CtB6rLD;AuDzqLC;EACE,WAAA;EACA,gBAAA;EACA,wBAAA;EACA,UAAA;EACA,yBAAA;CvD2qLH;AwDhsLD;EACE,iBAAA;CxDksLD;AwD9rLD;EACE,cAAA;EACA,iBAAA;EACA,gBAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,kCAAA;EAIA,WAAA;CxD6rLD;AwD1rLC;EnD+GA,sCAAA;EACI,kCAAA;EACC,iCAAA;EACG,8BAAA;EAkER,oDAAA;EAEK,0CAAA;EACG,oCAAA;CL6gLT;AwDhsLC;EnD2GA,mCAAA;EACI,+BAAA;EACC,8BAAA;EACG,2BAAA;CLwlLT;AwDpsLD;EACE,mBAAA;EACA,iBAAA;CxDssLD;AwDlsLD;EACE,mBAAA;EACA,YAAA;EACA,aAAA;CxDosLD;AwDhsLD;EACE,mBAAA;EACA,uBAAA;EACA,uBAAA;EACA,qCAAA;EACA,mBAAA;EnDaA,iDAAA;EACQ,yCAAA;EmDZR,qCAAA;UAAA,6BAAA;EAEA,WAAA;CxDksLD;AwD9rLD;EACE,gBAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,uBAAA;CxDgsLD;AwD9rLC;ElCrEA,WAAA;EAGA,yBAAA;CtBowLD;AwDjsLC;ElCtEA,aAAA;EAGA,0BAAA;CtBwwLD;AwDhsLD;EACE,cAAA;EACA,iCAAA;CxDksLD;AwD9rLD;EACE,iBAAA;CxDgsLD;AwD5rLD;EACE,UAAA;EACA,wBAAA;CxD8rLD;AwDzrLD;EACE,mBAAA;EACA,cAAA;CxD2rLD;AwDvrLD;EACE,cAAA;EACA,kBAAA;EACA,8BAAA;CxDyrLD;AwD5rLD;EAQI,iBAAA;EACA,iBAAA;CxDurLH;AwDhsLD;EAaI,kBAAA;CxDsrLH;AwDnsLD;EAiBI,eAAA;CxDqrLH;AwDhrLD;EACE,mBAAA;EACA,aAAA;EACA,YAAA;EACA,aAAA;EACA,iBAAA;CxDkrLD;AwDhqLD;EAZE;IACE,aAAA;IACA,kBAAA;GxD+qLD;EwD7qLD;InDvEA,kDAAA;IACQ,0CAAA;GLuvLP;EwD5qLD;IAAY,aAAA;GxD+qLX;CACF;AwD1qLD;EAFE;IAAY,aAAA;GxDgrLX;CACF;AyD/zLD;EACE,mBAAA;EACA,cAAA;EACA,eAAA;ECRA,4DAAA;EAEA,mBAAA;EACA,oBAAA;EACA,uBAAA;EACA,iBAAA;EACA,wBAAA;EACA,iBAAA;EACA,kBAAA;EACA,sBAAA;EACA,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mBAAA;EACA,qBAAA;EACA,kBAAA;EDHA,gBAAA;EnCVA,WAAA;EAGA,yBAAA;CtBs1LD;AyD30LC;EnCdA,aAAA;EAGA,0BAAA;CtB01LD;AyD90LC;EAAW,iBAAA;EAAmB,eAAA;CzDk1L/B;AyDj1LC;EAAW,iBAAA;EAAmB,eAAA;CzDq1L/B;AyDp1LC;EAAW,gBAAA;EAAmB,eAAA;CzDw1L/B;AyDv1LC;EAAW,kBAAA;EAAmB,eAAA;CzD21L/B;AyDv1LD;EACE,iBAAA;EACA,iBAAA;EACA,YAAA;EACA,mBAAA;EACA,uBAAA;EACA,mBAAA;CzDy1LD;AyDr1LD;EACE,mBAAA;EACA,SAAA;EACA,UAAA;EACA,0BAAA;EACA,oBAAA;CzDu1LD;AyDn1LC;EACE,UAAA;EACA,UAAA;EACA,kBAAA;EACA,wBAAA;EACA,uBAAA;CzDq1LH;AyDn1LC;EACE,UAAA;EACA,WAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;CzDq1LH;AyDn1LC;EACE,UAAA;EACA,UAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;CzDq1LH;AyDn1LC;EACE,SAAA;EACA,QAAA;EACA,iBAAA;EACA,4BAAA;EACA,yBAAA;CzDq1LH;AyDn1LC;EACE,SAAA;EACA,SAAA;EACA,iBAAA;EACA,4BAAA;EACA,wBAAA;CzDq1LH;AyDn1LC;EACE,OAAA;EACA,UAAA;EACA,kBAAA;EACA,wBAAA;EACA,0BAAA;CzDq1LH;AyDn1LC;EACE,OAAA;EACA,WAAA;EACA,iBAAA;EACA,wBAAA;EACA,0BAAA;CzDq1LH;AyDn1LC;EACE,OAAA;EACA,UAAA;EACA,iBAAA;EACA,wBAAA;EACA,0BAAA;CzDq1LH;A2Dl7LD;EACE,mBAAA;EACA,OAAA;EACA,QAAA;EACA,cAAA;EACA,cAAA;EACA,iBAAA;EACA,aAAA;EDXA,4DAAA;EAEA,mBAAA;EACA,oBAAA;EACA,uBAAA;EACA,iBAAA;EACA,wBAAA;EACA,iBAAA;EACA,kBAAA;EACA,sBAAA;EACA,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mBAAA;EACA,qBAAA;EACA,kBAAA;ECAA,gBAAA;EAEA,uBAAA;EACA,qCAAA;UAAA,6BAAA;EACA,uBAAA;EACA,qCAAA;EACA,mBAAA;EtD8CA,kDAAA;EACQ,0CAAA;CLk5LT;A2D77LC;EAAY,kBAAA;C3Dg8Lb;A2D/7LC;EAAY,kBAAA;C3Dk8Lb;A2Dj8LC;EAAY,iBAAA;C3Do8Lb;A2Dn8LC;EAAY,mBAAA;C3Ds8Lb;A2Dn8LD;EACE,UAAA;EACA,kBAAA;EACA,gBAAA;EACA,0BAAA;EACA,iCAAA;EACA,2BAAA;C3Dq8LD;A2Dl8LD;EACE,kBAAA;C3Do8LD;A2D57LC;;EAEE,mBAAA;EACA,eAAA;EACA,SAAA;EACA,UAAA;EACA,0BAAA;EACA,oBAAA;C3D87LH;A2D37LD;EACE,mBAAA;C3D67LD;A2D37LD;EACE,mBAAA;EACA,YAAA;C3D67LD;A2Dz7LC;EACE,UAAA;EACA,mBAAA;EACA,uBAAA;EACA,0BAAA;EACA,sCAAA;EACA,cAAA;C3D27LH;A2D17LG;EACE,aAAA;EACA,YAAA;EACA,mBAAA;EACA,uBAAA;EACA,uBAAA;C3D47LL;A2Dz7LC;EACE,SAAA;EACA,YAAA;EACA,kBAAA;EACA,qBAAA;EACA,4BAAA;EACA,wCAAA;C3D27LH;A2D17LG;EACE,aAAA;EACA,UAAA;EACA,cAAA;EACA,qBAAA;EACA,yBAAA;C3D47LL;A2Dz7LC;EACE,UAAA;EACA,mBAAA;EACA,oBAAA;EACA,6BAAA;EACA,yCAAA;EACA,WAAA;C3D27LH;A2D17LG;EACE,aAAA;EACA,SAAA;EACA,mBAAA;EACA,oBAAA;EACA,0BAAA;C3D47LL;A2Dx7LC;EACE,SAAA;EACA,aAAA;EACA,kBAAA;EACA,sBAAA;EACA,2BAAA;EACA,uCAAA;C3D07LH;A2Dz7LG;EACE,aAAA;EACA,WAAA;EACA,sBAAA;EACA,wBAAA;EACA,cAAA;C3D27LL;A4DpjMD;EACE,mBAAA;C5DsjMD;A4DnjMD;EACE,mBAAA;EACA,iBAAA;EACA,YAAA;C5DqjMD;A4DxjMD;EAMI,cAAA;EACA,mBAAA;EvD6KF,0CAAA;EACK,qCAAA;EACG,kCAAA;CLy4LT;A4D/jMD;;EAcM,eAAA;C5DqjML;A4D3hMC;EA4NF;IvD3DE,uDAAA;IAEK,6CAAA;IACG,uCAAA;IA7JR,oCAAA;IAEQ,4BAAA;IA+GR,4BAAA;IAEQ,oBAAA;GL86LP;E4DzjMG;;IvDmHJ,2CAAA;IACQ,mCAAA;IuDjHF,QAAA;G5D4jML;E4D1jMG;;IvD8GJ,4CAAA;IACQ,oCAAA;IuD5GF,QAAA;G5D6jML;E4D3jMG;;;IvDyGJ,wCAAA;IACQ,gCAAA;IuDtGF,QAAA;G5D8jML;CACF;A4DpmMD;;;EA6CI,eAAA;C5D4jMH;A4DzmMD;EAiDI,QAAA;C5D2jMH;A4D5mMD;;EAsDI,mBAAA;EACA,OAAA;EACA,YAAA;C5D0jMH;A4DlnMD;EA4DI,WAAA;C5DyjMH;A4DrnMD;EA+DI,YAAA;C5DyjMH;A4DxnMD;;EAmEI,QAAA;C5DyjMH;A4D5nMD;EAuEI,YAAA;C5DwjMH;A4D/nMD;EA0EI,WAAA;C5DwjMH;A4DhjMD;EACE,mBAAA;EACA,OAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EtC9FA,aAAA;EAGA,0BAAA;EsC6FA,gBAAA;EACA,YAAA;EACA,mBAAA;EACA,0CAAA;EACA,mCAAA;C5DmjMD;A4D9iMC;EdnGE,mGAAA;EACA,8FAAA;EACA,qHAAA;EAAA,+FAAA;EACA,4BAAA;EACA,uHAAA;C9CopMH;A4DljMC;EACE,WAAA;EACA,SAAA;EdxGA,mGAAA;EACA,8FAAA;EACA,qHAAA;EAAA,+FAAA;EACA,4BAAA;EACA,uHAAA;C9C6pMH;A4DpjMC;;EAEE,WAAA;EACA,YAAA;EACA,sBAAA;EtCvHF,aAAA;EAGA,0BAAA;CtB4qMD;A4DtlMD;;;;EAuCI,mBAAA;EACA,SAAA;EACA,kBAAA;EACA,WAAA;EACA,sBAAA;C5DqjMH;A4DhmMD;;EA+CI,UAAA;EACA,mBAAA;C5DqjMH;A4DrmMD;;EAoDI,WAAA;EACA,oBAAA;C5DqjMH;A4D1mMD;;EAyDI,YAAA;EACA,aAAA;EACA,eAAA;EACA,mBAAA;C5DqjMH;A4DhjMG;EACE,iBAAA;C5DkjML;A4D9iMG;EACE,iBAAA;C5DgjML;A4DtiMD;EACE,mBAAA;EACA,aAAA;EACA,UAAA;EACA,YAAA;EACA,WAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;C5DwiMD;A4DjjMD;EAYI,sBAAA;EACA,YAAA;EACA,aAAA;EACA,YAAA;EACA,oBAAA;EACA,uBAAA;EACA,oBAAA;EACA,gBAAA;EAWA,0BAAA;EACA,mCAAA;C5D8hMH;A4D7jMD;EAkCI,UAAA;EACA,YAAA;EACA,aAAA;EACA,uBAAA;C5D8hMH;A4DvhMD;EACE,mBAAA;EACA,UAAA;EACA,WAAA;EACA,aAAA;EACA,YAAA;EACA,kBAAA;EACA,qBAAA;EACA,YAAA;EACA,mBAAA;EACA,0CAAA;C5DyhMD;A4DxhMC;EACE,kBAAA;C5D0hMH;A4Dj/LD;EAhCE;;;;IAKI,YAAA;IACA,aAAA;IACA,kBAAA;IACA,gBAAA;G5DmhMH;E4D3hMD;;IAYI,mBAAA;G5DmhMH;E4D/hMD;;IAgBI,oBAAA;G5DmhMH;E4D9gMD;IACE,UAAA;IACA,WAAA;IACA,qBAAA;G5DghMD;E4D5gMD;IACE,aAAA;G5D8gMD;CACF;A6D7wMC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEE,aAAA;EACA,eAAA;C7D6yMH;A6D3yMC;;;;;;;;;;;;;;;;EACE,YAAA;C7D4zMH;AiCp0MD;E6BRE,eAAA;EACA,kBAAA;EACA,mBAAA;C9D+0MD;AiCt0MD;EACE,wBAAA;CjCw0MD;AiCt0MD;EACE,uBAAA;CjCw0MD;AiCh0MD;EACE,yBAAA;CjCk0MD;AiCh0MD;EACE,0BAAA;CjCk0MD;AiCh0MD;EACE,mBAAA;CjCk0MD;AiCh0MD;E8BzBE,YAAA;EACA,mBAAA;EACA,kBAAA;EACA,8BAAA;EACA,UAAA;C/D41MD;AiC9zMD;EACE,yBAAA;CjCg0MD;AiCzzMD;EACE,gBAAA;CjC2zMD;AgE51MD;EACE,oBAAA;ChE81MD;AgEx1MD;;;;ECdE,yBAAA;CjE42MD;AgEv1MD;;;;;;;;;;;;EAYE,yBAAA;ChEy1MD;AgEl1MD;EA6IA;IC7LE,0BAAA;GjEs4MC;EiEr4MD;IAAU,0BAAA;GjEw4MT;EiEv4MD;IAAU,8BAAA;GjE04MT;EiEz4MD;;IACU,+BAAA;GjE44MT;CACF;AgE51MD;EAwIA;IA1II,0BAAA;GhEk2MD;CACF;AgE51MD;EAmIA;IArII,2BAAA;GhEk2MD;CACF;AgE51MD;EA8HA;IAhII,iCAAA;GhEk2MD;CACF;AgE31MD;EAwHA;IC7LE,0BAAA;GjEo6MC;EiEn6MD;IAAU,0BAAA;GjEs6MT;EiEr6MD;IAAU,8BAAA;GjEw6MT;EiEv6MD;;IACU,+BAAA;GjE06MT;CACF;AgEr2MD;EAmHA;IArHI,0BAAA;GhE22MD;CACF;AgEr2MD;EA8GA;IAhHI,2BAAA;GhE22MD;CACF;AgEr2MD;EAyGA;IA3GI,iCAAA;GhE22MD;CACF;AgEp2MD;EAmGA;IC7LE,0BAAA;GjEk8MC;EiEj8MD;IAAU,0BAAA;GjEo8MT;EiEn8MD;IAAU,8BAAA;GjEs8MT;EiEr8MD;;IACU,+BAAA;GjEw8MT;CACF;AgE92MD;EA8FA;IAhGI,0BAAA;GhEo3MD;CACF;AgE92MD;EAyFA;IA3FI,2BAAA;GhEo3MD;CACF;AgE92MD;EAoFA;IAtFI,iCAAA;GhEo3MD;CACF;AgE72MD;EA8EA;IC7LE,0BAAA;GjEg+MC;EiE/9MD;IAAU,0BAAA;GjEk+MT;EiEj+MD;IAAU,8BAAA;GjEo+MT;EiEn+MD;;IACU,+BAAA;GjEs+MT;CACF;AgEv3MD;EAyEA;IA3EI,0BAAA;GhE63MD;CACF;AgEv3MD;EAoEA;IAtEI,2BAAA;GhE63MD;CACF;AgEv3MD;EA+DA;IAjEI,iCAAA;GhE63MD;CACF;AgEt3MD;EAyDA;ICrLE,yBAAA;GjEs/MC;CACF;AgEt3MD;EAoDA;ICrLE,yBAAA;GjE2/MC;CACF;AgEt3MD;EA+CA;ICrLE,yBAAA;GjEggNC;CACF;AgEt3MD;EA0CA;ICrLE,yBAAA;GjEqgNC;CACF;AgEn3MD;ECnJE,yBAAA;CjEygND;AgEh3MD;EA4BA;IC7LE,0BAAA;GjEqhNC;EiEphND;IAAU,0BAAA;GjEuhNT;EiEthND;IAAU,8BAAA;GjEyhNT;EiExhND;;IACU,+BAAA;GjE2hNT;CACF;AgE93MD;EACE,yBAAA;ChEg4MD;AgE33MD;EAqBA;IAvBI,0BAAA;GhEi4MD;CACF;AgE/3MD;EACE,yBAAA;ChEi4MD;AgE53MD;EAcA;IAhBI,2BAAA;GhEk4MD;CACF;AgEh4MD;EACE,yBAAA;ChEk4MD;AgE73MD;EAOA;IATI,iCAAA;GhEm4MD;CACF;AgE53MD;EACA;ICrLE,yBAAA;GjEojNC;CACF", - "file": "bootstrap.css", - "sourcesContent": [ - "/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\nhtml {\n font-family: sans-serif;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n}\nbody {\n margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block;\n vertical-align: baseline;\n}\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n[hidden],\ntemplate {\n display: none;\n}\na {\n background-color: transparent;\n}\na:active,\na:hover {\n outline: 0;\n}\nabbr[title] {\n border-bottom: 1px dotted;\n}\nb,\nstrong {\n font-weight: bold;\n}\ndfn {\n font-style: italic;\n}\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\nmark {\n background: #ff0;\n color: #000;\n}\nsmall {\n font-size: 80%;\n}\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsup {\n top: -0.5em;\n}\nsub {\n bottom: -0.25em;\n}\nimg {\n border: 0;\n}\nsvg:not(:root) {\n overflow: hidden;\n}\nfigure {\n margin: 1em 40px;\n}\nhr {\n box-sizing: content-box;\n height: 0;\n}\npre {\n overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit;\n font: inherit;\n margin: 0;\n}\nbutton {\n overflow: visible;\n}\nbutton,\nselect {\n text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button;\n cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\ninput {\n line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box;\n padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: textfield;\n box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n border: 0;\n padding: 0;\n}\ntextarea {\n overflow: auto;\n}\noptgroup {\n font-weight: bold;\n}\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\ntd,\nth {\n padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important;\n box-shadow: none !important;\n text-shadow: none !important;\n }\n a,\n a:visited {\n text-decoration: underline;\n }\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n img {\n max-width: 100% !important;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n .navbar {\n display: none;\n }\n .btn > .caret,\n .dropup > .btn > .caret {\n border-top-color: #000 !important;\n }\n .label {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #ddd !important;\n }\n}\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('../fonts/glyphicons-halflings-regular.eot');\n src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\n}\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n content: \"\\002a\";\n}\n.glyphicon-plus:before {\n content: \"\\002b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n content: \"\\270f\";\n}\n.glyphicon-glass:before {\n content: \"\\e001\";\n}\n.glyphicon-music:before {\n content: \"\\e002\";\n}\n.glyphicon-search:before {\n content: \"\\e003\";\n}\n.glyphicon-heart:before {\n content: \"\\e005\";\n}\n.glyphicon-star:before {\n content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n content: \"\\e007\";\n}\n.glyphicon-user:before {\n content: \"\\e008\";\n}\n.glyphicon-film:before {\n content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n content: \"\\e010\";\n}\n.glyphicon-th:before {\n content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n content: \"\\e012\";\n}\n.glyphicon-ok:before {\n content: \"\\e013\";\n}\n.glyphicon-remove:before {\n content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n content: \"\\e016\";\n}\n.glyphicon-off:before {\n content: \"\\e017\";\n}\n.glyphicon-signal:before {\n content: \"\\e018\";\n}\n.glyphicon-cog:before {\n content: \"\\e019\";\n}\n.glyphicon-trash:before {\n content: \"\\e020\";\n}\n.glyphicon-home:before {\n content: \"\\e021\";\n}\n.glyphicon-file:before {\n content: \"\\e022\";\n}\n.glyphicon-time:before {\n content: \"\\e023\";\n}\n.glyphicon-road:before {\n content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n content: \"\\e025\";\n}\n.glyphicon-download:before {\n content: \"\\e026\";\n}\n.glyphicon-upload:before {\n content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n content: \"\\e032\";\n}\n.glyphicon-lock:before {\n content: \"\\e033\";\n}\n.glyphicon-flag:before {\n content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n content: \"\\e040\";\n}\n.glyphicon-tag:before {\n content: \"\\e041\";\n}\n.glyphicon-tags:before {\n content: \"\\e042\";\n}\n.glyphicon-book:before {\n content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n content: \"\\e044\";\n}\n.glyphicon-print:before {\n content: \"\\e045\";\n}\n.glyphicon-camera:before {\n content: \"\\e046\";\n}\n.glyphicon-font:before {\n content: \"\\e047\";\n}\n.glyphicon-bold:before {\n content: \"\\e048\";\n}\n.glyphicon-italic:before {\n content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n content: \"\\e055\";\n}\n.glyphicon-list:before {\n content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n content: \"\\e059\";\n}\n.glyphicon-picture:before {\n content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n content: \"\\e063\";\n}\n.glyphicon-tint:before {\n content: \"\\e064\";\n}\n.glyphicon-edit:before {\n content: \"\\e065\";\n}\n.glyphicon-share:before {\n content: \"\\e066\";\n}\n.glyphicon-check:before {\n content: \"\\e067\";\n}\n.glyphicon-move:before {\n content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n content: \"\\e070\";\n}\n.glyphicon-backward:before {\n content: \"\\e071\";\n}\n.glyphicon-play:before {\n content: \"\\e072\";\n}\n.glyphicon-pause:before {\n content: \"\\e073\";\n}\n.glyphicon-stop:before {\n content: \"\\e074\";\n}\n.glyphicon-forward:before {\n content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n content: \"\\e077\";\n}\n.glyphicon-eject:before {\n content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n content: \"\\e101\";\n}\n.glyphicon-gift:before {\n content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n content: \"\\e103\";\n}\n.glyphicon-fire:before {\n content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n content: \"\\e107\";\n}\n.glyphicon-plane:before {\n content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n content: \"\\e109\";\n}\n.glyphicon-random:before {\n content: \"\\e110\";\n}\n.glyphicon-comment:before {\n content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n content: \"\\e122\";\n}\n.glyphicon-bell:before {\n content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n content: \"\\e134\";\n}\n.glyphicon-globe:before {\n content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n content: \"\\e137\";\n}\n.glyphicon-filter:before {\n content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n content: \"\\e143\";\n}\n.glyphicon-link:before {\n content: \"\\e144\";\n}\n.glyphicon-phone:before {\n content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n content: \"\\e146\";\n}\n.glyphicon-usd:before {\n content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n content: \"\\e149\";\n}\n.glyphicon-sort:before {\n content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n content: \"\\e157\";\n}\n.glyphicon-expand:before {\n content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n content: \"\\e161\";\n}\n.glyphicon-flash:before {\n content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n content: \"\\e164\";\n}\n.glyphicon-record:before {\n content: \"\\e165\";\n}\n.glyphicon-save:before {\n content: \"\\e166\";\n}\n.glyphicon-open:before {\n content: \"\\e167\";\n}\n.glyphicon-saved:before {\n content: \"\\e168\";\n}\n.glyphicon-import:before {\n content: \"\\e169\";\n}\n.glyphicon-export:before {\n content: \"\\e170\";\n}\n.glyphicon-send:before {\n content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n content: \"\\e179\";\n}\n.glyphicon-header:before {\n content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n content: \"\\e183\";\n}\n.glyphicon-tower:before {\n content: \"\\e184\";\n}\n.glyphicon-stats:before {\n content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n content: \"\\e200\";\n}\n.glyphicon-cd:before {\n content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n content: \"\\e204\";\n}\n.glyphicon-copy:before {\n content: \"\\e205\";\n}\n.glyphicon-paste:before {\n content: \"\\e206\";\n}\n.glyphicon-alert:before {\n content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n content: \"\\e210\";\n}\n.glyphicon-king:before {\n content: \"\\e211\";\n}\n.glyphicon-queen:before {\n content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n content: \"\\e214\";\n}\n.glyphicon-knight:before {\n content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n content: \"\\e216\";\n}\n.glyphicon-tent:before {\n content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n content: \"\\e218\";\n}\n.glyphicon-bed:before {\n content: \"\\e219\";\n}\n.glyphicon-apple:before {\n content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n content: \"\\e227\";\n}\n.glyphicon-btc:before {\n content: \"\\e227\";\n}\n.glyphicon-xbt:before {\n content: \"\\e227\";\n}\n.glyphicon-yen:before {\n content: \"\\00a5\";\n}\n.glyphicon-jpy:before {\n content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n content: \"\\20bd\";\n}\n.glyphicon-rub:before {\n content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n content: \"\\e232\";\n}\n.glyphicon-education:before {\n content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n content: \"\\e237\";\n}\n.glyphicon-oil:before {\n content: \"\\e238\";\n}\n.glyphicon-grain:before {\n content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n content: \"\\e253\";\n}\n.glyphicon-console:before {\n content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n content: \"\\e260\";\n}\n* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n*:before,\n*:after {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.42857143;\n color: #333333;\n background-color: #fff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\na {\n color: #337ab7;\n text-decoration: none;\n}\na:hover,\na:focus {\n color: #23527c;\n text-decoration: underline;\n}\na:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\nfigure {\n margin: 0;\n}\nimg {\n vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.img-rounded {\n border-radius: 6px;\n}\n.img-thumbnail {\n padding: 4px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: all 0.2s ease-in-out;\n -o-transition: all 0.2s ease-in-out;\n transition: all 0.2s ease-in-out;\n display: inline-block;\n max-width: 100%;\n height: auto;\n}\n.img-circle {\n border-radius: 50%;\n}\nhr {\n margin-top: 20px;\n margin-bottom: 20px;\n border: 0;\n border-top: 1px solid #eeeeee;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n}\n[role=\"button\"] {\n cursor: pointer;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: inherit;\n font-weight: 500;\n line-height: 1.1;\n color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n font-weight: normal;\n line-height: 1;\n color: #777777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n margin-top: 20px;\n margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n margin-top: 10px;\n margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n font-size: 75%;\n}\nh1,\n.h1 {\n font-size: 36px;\n}\nh2,\n.h2 {\n font-size: 30px;\n}\nh3,\n.h3 {\n font-size: 24px;\n}\nh4,\n.h4 {\n font-size: 18px;\n}\nh5,\n.h5 {\n font-size: 14px;\n}\nh6,\n.h6 {\n font-size: 12px;\n}\np {\n margin: 0 0 10px;\n}\n.lead {\n margin-bottom: 20px;\n font-size: 16px;\n font-weight: 300;\n line-height: 1.4;\n}\n@media (min-width: 768px) {\n .lead {\n font-size: 21px;\n }\n}\nsmall,\n.small {\n font-size: 85%;\n}\nmark,\n.mark {\n background-color: #fcf8e3;\n padding: .2em;\n}\n.text-left {\n text-align: left;\n}\n.text-right {\n text-align: right;\n}\n.text-center {\n text-align: center;\n}\n.text-justify {\n text-align: justify;\n}\n.text-nowrap {\n white-space: nowrap;\n}\n.text-lowercase {\n text-transform: lowercase;\n}\n.text-uppercase {\n text-transform: uppercase;\n}\n.text-capitalize {\n text-transform: capitalize;\n}\n.text-muted {\n color: #777777;\n}\n.text-primary {\n color: #337ab7;\n}\na.text-primary:hover,\na.text-primary:focus {\n color: #286090;\n}\n.text-success {\n color: #3c763d;\n}\na.text-success:hover,\na.text-success:focus {\n color: #2b542c;\n}\n.text-info {\n color: #31708f;\n}\na.text-info:hover,\na.text-info:focus {\n color: #245269;\n}\n.text-warning {\n color: #8a6d3b;\n}\na.text-warning:hover,\na.text-warning:focus {\n color: #66512c;\n}\n.text-danger {\n color: #a94442;\n}\na.text-danger:hover,\na.text-danger:focus {\n color: #843534;\n}\n.bg-primary {\n color: #fff;\n background-color: #337ab7;\n}\na.bg-primary:hover,\na.bg-primary:focus {\n background-color: #286090;\n}\n.bg-success {\n background-color: #dff0d8;\n}\na.bg-success:hover,\na.bg-success:focus {\n background-color: #c1e2b3;\n}\n.bg-info {\n background-color: #d9edf7;\n}\na.bg-info:hover,\na.bg-info:focus {\n background-color: #afd9ee;\n}\n.bg-warning {\n background-color: #fcf8e3;\n}\na.bg-warning:hover,\na.bg-warning:focus {\n background-color: #f7ecb5;\n}\n.bg-danger {\n background-color: #f2dede;\n}\na.bg-danger:hover,\na.bg-danger:focus {\n background-color: #e4b9b9;\n}\n.page-header {\n padding-bottom: 9px;\n margin: 40px 0 20px;\n border-bottom: 1px solid #eeeeee;\n}\nul,\nol {\n margin-top: 0;\n margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n margin-bottom: 0;\n}\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n.list-inline {\n padding-left: 0;\n list-style: none;\n margin-left: -5px;\n}\n.list-inline > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n}\ndl {\n margin-top: 0;\n margin-bottom: 20px;\n}\ndt,\ndd {\n line-height: 1.42857143;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0;\n}\n@media (min-width: 768px) {\n .dl-horizontal dt {\n float: left;\n width: 160px;\n clear: left;\n text-align: right;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dl-horizontal dd {\n margin-left: 180px;\n }\n}\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted #777777;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\nblockquote {\n padding: 10px 20px;\n margin: 0 0 20px;\n font-size: 17.5px;\n border-left: 5px solid #eeeeee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n display: block;\n font-size: 80%;\n line-height: 1.42857143;\n color: #777777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid #eeeeee;\n border-left: 0;\n text-align: right;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n content: '\\00A0 \\2014';\n}\naddress {\n margin-bottom: 20px;\n font-style: normal;\n line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: #c7254e;\n background-color: #f9f2f4;\n border-radius: 4px;\n}\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: #fff;\n background-color: #333;\n border-radius: 3px;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n}\npre {\n display: block;\n padding: 9.5px;\n margin: 0 0 10px;\n font-size: 13px;\n line-height: 1.42857143;\n word-break: break-all;\n word-wrap: break-word;\n color: #333333;\n background-color: #f5f5f5;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\npre code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n}\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n.container {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n@media (min-width: 768px) {\n .container {\n width: 750px;\n }\n}\n@media (min-width: 992px) {\n .container {\n width: 970px;\n }\n}\n@media (min-width: 1200px) {\n .container {\n width: 1170px;\n }\n}\n.container-fluid {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n.row {\n margin-left: -15px;\n margin-right: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n position: relative;\n min-height: 1px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n float: left;\n}\n.col-xs-12 {\n width: 100%;\n}\n.col-xs-11 {\n width: 91.66666667%;\n}\n.col-xs-10 {\n width: 83.33333333%;\n}\n.col-xs-9 {\n width: 75%;\n}\n.col-xs-8 {\n width: 66.66666667%;\n}\n.col-xs-7 {\n width: 58.33333333%;\n}\n.col-xs-6 {\n width: 50%;\n}\n.col-xs-5 {\n width: 41.66666667%;\n}\n.col-xs-4 {\n width: 33.33333333%;\n}\n.col-xs-3 {\n width: 25%;\n}\n.col-xs-2 {\n width: 16.66666667%;\n}\n.col-xs-1 {\n width: 8.33333333%;\n}\n.col-xs-pull-12 {\n right: 100%;\n}\n.col-xs-pull-11 {\n right: 91.66666667%;\n}\n.col-xs-pull-10 {\n right: 83.33333333%;\n}\n.col-xs-pull-9 {\n right: 75%;\n}\n.col-xs-pull-8 {\n right: 66.66666667%;\n}\n.col-xs-pull-7 {\n right: 58.33333333%;\n}\n.col-xs-pull-6 {\n right: 50%;\n}\n.col-xs-pull-5 {\n right: 41.66666667%;\n}\n.col-xs-pull-4 {\n right: 33.33333333%;\n}\n.col-xs-pull-3 {\n right: 25%;\n}\n.col-xs-pull-2 {\n right: 16.66666667%;\n}\n.col-xs-pull-1 {\n right: 8.33333333%;\n}\n.col-xs-pull-0 {\n right: auto;\n}\n.col-xs-push-12 {\n left: 100%;\n}\n.col-xs-push-11 {\n left: 91.66666667%;\n}\n.col-xs-push-10 {\n left: 83.33333333%;\n}\n.col-xs-push-9 {\n left: 75%;\n}\n.col-xs-push-8 {\n left: 66.66666667%;\n}\n.col-xs-push-7 {\n left: 58.33333333%;\n}\n.col-xs-push-6 {\n left: 50%;\n}\n.col-xs-push-5 {\n left: 41.66666667%;\n}\n.col-xs-push-4 {\n left: 33.33333333%;\n}\n.col-xs-push-3 {\n left: 25%;\n}\n.col-xs-push-2 {\n left: 16.66666667%;\n}\n.col-xs-push-1 {\n left: 8.33333333%;\n}\n.col-xs-push-0 {\n left: auto;\n}\n.col-xs-offset-12 {\n margin-left: 100%;\n}\n.col-xs-offset-11 {\n margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n margin-left: 75%;\n}\n.col-xs-offset-8 {\n margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n margin-left: 50%;\n}\n.col-xs-offset-5 {\n margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n margin-left: 25%;\n}\n.col-xs-offset-2 {\n margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n margin-left: 0%;\n}\n@media (min-width: 768px) {\n .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\n float: left;\n }\n .col-sm-12 {\n width: 100%;\n }\n .col-sm-11 {\n width: 91.66666667%;\n }\n .col-sm-10 {\n width: 83.33333333%;\n }\n .col-sm-9 {\n width: 75%;\n }\n .col-sm-8 {\n width: 66.66666667%;\n }\n .col-sm-7 {\n width: 58.33333333%;\n }\n .col-sm-6 {\n width: 50%;\n }\n .col-sm-5 {\n width: 41.66666667%;\n }\n .col-sm-4 {\n width: 33.33333333%;\n }\n .col-sm-3 {\n width: 25%;\n }\n .col-sm-2 {\n width: 16.66666667%;\n }\n .col-sm-1 {\n width: 8.33333333%;\n }\n .col-sm-pull-12 {\n right: 100%;\n }\n .col-sm-pull-11 {\n right: 91.66666667%;\n }\n .col-sm-pull-10 {\n right: 83.33333333%;\n }\n .col-sm-pull-9 {\n right: 75%;\n }\n .col-sm-pull-8 {\n right: 66.66666667%;\n }\n .col-sm-pull-7 {\n right: 58.33333333%;\n }\n .col-sm-pull-6 {\n right: 50%;\n }\n .col-sm-pull-5 {\n right: 41.66666667%;\n }\n .col-sm-pull-4 {\n right: 33.33333333%;\n }\n .col-sm-pull-3 {\n right: 25%;\n }\n .col-sm-pull-2 {\n right: 16.66666667%;\n }\n .col-sm-pull-1 {\n right: 8.33333333%;\n }\n .col-sm-pull-0 {\n right: auto;\n }\n .col-sm-push-12 {\n left: 100%;\n }\n .col-sm-push-11 {\n left: 91.66666667%;\n }\n .col-sm-push-10 {\n left: 83.33333333%;\n }\n .col-sm-push-9 {\n left: 75%;\n }\n .col-sm-push-8 {\n left: 66.66666667%;\n }\n .col-sm-push-7 {\n left: 58.33333333%;\n }\n .col-sm-push-6 {\n left: 50%;\n }\n .col-sm-push-5 {\n left: 41.66666667%;\n }\n .col-sm-push-4 {\n left: 33.33333333%;\n }\n .col-sm-push-3 {\n left: 25%;\n }\n .col-sm-push-2 {\n left: 16.66666667%;\n }\n .col-sm-push-1 {\n left: 8.33333333%;\n }\n .col-sm-push-0 {\n left: auto;\n }\n .col-sm-offset-12 {\n margin-left: 100%;\n }\n .col-sm-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-sm-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-sm-offset-9 {\n margin-left: 75%;\n }\n .col-sm-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-sm-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-sm-offset-6 {\n margin-left: 50%;\n }\n .col-sm-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-sm-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-sm-offset-3 {\n margin-left: 25%;\n }\n .col-sm-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-sm-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-sm-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 992px) {\n .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\n float: left;\n }\n .col-md-12 {\n width: 100%;\n }\n .col-md-11 {\n width: 91.66666667%;\n }\n .col-md-10 {\n width: 83.33333333%;\n }\n .col-md-9 {\n width: 75%;\n }\n .col-md-8 {\n width: 66.66666667%;\n }\n .col-md-7 {\n width: 58.33333333%;\n }\n .col-md-6 {\n width: 50%;\n }\n .col-md-5 {\n width: 41.66666667%;\n }\n .col-md-4 {\n width: 33.33333333%;\n }\n .col-md-3 {\n width: 25%;\n }\n .col-md-2 {\n width: 16.66666667%;\n }\n .col-md-1 {\n width: 8.33333333%;\n }\n .col-md-pull-12 {\n right: 100%;\n }\n .col-md-pull-11 {\n right: 91.66666667%;\n }\n .col-md-pull-10 {\n right: 83.33333333%;\n }\n .col-md-pull-9 {\n right: 75%;\n }\n .col-md-pull-8 {\n right: 66.66666667%;\n }\n .col-md-pull-7 {\n right: 58.33333333%;\n }\n .col-md-pull-6 {\n right: 50%;\n }\n .col-md-pull-5 {\n right: 41.66666667%;\n }\n .col-md-pull-4 {\n right: 33.33333333%;\n }\n .col-md-pull-3 {\n right: 25%;\n }\n .col-md-pull-2 {\n right: 16.66666667%;\n }\n .col-md-pull-1 {\n right: 8.33333333%;\n }\n .col-md-pull-0 {\n right: auto;\n }\n .col-md-push-12 {\n left: 100%;\n }\n .col-md-push-11 {\n left: 91.66666667%;\n }\n .col-md-push-10 {\n left: 83.33333333%;\n }\n .col-md-push-9 {\n left: 75%;\n }\n .col-md-push-8 {\n left: 66.66666667%;\n }\n .col-md-push-7 {\n left: 58.33333333%;\n }\n .col-md-push-6 {\n left: 50%;\n }\n .col-md-push-5 {\n left: 41.66666667%;\n }\n .col-md-push-4 {\n left: 33.33333333%;\n }\n .col-md-push-3 {\n left: 25%;\n }\n .col-md-push-2 {\n left: 16.66666667%;\n }\n .col-md-push-1 {\n left: 8.33333333%;\n }\n .col-md-push-0 {\n left: auto;\n }\n .col-md-offset-12 {\n margin-left: 100%;\n }\n .col-md-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-md-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-md-offset-9 {\n margin-left: 75%;\n }\n .col-md-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-md-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-md-offset-6 {\n margin-left: 50%;\n }\n .col-md-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-md-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-md-offset-3 {\n margin-left: 25%;\n }\n .col-md-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-md-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-md-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 1200px) {\n .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\n float: left;\n }\n .col-lg-12 {\n width: 100%;\n }\n .col-lg-11 {\n width: 91.66666667%;\n }\n .col-lg-10 {\n width: 83.33333333%;\n }\n .col-lg-9 {\n width: 75%;\n }\n .col-lg-8 {\n width: 66.66666667%;\n }\n .col-lg-7 {\n width: 58.33333333%;\n }\n .col-lg-6 {\n width: 50%;\n }\n .col-lg-5 {\n width: 41.66666667%;\n }\n .col-lg-4 {\n width: 33.33333333%;\n }\n .col-lg-3 {\n width: 25%;\n }\n .col-lg-2 {\n width: 16.66666667%;\n }\n .col-lg-1 {\n width: 8.33333333%;\n }\n .col-lg-pull-12 {\n right: 100%;\n }\n .col-lg-pull-11 {\n right: 91.66666667%;\n }\n .col-lg-pull-10 {\n right: 83.33333333%;\n }\n .col-lg-pull-9 {\n right: 75%;\n }\n .col-lg-pull-8 {\n right: 66.66666667%;\n }\n .col-lg-pull-7 {\n right: 58.33333333%;\n }\n .col-lg-pull-6 {\n right: 50%;\n }\n .col-lg-pull-5 {\n right: 41.66666667%;\n }\n .col-lg-pull-4 {\n right: 33.33333333%;\n }\n .col-lg-pull-3 {\n right: 25%;\n }\n .col-lg-pull-2 {\n right: 16.66666667%;\n }\n .col-lg-pull-1 {\n right: 8.33333333%;\n }\n .col-lg-pull-0 {\n right: auto;\n }\n .col-lg-push-12 {\n left: 100%;\n }\n .col-lg-push-11 {\n left: 91.66666667%;\n }\n .col-lg-push-10 {\n left: 83.33333333%;\n }\n .col-lg-push-9 {\n left: 75%;\n }\n .col-lg-push-8 {\n left: 66.66666667%;\n }\n .col-lg-push-7 {\n left: 58.33333333%;\n }\n .col-lg-push-6 {\n left: 50%;\n }\n .col-lg-push-5 {\n left: 41.66666667%;\n }\n .col-lg-push-4 {\n left: 33.33333333%;\n }\n .col-lg-push-3 {\n left: 25%;\n }\n .col-lg-push-2 {\n left: 16.66666667%;\n }\n .col-lg-push-1 {\n left: 8.33333333%;\n }\n .col-lg-push-0 {\n left: auto;\n }\n .col-lg-offset-12 {\n margin-left: 100%;\n }\n .col-lg-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-lg-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-lg-offset-9 {\n margin-left: 75%;\n }\n .col-lg-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-lg-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-lg-offset-6 {\n margin-left: 50%;\n }\n .col-lg-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-lg-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-lg-offset-3 {\n margin-left: 25%;\n }\n .col-lg-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-lg-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-lg-offset-0 {\n margin-left: 0%;\n }\n}\ntable {\n background-color: transparent;\n}\ncaption {\n padding-top: 8px;\n padding-bottom: 8px;\n color: #777777;\n text-align: left;\n}\nth {\n text-align: left;\n}\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n padding: 8px;\n line-height: 1.42857143;\n vertical-align: top;\n border-top: 1px solid #ddd;\n}\n.table > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid #ddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n border-top: 0;\n}\n.table > tbody + tbody {\n border-top: 2px solid #ddd;\n}\n.table .table {\n background-color: #fff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n padding: 5px;\n}\n.table-bordered {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-column;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-cell;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n background-color: #ebcccc;\n}\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%;\n}\n@media screen and (max-width: 767px) {\n .table-responsive {\n width: 100%;\n margin-bottom: 15px;\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid #ddd;\n }\n .table-responsive > .table {\n margin-bottom: 0;\n }\n .table-responsive > .table > thead > tr > th,\n .table-responsive > .table > tbody > tr > th,\n .table-responsive > .table > tfoot > tr > th,\n .table-responsive > .table > thead > tr > td,\n .table-responsive > .table > tbody > tr > td,\n .table-responsive > .table > tfoot > tr > td {\n white-space: nowrap;\n }\n .table-responsive > .table-bordered {\n border: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:first-child,\n .table-responsive > .table-bordered > tbody > tr > th:first-child,\n .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n .table-responsive > .table-bordered > thead > tr > td:first-child,\n .table-responsive > .table-bordered > tbody > tr > td:first-child,\n .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:last-child,\n .table-responsive > .table-bordered > tbody > tr > th:last-child,\n .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n .table-responsive > .table-bordered > thead > tr > td:last-child,\n .table-responsive > .table-bordered > tbody > tr > td:last-child,\n .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n }\n .table-responsive > .table-bordered > tbody > tr:last-child > th,\n .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n .table-responsive > .table-bordered > tbody > tr:last-child > td,\n .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n border-bottom: 0;\n }\n}\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n min-width: 0;\n}\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: 20px;\n font-size: 21px;\n line-height: inherit;\n color: #333333;\n border: 0;\n border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n display: inline-block;\n max-width: 100%;\n margin-bottom: 5px;\n font-weight: bold;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9;\n line-height: normal;\n}\ninput[type=\"file\"] {\n display: block;\n}\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\nselect[multiple],\nselect[size] {\n height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\noutput {\n display: block;\n padding-top: 7px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n}\n.form-control {\n display: block;\n width: 100%;\n height: 34px;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n background-color: #fff;\n background-image: none;\n border: 1px solid #ccc;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n border-color: #66afe9;\n outline: 0;\n -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n}\n.form-control::-moz-placeholder {\n color: #999;\n opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n color: #999;\n}\n.form-control::-webkit-input-placeholder {\n color: #999;\n}\n.form-control::-ms-expand {\n border: 0;\n background-color: transparent;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: #eeeeee;\n opacity: 1;\n}\n.form-control[disabled],\nfieldset[disabled] .form-control {\n cursor: not-allowed;\n}\ntextarea.form-control {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"].form-control,\n input[type=\"time\"].form-control,\n input[type=\"datetime-local\"].form-control,\n input[type=\"month\"].form-control {\n line-height: 34px;\n }\n input[type=\"date\"].input-sm,\n input[type=\"time\"].input-sm,\n input[type=\"datetime-local\"].input-sm,\n input[type=\"month\"].input-sm,\n .input-group-sm input[type=\"date\"],\n .input-group-sm input[type=\"time\"],\n .input-group-sm input[type=\"datetime-local\"],\n .input-group-sm input[type=\"month\"] {\n line-height: 30px;\n }\n input[type=\"date\"].input-lg,\n input[type=\"time\"].input-lg,\n input[type=\"datetime-local\"].input-lg,\n input[type=\"month\"].input-lg,\n .input-group-lg input[type=\"date\"],\n .input-group-lg input[type=\"time\"],\n .input-group-lg input[type=\"datetime-local\"],\n .input-group-lg input[type=\"month\"] {\n line-height: 46px;\n }\n}\n.form-group {\n margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n min-height: 20px;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n position: relative;\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n cursor: not-allowed;\n}\n.form-control-static {\n padding-top: 7px;\n padding-bottom: 7px;\n margin-bottom: 0;\n min-height: 34px;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n padding-left: 0;\n padding-right: 0;\n}\n.input-sm {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-sm {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n height: auto;\n}\n.form-group-sm .form-control {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.form-group-sm select.form-control {\n height: 30px;\n line-height: 30px;\n}\n.form-group-sm textarea.form-control,\n.form-group-sm select[multiple].form-control {\n height: auto;\n}\n.form-group-sm .form-control-static {\n height: 30px;\n min-height: 32px;\n padding: 6px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.input-lg {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-lg {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n height: auto;\n}\n.form-group-lg .form-control {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.form-group-lg select.form-control {\n height: 46px;\n line-height: 46px;\n}\n.form-group-lg textarea.form-control,\n.form-group-lg select[multiple].form-control {\n height: auto;\n}\n.form-group-lg .form-control-static {\n height: 46px;\n min-height: 38px;\n padding: 11px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.has-feedback {\n position: relative;\n}\n.has-feedback .form-control {\n padding-right: 42.5px;\n}\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback,\n.input-group-lg + .form-control-feedback,\n.form-group-lg .form-control + .form-control-feedback {\n width: 46px;\n height: 46px;\n line-height: 46px;\n}\n.input-sm + .form-control-feedback,\n.input-group-sm + .form-control-feedback,\n.form-group-sm .form-control + .form-control-feedback {\n width: 30px;\n height: 30px;\n line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n color: #3c763d;\n}\n.has-success .form-control {\n border-color: #3c763d;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-success .form-control:focus {\n border-color: #2b542c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n color: #3c763d;\n border-color: #3c763d;\n background-color: #dff0d8;\n}\n.has-success .form-control-feedback {\n color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n color: #8a6d3b;\n}\n.has-warning .form-control {\n border-color: #8a6d3b;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-warning .form-control:focus {\n border-color: #66512c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n color: #8a6d3b;\n border-color: #8a6d3b;\n background-color: #fcf8e3;\n}\n.has-warning .form-control-feedback {\n color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n color: #a94442;\n}\n.has-error .form-control {\n border-color: #a94442;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-error .form-control:focus {\n border-color: #843534;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n color: #a94442;\n border-color: #a94442;\n background-color: #f2dede;\n}\n.has-error .form-control-feedback {\n color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n top: 0;\n}\n.help-block {\n display: block;\n margin-top: 5px;\n margin-bottom: 10px;\n color: #737373;\n}\n@media (min-width: 768px) {\n .form-inline .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-static {\n display: inline-block;\n }\n .form-inline .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .form-inline .input-group .input-group-addon,\n .form-inline .input-group .input-group-btn,\n .form-inline .input-group .form-control {\n width: auto;\n }\n .form-inline .input-group > .form-control {\n width: 100%;\n }\n .form-inline .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio,\n .form-inline .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio label,\n .form-inline .checkbox label {\n padding-left: 0;\n }\n .form-inline .radio input[type=\"radio\"],\n .form-inline .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .form-inline .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: 7px;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n min-height: 27px;\n}\n.form-horizontal .form-group {\n margin-left: -15px;\n margin-right: -15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: 7px;\n }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n right: 15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-lg .control-label {\n padding-top: 11px;\n font-size: 18px;\n }\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-sm .control-label {\n padding-top: 6px;\n font-size: 12px;\n }\n}\n.btn {\n display: inline-block;\n margin-bottom: 0;\n font-weight: normal;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none;\n border: 1px solid transparent;\n white-space: nowrap;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n border-radius: 4px;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n color: #333;\n text-decoration: none;\n}\n.btn:active,\n.btn.active {\n outline: 0;\n background-image: none;\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n cursor: not-allowed;\n opacity: 0.65;\n filter: alpha(opacity=65);\n -webkit-box-shadow: none;\n box-shadow: none;\n}\na.btn.disabled,\nfieldset[disabled] a.btn {\n pointer-events: none;\n}\n.btn-default {\n color: #333;\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default:focus,\n.btn-default.focus {\n color: #333;\n background-color: #e6e6e6;\n border-color: #8c8c8c;\n}\n.btn-default:hover {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active:hover,\n.btn-default.active:hover,\n.open > .dropdown-toggle.btn-default:hover,\n.btn-default:active:focus,\n.btn-default.active:focus,\n.open > .dropdown-toggle.btn-default:focus,\n.btn-default:active.focus,\n.btn-default.active.focus,\n.open > .dropdown-toggle.btn-default.focus {\n color: #333;\n background-color: #d4d4d4;\n border-color: #8c8c8c;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n background-image: none;\n}\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus {\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default .badge {\n color: #fff;\n background-color: #333;\n}\n.btn-primary {\n color: #fff;\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary:focus,\n.btn-primary.focus {\n color: #fff;\n background-color: #286090;\n border-color: #122b40;\n}\n.btn-primary:hover {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active:hover,\n.btn-primary.active:hover,\n.open > .dropdown-toggle.btn-primary:hover,\n.btn-primary:active:focus,\n.btn-primary.active:focus,\n.open > .dropdown-toggle.btn-primary:focus,\n.btn-primary:active.focus,\n.btn-primary.active.focus,\n.open > .dropdown-toggle.btn-primary.focus {\n color: #fff;\n background-color: #204d74;\n border-color: #122b40;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n background-image: none;\n}\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus {\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.btn-success {\n color: #fff;\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success:focus,\n.btn-success.focus {\n color: #fff;\n background-color: #449d44;\n border-color: #255625;\n}\n.btn-success:hover {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active:hover,\n.btn-success.active:hover,\n.open > .dropdown-toggle.btn-success:hover,\n.btn-success:active:focus,\n.btn-success.active:focus,\n.open > .dropdown-toggle.btn-success:focus,\n.btn-success:active.focus,\n.btn-success.active.focus,\n.open > .dropdown-toggle.btn-success.focus {\n color: #fff;\n background-color: #398439;\n border-color: #255625;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n background-image: none;\n}\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus {\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success .badge {\n color: #5cb85c;\n background-color: #fff;\n}\n.btn-info {\n color: #fff;\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info:focus,\n.btn-info.focus {\n color: #fff;\n background-color: #31b0d5;\n border-color: #1b6d85;\n}\n.btn-info:hover {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active:hover,\n.btn-info.active:hover,\n.open > .dropdown-toggle.btn-info:hover,\n.btn-info:active:focus,\n.btn-info.active:focus,\n.open > .dropdown-toggle.btn-info:focus,\n.btn-info:active.focus,\n.btn-info.active.focus,\n.open > .dropdown-toggle.btn-info.focus {\n color: #fff;\n background-color: #269abc;\n border-color: #1b6d85;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n background-image: none;\n}\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus {\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info .badge {\n color: #5bc0de;\n background-color: #fff;\n}\n.btn-warning {\n color: #fff;\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning:focus,\n.btn-warning.focus {\n color: #fff;\n background-color: #ec971f;\n border-color: #985f0d;\n}\n.btn-warning:hover {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active:hover,\n.btn-warning.active:hover,\n.open > .dropdown-toggle.btn-warning:hover,\n.btn-warning:active:focus,\n.btn-warning.active:focus,\n.open > .dropdown-toggle.btn-warning:focus,\n.btn-warning:active.focus,\n.btn-warning.active.focus,\n.open > .dropdown-toggle.btn-warning.focus {\n color: #fff;\n background-color: #d58512;\n border-color: #985f0d;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n background-image: none;\n}\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus {\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning .badge {\n color: #f0ad4e;\n background-color: #fff;\n}\n.btn-danger {\n color: #fff;\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger:focus,\n.btn-danger.focus {\n color: #fff;\n background-color: #c9302c;\n border-color: #761c19;\n}\n.btn-danger:hover {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active:hover,\n.btn-danger.active:hover,\n.open > .dropdown-toggle.btn-danger:hover,\n.btn-danger:active:focus,\n.btn-danger.active:focus,\n.open > .dropdown-toggle.btn-danger:focus,\n.btn-danger:active.focus,\n.btn-danger.active.focus,\n.open > .dropdown-toggle.btn-danger.focus {\n color: #fff;\n background-color: #ac2925;\n border-color: #761c19;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n background-image: none;\n}\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus {\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger .badge {\n color: #d9534f;\n background-color: #fff;\n}\n.btn-link {\n color: #337ab7;\n font-weight: normal;\n border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n background-color: transparent;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n color: #23527c;\n text-decoration: underline;\n background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n color: #777777;\n text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n padding: 1px 5px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-block {\n display: block;\n width: 100%;\n}\n.btn-block + .btn-block {\n margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n.fade {\n opacity: 0;\n -webkit-transition: opacity 0.15s linear;\n -o-transition: opacity 0.15s linear;\n transition: opacity 0.15s linear;\n}\n.fade.in {\n opacity: 1;\n}\n.collapse {\n display: none;\n}\n.collapse.in {\n display: block;\n}\ntr.collapse.in {\n display: table-row;\n}\ntbody.collapse.in {\n display: table-row-group;\n}\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n -webkit-transition-property: height, visibility;\n transition-property: height, visibility;\n -webkit-transition-duration: 0.35s;\n transition-duration: 0.35s;\n -webkit-transition-timing-function: ease;\n transition-timing-function: ease;\n}\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: 4px dashed;\n border-top: 4px solid \\9;\n border-right: 4px solid transparent;\n border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n position: relative;\n}\n.dropdown-toggle:focus {\n outline: 0;\n}\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0;\n list-style: none;\n font-size: 14px;\n text-align: left;\n background-color: #fff;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 4px;\n -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n background-clip: padding-box;\n}\n.dropdown-menu.pull-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu .divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: 1.42857143;\n color: #333333;\n white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n text-decoration: none;\n color: #262626;\n background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n background-color: #337ab7;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n color: #777777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n cursor: not-allowed;\n}\n.open > .dropdown-menu {\n display: block;\n}\n.open > a {\n outline: 0;\n}\n.dropdown-menu-right {\n left: auto;\n right: 0;\n}\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: 12px;\n line-height: 1.42857143;\n color: #777777;\n white-space: nowrap;\n}\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: 990;\n}\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n border-top: 0;\n border-bottom: 4px dashed;\n border-bottom: 4px solid \\9;\n content: \"\";\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n .navbar-right .dropdown-menu {\n left: auto;\n right: 0;\n }\n .navbar-right .dropdown-menu-left {\n left: 0;\n right: auto;\n }\n}\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n margin-left: -1px;\n}\n.btn-toolbar {\n margin-left: -5px;\n}\n.btn-toolbar .btn,\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n.btn-group.open .dropdown-toggle {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn .caret {\n margin-left: 0;\n}\n.btn-lg .caret {\n border-width: 5px 5px 0;\n border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n.input-group {\n position: relative;\n display: table;\n border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n}\n.input-group .form-control {\n position: relative;\n z-index: 2;\n float: left;\n width: 100%;\n margin-bottom: 0;\n}\n.input-group .form-control:focus {\n z-index: 3;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle;\n}\n.input-group-addon {\n padding: 6px 12px;\n font-size: 14px;\n font-weight: normal;\n line-height: 1;\n color: #555555;\n text-align: center;\n background-color: #eeeeee;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\n.input-group-addon.input-sm {\n padding: 5px 10px;\n font-size: 12px;\n border-radius: 3px;\n}\n.input-group-addon.input-lg {\n padding: 10px 16px;\n font-size: 18px;\n border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n.input-group-btn {\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n.input-group-btn > .btn {\n position: relative;\n}\n.input-group-btn > .btn + .btn {\n margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n z-index: 2;\n margin-left: -1px;\n}\n.nav {\n margin-bottom: 0;\n padding-left: 0;\n list-style: none;\n}\n.nav > li {\n position: relative;\n display: block;\n}\n.nav > li > a {\n position: relative;\n display: block;\n padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.nav > li.disabled > a {\n color: #777777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n color: #777777;\n text-decoration: none;\n background-color: transparent;\n cursor: not-allowed;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n background-color: #eeeeee;\n border-color: #337ab7;\n}\n.nav .nav-divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.nav > li > a > img {\n max-width: none;\n}\n.nav-tabs {\n border-bottom: 1px solid #ddd;\n}\n.nav-tabs > li {\n float: left;\n margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n margin-right: 2px;\n line-height: 1.42857143;\n border: 1px solid transparent;\n border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n border-color: #eeeeee #eeeeee #ddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n color: #555555;\n background-color: #fff;\n border: 1px solid #ddd;\n border-bottom-color: transparent;\n cursor: default;\n}\n.nav-tabs.nav-justified {\n width: 100%;\n border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n float: none;\n}\n.nav-tabs.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-tabs.nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs.nav-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs.nav-justified > .active > a,\n .nav-tabs.nav-justified > .active > a:hover,\n .nav-tabs.nav-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.nav-pills > li {\n float: left;\n}\n.nav-pills > li > a {\n border-radius: 4px;\n}\n.nav-pills > li + li {\n margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n color: #fff;\n background-color: #337ab7;\n}\n.nav-stacked > li {\n float: none;\n}\n.nav-stacked > li + li {\n margin-top: 2px;\n margin-left: 0;\n}\n.nav-justified {\n width: 100%;\n}\n.nav-justified > li {\n float: none;\n}\n.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs-justified {\n border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs-justified > .active > a,\n .nav-tabs-justified > .active > a:hover,\n .nav-tabs-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.tab-content > .tab-pane {\n display: none;\n}\n.tab-content > .active {\n display: block;\n}\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar {\n position: relative;\n min-height: 50px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n .navbar {\n border-radius: 4px;\n }\n}\n@media (min-width: 768px) {\n .navbar-header {\n float: left;\n }\n}\n.navbar-collapse {\n overflow-x: visible;\n padding-right: 15px;\n padding-left: 15px;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\n -webkit-overflow-scrolling: touch;\n}\n.navbar-collapse.in {\n overflow-y: auto;\n}\n@media (min-width: 768px) {\n .navbar-collapse {\n width: auto;\n border-top: 0;\n box-shadow: none;\n }\n .navbar-collapse.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0;\n overflow: visible !important;\n }\n .navbar-collapse.in {\n overflow-y: visible;\n }\n .navbar-fixed-top .navbar-collapse,\n .navbar-static-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n padding-left: 0;\n padding-right: 0;\n }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n .navbar-fixed-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n max-height: 200px;\n }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .container > .navbar-header,\n .container-fluid > .navbar-header,\n .container > .navbar-collapse,\n .container-fluid > .navbar-collapse {\n margin-right: 0;\n margin-left: 0;\n }\n}\n.navbar-static-top {\n z-index: 1000;\n border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n .navbar-static-top {\n border-radius: 0;\n }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n@media (min-width: 768px) {\n .navbar-fixed-top,\n .navbar-fixed-bottom {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0;\n border-width: 1px 0 0;\n}\n.navbar-brand {\n float: left;\n padding: 15px 15px;\n font-size: 18px;\n line-height: 20px;\n height: 50px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n text-decoration: none;\n}\n.navbar-brand > img {\n display: block;\n}\n@media (min-width: 768px) {\n .navbar > .container .navbar-brand,\n .navbar > .container-fluid .navbar-brand {\n margin-left: -15px;\n }\n}\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: 15px;\n padding: 9px 10px;\n margin-top: 8px;\n margin-bottom: 8px;\n background-color: transparent;\n background-image: none;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.navbar-toggle:focus {\n outline: 0;\n}\n.navbar-toggle .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n margin-top: 4px;\n}\n@media (min-width: 768px) {\n .navbar-toggle {\n display: none;\n }\n}\n.navbar-nav {\n margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: 20px;\n}\n@media (max-width: 767px) {\n .navbar-nav .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n }\n .navbar-nav .open .dropdown-menu > li > a,\n .navbar-nav .open .dropdown-menu .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n .navbar-nav .open .dropdown-menu > li > a {\n line-height: 20px;\n }\n .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-nav .open .dropdown-menu > li > a:focus {\n background-image: none;\n }\n}\n@media (min-width: 768px) {\n .navbar-nav {\n float: left;\n margin: 0;\n }\n .navbar-nav > li {\n float: left;\n }\n .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n }\n}\n.navbar-form {\n margin-left: -15px;\n margin-right: -15px;\n padding: 10px 15px;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n margin-top: 8px;\n margin-bottom: 8px;\n}\n@media (min-width: 768px) {\n .navbar-form .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .navbar-form .form-control-static {\n display: inline-block;\n }\n .navbar-form .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .navbar-form .input-group .input-group-addon,\n .navbar-form .input-group .input-group-btn,\n .navbar-form .input-group .form-control {\n width: auto;\n }\n .navbar-form .input-group > .form-control {\n width: 100%;\n }\n .navbar-form .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio,\n .navbar-form .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio label,\n .navbar-form .checkbox label {\n padding-left: 0;\n }\n .navbar-form .radio input[type=\"radio\"],\n .navbar-form .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .navbar-form .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n@media (max-width: 767px) {\n .navbar-form .form-group {\n margin-bottom: 5px;\n }\n .navbar-form .form-group:last-child {\n margin-bottom: 0;\n }\n}\n@media (min-width: 768px) {\n .navbar-form {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n}\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.navbar-btn {\n margin-top: 8px;\n margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n margin-top: 14px;\n margin-bottom: 14px;\n}\n.navbar-text {\n margin-top: 15px;\n margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n .navbar-text {\n float: left;\n margin-left: 15px;\n margin-right: 15px;\n }\n}\n@media (min-width: 768px) {\n .navbar-left {\n float: left !important;\n }\n .navbar-right {\n float: right !important;\n margin-right: -15px;\n }\n .navbar-right ~ .navbar-right {\n margin-right: 0;\n }\n}\n.navbar-default {\n background-color: #f8f8f8;\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n color: #777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n color: #5e5e5e;\n background-color: transparent;\n}\n.navbar-default .navbar-text {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n color: #333;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n border-color: #ddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n background-color: #ddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n background-color: #888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n background-color: #e7e7e7;\n color: #555;\n}\n@media (max-width: 767px) {\n .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n color: #777;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #333;\n background-color: transparent;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n }\n}\n.navbar-default .navbar-link {\n color: #777;\n}\n.navbar-default .navbar-link:hover {\n color: #333;\n}\n.navbar-default .btn-link {\n color: #777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n color: #333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n color: #ccc;\n}\n.navbar-inverse {\n background-color: #222;\n border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n color: #fff;\n background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n border-color: #333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n background-color: #333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n background-color: #fff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n background-color: #080808;\n color: #fff;\n}\n@media (max-width: 767px) {\n .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n border-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n color: #9d9d9d;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #fff;\n background-color: transparent;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n }\n}\n.navbar-inverse .navbar-link {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n color: #fff;\n}\n.navbar-inverse .btn-link {\n color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n color: #fff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n color: #444;\n}\n.breadcrumb {\n padding: 8px 15px;\n margin-bottom: 20px;\n list-style: none;\n background-color: #f5f5f5;\n border-radius: 4px;\n}\n.breadcrumb > li {\n display: inline-block;\n}\n.breadcrumb > li + li:before {\n content: \"/\\00a0\";\n padding: 0 5px;\n color: #ccc;\n}\n.breadcrumb > .active {\n color: #777777;\n}\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: 20px 0;\n border-radius: 4px;\n}\n.pagination > li {\n display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n position: relative;\n float: left;\n padding: 6px 12px;\n line-height: 1.42857143;\n text-decoration: none;\n color: #337ab7;\n background-color: #fff;\n border: 1px solid #ddd;\n margin-left: -1px;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n margin-left: 0;\n border-bottom-left-radius: 4px;\n border-top-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n border-bottom-right-radius: 4px;\n border-top-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n z-index: 2;\n color: #23527c;\n background-color: #eeeeee;\n border-color: #ddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n z-index: 3;\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n cursor: default;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n color: #777777;\n background-color: #fff;\n border-color: #ddd;\n cursor: not-allowed;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n border-bottom-left-radius: 6px;\n border-top-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n border-bottom-right-radius: 6px;\n border-top-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n border-bottom-left-radius: 3px;\n border-top-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n border-bottom-right-radius: 3px;\n border-top-right-radius: 3px;\n}\n.pager {\n padding-left: 0;\n margin: 20px 0;\n list-style: none;\n text-align: center;\n}\n.pager li {\n display: inline;\n}\n.pager li > a,\n.pager li > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.pager .next > a,\n.pager .next > span {\n float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n color: #777777;\n background-color: #fff;\n cursor: not-allowed;\n}\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.label:empty {\n display: none;\n}\n.btn .label {\n position: relative;\n top: -1px;\n}\n.label-default {\n background-color: #777777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n background-color: #5e5e5e;\n}\n.label-primary {\n background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n background-color: #286090;\n}\n.label-success {\n background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n background-color: #449d44;\n}\n.label-info {\n background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n background-color: #31b0d5;\n}\n.label-warning {\n background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n background-color: #ec971f;\n}\n.label-danger {\n background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n background-color: #c9302c;\n}\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: 12px;\n font-weight: bold;\n color: #fff;\n line-height: 1;\n vertical-align: middle;\n white-space: nowrap;\n text-align: center;\n background-color: #777777;\n border-radius: 10px;\n}\n.badge:empty {\n display: none;\n}\n.btn .badge {\n position: relative;\n top: -1px;\n}\n.btn-xs .badge,\n.btn-group-xs > .btn .badge {\n top: 0;\n padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.list-group-item > .badge {\n float: right;\n}\n.list-group-item > .badge + .badge {\n margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n margin-left: 3px;\n}\n.jumbotron {\n padding-top: 30px;\n padding-bottom: 30px;\n margin-bottom: 30px;\n color: inherit;\n background-color: #eeeeee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n color: inherit;\n}\n.jumbotron p {\n margin-bottom: 15px;\n font-size: 21px;\n font-weight: 200;\n}\n.jumbotron > hr {\n border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n border-radius: 6px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.jumbotron .container {\n max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n .jumbotron {\n padding-top: 48px;\n padding-bottom: 48px;\n }\n .container .jumbotron,\n .container-fluid .jumbotron {\n padding-left: 60px;\n padding-right: 60px;\n }\n .jumbotron h1,\n .jumbotron .h1 {\n font-size: 63px;\n }\n}\n.thumbnail {\n display: block;\n padding: 4px;\n margin-bottom: 20px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: border 0.2s ease-in-out;\n -o-transition: border 0.2s ease-in-out;\n transition: border 0.2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n margin-left: auto;\n margin-right: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n border-color: #337ab7;\n}\n.thumbnail .caption {\n padding: 9px;\n color: #333333;\n}\n.alert {\n padding: 15px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.alert h4 {\n margin-top: 0;\n color: inherit;\n}\n.alert .alert-link {\n font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n margin-bottom: 0;\n}\n.alert > p + p {\n margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n}\n.alert-success {\n background-color: #dff0d8;\n border-color: #d6e9c6;\n color: #3c763d;\n}\n.alert-success hr {\n border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n color: #2b542c;\n}\n.alert-info {\n background-color: #d9edf7;\n border-color: #bce8f1;\n color: #31708f;\n}\n.alert-info hr {\n border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n color: #245269;\n}\n.alert-warning {\n background-color: #fcf8e3;\n border-color: #faebcc;\n color: #8a6d3b;\n}\n.alert-warning hr {\n border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n color: #66512c;\n}\n.alert-danger {\n background-color: #f2dede;\n border-color: #ebccd1;\n color: #a94442;\n}\n.alert-danger hr {\n border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n@keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n.progress {\n overflow: hidden;\n height: 20px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: 12px;\n line-height: 20px;\n color: #fff;\n text-align: center;\n background-color: #337ab7;\n -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n -webkit-transition: width 0.6s ease;\n -o-transition: width 0.6s ease;\n transition: width 0.6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n -webkit-animation: progress-bar-stripes 2s linear infinite;\n -o-animation: progress-bar-stripes 2s linear infinite;\n animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.media {\n margin-top: 15px;\n}\n.media:first-child {\n margin-top: 0;\n}\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n.media-body {\n width: 10000px;\n}\n.media-object {\n display: block;\n}\n.media-object.img-thumbnail {\n max-width: none;\n}\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n.media-middle {\n vertical-align: middle;\n}\n.media-bottom {\n vertical-align: bottom;\n}\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n.list-group {\n margin-bottom: 20px;\n padding-left: 0;\n}\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid #ddd;\n}\n.list-group-item:first-child {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n}\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\na.list-group-item,\nbutton.list-group-item {\n color: #555;\n}\na.list-group-item .list-group-item-heading,\nbutton.list-group-item .list-group-item-heading {\n color: #333;\n}\na.list-group-item:hover,\nbutton.list-group-item:hover,\na.list-group-item:focus,\nbutton.list-group-item:focus {\n text-decoration: none;\n color: #555;\n background-color: #f5f5f5;\n}\nbutton.list-group-item {\n width: 100%;\n text-align: left;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n background-color: #eeeeee;\n color: #777777;\n cursor: not-allowed;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n color: #777777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n z-index: 2;\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n color: #c7ddef;\n}\n.list-group-item-success {\n color: #3c763d;\n background-color: #dff0d8;\n}\na.list-group-item-success,\nbutton.list-group-item-success {\n color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading,\nbutton.list-group-item-success .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-success:hover,\nbutton.list-group-item-success:hover,\na.list-group-item-success:focus,\nbutton.list-group-item-success:focus {\n color: #3c763d;\n background-color: #d0e9c6;\n}\na.list-group-item-success.active,\nbutton.list-group-item-success.active,\na.list-group-item-success.active:hover,\nbutton.list-group-item-success.active:hover,\na.list-group-item-success.active:focus,\nbutton.list-group-item-success.active:focus {\n color: #fff;\n background-color: #3c763d;\n border-color: #3c763d;\n}\n.list-group-item-info {\n color: #31708f;\n background-color: #d9edf7;\n}\na.list-group-item-info,\nbutton.list-group-item-info {\n color: #31708f;\n}\na.list-group-item-info .list-group-item-heading,\nbutton.list-group-item-info .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-info:hover,\nbutton.list-group-item-info:hover,\na.list-group-item-info:focus,\nbutton.list-group-item-info:focus {\n color: #31708f;\n background-color: #c4e3f3;\n}\na.list-group-item-info.active,\nbutton.list-group-item-info.active,\na.list-group-item-info.active:hover,\nbutton.list-group-item-info.active:hover,\na.list-group-item-info.active:focus,\nbutton.list-group-item-info.active:focus {\n color: #fff;\n background-color: #31708f;\n border-color: #31708f;\n}\n.list-group-item-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n}\na.list-group-item-warning,\nbutton.list-group-item-warning {\n color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading,\nbutton.list-group-item-warning .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-warning:hover,\nbutton.list-group-item-warning:hover,\na.list-group-item-warning:focus,\nbutton.list-group-item-warning:focus {\n color: #8a6d3b;\n background-color: #faf2cc;\n}\na.list-group-item-warning.active,\nbutton.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\nbutton.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus,\nbutton.list-group-item-warning.active:focus {\n color: #fff;\n background-color: #8a6d3b;\n border-color: #8a6d3b;\n}\n.list-group-item-danger {\n color: #a94442;\n background-color: #f2dede;\n}\na.list-group-item-danger,\nbutton.list-group-item-danger {\n color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading,\nbutton.list-group-item-danger .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-danger:hover,\nbutton.list-group-item-danger:hover,\na.list-group-item-danger:focus,\nbutton.list-group-item-danger:focus {\n color: #a94442;\n background-color: #ebcccc;\n}\na.list-group-item-danger.active,\nbutton.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\nbutton.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus,\nbutton.list-group-item-danger.active:focus {\n color: #fff;\n background-color: #a94442;\n border-color: #a94442;\n}\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n.panel {\n margin-bottom: 20px;\n background-color: #fff;\n border: 1px solid transparent;\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.panel-body {\n padding: 15px;\n}\n.panel-heading {\n padding: 10px 15px;\n border-bottom: 1px solid transparent;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n color: inherit;\n}\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: 16px;\n color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n color: inherit;\n}\n.panel-footer {\n padding: 10px 15px;\n background-color: #f5f5f5;\n border-top: 1px solid #ddd;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n border-top: 0;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n border-bottom: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n border-top-width: 0;\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n padding-left: 15px;\n padding-right: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n border-bottom-left-radius: 3px;\n border-bottom-right-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n border-top: 1px solid #ddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n border-bottom: 0;\n}\n.panel > .table-responsive {\n border: 0;\n margin-bottom: 0;\n}\n.panel-group {\n margin-bottom: 20px;\n}\n.panel-group .panel {\n margin-bottom: 0;\n border-radius: 4px;\n}\n.panel-group .panel + .panel {\n margin-top: 5px;\n}\n.panel-group .panel-heading {\n border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n border-top: 1px solid #ddd;\n}\n.panel-group .panel-footer {\n border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n border-bottom: 1px solid #ddd;\n}\n.panel-default {\n border-color: #ddd;\n}\n.panel-default > .panel-heading {\n color: #333333;\n background-color: #f5f5f5;\n border-color: #ddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ddd;\n}\n.panel-default > .panel-heading .badge {\n color: #f5f5f5;\n background-color: #333333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ddd;\n}\n.panel-primary {\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #337ab7;\n}\n.panel-success {\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n color: #dff0d8;\n background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #d6e9c6;\n}\n.panel-info {\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n color: #d9edf7;\n background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #bce8f1;\n}\n.panel-warning {\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n color: #fcf8e3;\n background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #faebcc;\n}\n.panel-danger {\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n color: #f2dede;\n background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n}\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border: 1px solid #e3e3e3;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.well blockquote {\n border-color: #ddd;\n border-color: rgba(0, 0, 0, 0.15);\n}\n.well-lg {\n padding: 24px;\n border-radius: 6px;\n}\n.well-sm {\n padding: 9px;\n border-radius: 3px;\n}\n.close {\n float: right;\n font-size: 21px;\n font-weight: bold;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: 0.2;\n filter: alpha(opacity=20);\n}\n.close:hover,\n.close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\nbutton.close {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n.modal-open {\n overflow: hidden;\n}\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1050;\n -webkit-overflow-scrolling: touch;\n outline: 0;\n}\n.modal.fade .modal-dialog {\n -webkit-transform: translate(0, -25%);\n -ms-transform: translate(0, -25%);\n -o-transform: translate(0, -25%);\n transform: translate(0, -25%);\n -webkit-transition: -webkit-transform 0.3s ease-out;\n -moz-transition: -moz-transform 0.3s ease-out;\n -o-transition: -o-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n}\n.modal.in .modal-dialog {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n.modal-content {\n position: relative;\n background-color: #fff;\n border: 1px solid #999;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n background-clip: padding-box;\n outline: 0;\n}\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n background-color: #000;\n}\n.modal-backdrop.fade {\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.modal-backdrop.in {\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\n.modal-header {\n padding: 15px;\n border-bottom: 1px solid #e5e5e5;\n}\n.modal-header .close {\n margin-top: -2px;\n}\n.modal-title {\n margin: 0;\n line-height: 1.42857143;\n}\n.modal-body {\n position: relative;\n padding: 15px;\n}\n.modal-footer {\n padding: 15px;\n text-align: right;\n border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0;\n}\n.modal-footer .btn-group .btn + .btn {\n margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n margin-left: 0;\n}\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n@media (min-width: 768px) {\n .modal-dialog {\n width: 600px;\n margin: 30px auto;\n }\n .modal-content {\n -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n }\n .modal-sm {\n width: 300px;\n }\n}\n@media (min-width: 992px) {\n .modal-lg {\n width: 900px;\n }\n}\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n font-size: 12px;\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.tooltip.in {\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.tooltip.top {\n margin-top: -3px;\n padding: 5px 0;\n}\n.tooltip.right {\n margin-left: 3px;\n padding: 0 5px;\n}\n.tooltip.bottom {\n margin-top: 3px;\n padding: 5px 0;\n}\n.tooltip.left {\n margin-left: -3px;\n padding: 0 5px;\n}\n.tooltip-inner {\n max-width: 200px;\n padding: 3px 8px;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 4px;\n}\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-left .tooltip-arrow {\n bottom: 0;\n right: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-right .tooltip-arrow {\n bottom: 0;\n left: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -5px;\n border-width: 5px 5px 5px 0;\n border-right-color: #000;\n}\n.tooltip.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -5px;\n border-width: 5px 0 5px 5px;\n border-left-color: #000;\n}\n.tooltip.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n top: 0;\n right: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n top: 0;\n left: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: none;\n max-width: 276px;\n padding: 1px;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n font-size: 14px;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n}\n.popover.top {\n margin-top: -10px;\n}\n.popover.right {\n margin-left: 10px;\n}\n.popover.bottom {\n margin-top: 10px;\n}\n.popover.left {\n margin-left: -10px;\n}\n.popover-title {\n margin: 0;\n padding: 8px 14px;\n font-size: 14px;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-radius: 5px 5px 0 0;\n}\n.popover-content {\n padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover > .arrow {\n border-width: 11px;\n}\n.popover > .arrow:after {\n border-width: 10px;\n content: \"\";\n}\n.popover.top > .arrow {\n left: 50%;\n margin-left: -11px;\n border-bottom-width: 0;\n border-top-color: #999999;\n border-top-color: rgba(0, 0, 0, 0.25);\n bottom: -11px;\n}\n.popover.top > .arrow:after {\n content: \" \";\n bottom: 1px;\n margin-left: -10px;\n border-bottom-width: 0;\n border-top-color: #fff;\n}\n.popover.right > .arrow {\n top: 50%;\n left: -11px;\n margin-top: -11px;\n border-left-width: 0;\n border-right-color: #999999;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n.popover.right > .arrow:after {\n content: \" \";\n left: 1px;\n bottom: -10px;\n border-left-width: 0;\n border-right-color: #fff;\n}\n.popover.bottom > .arrow {\n left: 50%;\n margin-left: -11px;\n border-top-width: 0;\n border-bottom-color: #999999;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n top: -11px;\n}\n.popover.bottom > .arrow:after {\n content: \" \";\n top: 1px;\n margin-left: -10px;\n border-top-width: 0;\n border-bottom-color: #fff;\n}\n.popover.left > .arrow {\n top: 50%;\n right: -11px;\n margin-top: -11px;\n border-right-width: 0;\n border-left-color: #999999;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n.popover.left > .arrow:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: #fff;\n bottom: -10px;\n}\n.carousel {\n position: relative;\n}\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n}\n.carousel-inner > .item {\n display: none;\n position: relative;\n -webkit-transition: 0.6s ease-in-out left;\n -o-transition: 0.6s ease-in-out left;\n transition: 0.6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n .carousel-inner > .item {\n -webkit-transition: -webkit-transform 0.6s ease-in-out;\n -moz-transition: -moz-transform 0.6s ease-in-out;\n -o-transition: -o-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000px;\n -moz-perspective: 1000px;\n perspective: 1000px;\n }\n .carousel-inner > .item.next,\n .carousel-inner > .item.active.right {\n -webkit-transform: translate3d(100%, 0, 0);\n transform: translate3d(100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.prev,\n .carousel-inner > .item.active.left {\n -webkit-transform: translate3d(-100%, 0, 0);\n transform: translate3d(-100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.next.left,\n .carousel-inner > .item.prev.right,\n .carousel-inner > .item.active {\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n left: 0;\n }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n display: block;\n}\n.carousel-inner > .active {\n left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n}\n.carousel-inner > .next {\n left: 100%;\n}\n.carousel-inner > .prev {\n left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n left: 0;\n}\n.carousel-inner > .active.left {\n left: -100%;\n}\n.carousel-inner > .active.right {\n left: 100%;\n}\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: 15%;\n opacity: 0.5;\n filter: alpha(opacity=50);\n font-size: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-control.left {\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n}\n.carousel-control.right {\n left: auto;\n right: 0;\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n}\n.carousel-control:hover,\n.carousel-control:focus {\n outline: 0;\n color: #fff;\n text-decoration: none;\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n margin-top: -10px;\n z-index: 5;\n display: inline-block;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n width: 20px;\n height: 20px;\n line-height: 1;\n font-family: serif;\n}\n.carousel-control .icon-prev:before {\n content: '\\2039';\n}\n.carousel-control .icon-next:before {\n content: '\\203a';\n}\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n}\n.carousel-indicators li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid #fff;\n border-radius: 10px;\n cursor: pointer;\n background-color: #000 \\9;\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-indicators .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: #fff;\n}\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-caption .btn {\n text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-prev,\n .carousel-control .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -10px;\n font-size: 30px;\n }\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .icon-prev {\n margin-left: -10px;\n }\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-next {\n margin-right: -10px;\n }\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n .carousel-indicators {\n bottom: 20px;\n }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-header:before,\n.modal-header:after,\n.modal-footer:before,\n.modal-footer:after {\n content: \" \";\n display: table;\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-header:after,\n.modal-footer:after {\n clear: both;\n}\n.center-block {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n.hidden {\n display: none !important;\n}\n.affix {\n position: fixed;\n}\n@-ms-viewport {\n width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n@media (max-width: 767px) {\n .visible-xs {\n display: block !important;\n }\n table.visible-xs {\n display: table !important;\n }\n tr.visible-xs {\n display: table-row !important;\n }\n th.visible-xs,\n td.visible-xs {\n display: table-cell !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-block {\n display: block !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline {\n display: inline !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm {\n display: block !important;\n }\n table.visible-sm {\n display: table !important;\n }\n tr.visible-sm {\n display: table-row !important;\n }\n th.visible-sm,\n td.visible-sm {\n display: table-cell !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-block {\n display: block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline {\n display: inline !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md {\n display: block !important;\n }\n table.visible-md {\n display: table !important;\n }\n tr.visible-md {\n display: table-row !important;\n }\n th.visible-md,\n td.visible-md {\n display: table-cell !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-block {\n display: block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline {\n display: inline !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg {\n display: block !important;\n }\n table.visible-lg {\n display: table !important;\n }\n tr.visible-lg {\n display: table-row !important;\n }\n th.visible-lg,\n td.visible-lg {\n display: table-cell !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-block {\n display: block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline {\n display: inline !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline-block {\n display: inline-block !important;\n }\n}\n@media (max-width: 767px) {\n .hidden-xs {\n display: none !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .hidden-sm {\n display: none !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .hidden-md {\n display: none !important;\n }\n}\n@media (min-width: 1200px) {\n .hidden-lg {\n display: none !important;\n }\n}\n.visible-print {\n display: none !important;\n}\n@media print {\n .visible-print {\n display: block !important;\n }\n table.visible-print {\n display: table !important;\n }\n tr.visible-print {\n display: table-row !important;\n }\n th.visible-print,\n td.visible-print {\n display: table-cell !important;\n }\n}\n.visible-print-block {\n display: none !important;\n}\n@media print {\n .visible-print-block {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n}\n@media print {\n .visible-print-inline {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n}\n@media print {\n .visible-print-inline-block {\n display: inline-block !important;\n }\n}\n@media print {\n .hidden-print {\n display: none !important;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */", - "/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS and IE text size adjust after device orientation change,\n// without disabling user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined for any HTML5 element in IE 8/9.\n// Correct `block` display not defined for `details` or `summary` in IE 10/11\n// and Firefox.\n// Correct `block` display not defined for `main` in IE 11.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9/10.\n// Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background-color: transparent;\n}\n\n//\n// Improve readability of focused elements when they are also in an\n// active/hover state.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n//\n\nabbr[title] {\n border-bottom: 1px dotted;\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9/10.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow not hidden in IE 9/10/11.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10/11.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari and Chrome.\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n box-sizing: content-box; //2\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9/10/11.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9/10/11.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}\n", - "/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request: h5bp.com/r\n// ==========================================================================\n\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important; // Black prints faster: h5bp.com/s\n box-shadow: none !important;\n text-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: #fff !important;\n }\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n\n // Bootstrap specific changes end\n}\n", - "//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// Star\n\n// Import the fonts\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('@{icon-font-path}@{icon-font-name}.eot');\n src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'),\n url('@{icon-font-path}@{icon-font-name}.woff2') format('woff2'),\n url('@{icon-font-path}@{icon-font-name}.woff') format('woff'),\n url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'),\n url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg');\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\002a\"; } }\n.glyphicon-plus { &:before { content: \"\\002b\"; } }\n.glyphicon-euro,\n.glyphicon-eur { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n.glyphicon-cd { &:before { content: \"\\e201\"; } }\n.glyphicon-save-file { &:before { content: \"\\e202\"; } }\n.glyphicon-open-file { &:before { content: \"\\e203\"; } }\n.glyphicon-level-up { &:before { content: \"\\e204\"; } }\n.glyphicon-copy { &:before { content: \"\\e205\"; } }\n.glyphicon-paste { &:before { content: \"\\e206\"; } }\n// The following 2 Glyphicons are omitted for the time being because\n// they currently use Unicode codepoints that are outside the\n// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle\n// non-BMP codepoints in CSS string escapes, and thus can't display these two icons.\n// Notably, the bug affects some older versions of the Android Browser.\n// More info: https://github.com/twbs/bootstrap/issues/10106\n// .glyphicon-door { &:before { content: \"\\1f6aa\"; } }\n// .glyphicon-key { &:before { content: \"\\1f511\"; } }\n.glyphicon-alert { &:before { content: \"\\e209\"; } }\n.glyphicon-equalizer { &:before { content: \"\\e210\"; } }\n.glyphicon-king { &:before { content: \"\\e211\"; } }\n.glyphicon-queen { &:before { content: \"\\e212\"; } }\n.glyphicon-pawn { &:before { content: \"\\e213\"; } }\n.glyphicon-bishop { &:before { content: \"\\e214\"; } }\n.glyphicon-knight { &:before { content: \"\\e215\"; } }\n.glyphicon-baby-formula { &:before { content: \"\\e216\"; } }\n.glyphicon-tent { &:before { content: \"\\26fa\"; } }\n.glyphicon-blackboard { &:before { content: \"\\e218\"; } }\n.glyphicon-bed { &:before { content: \"\\e219\"; } }\n.glyphicon-apple { &:before { content: \"\\f8ff\"; } }\n.glyphicon-erase { &:before { content: \"\\e221\"; } }\n.glyphicon-hourglass { &:before { content: \"\\231b\"; } }\n.glyphicon-lamp { &:before { content: \"\\e223\"; } }\n.glyphicon-duplicate { &:before { content: \"\\e224\"; } }\n.glyphicon-piggy-bank { &:before { content: \"\\e225\"; } }\n.glyphicon-scissors { &:before { content: \"\\e226\"; } }\n.glyphicon-bitcoin { &:before { content: \"\\e227\"; } }\n.glyphicon-btc { &:before { content: \"\\e227\"; } }\n.glyphicon-xbt { &:before { content: \"\\e227\"; } }\n.glyphicon-yen { &:before { content: \"\\00a5\"; } }\n.glyphicon-jpy { &:before { content: \"\\00a5\"; } }\n.glyphicon-ruble { &:before { content: \"\\20bd\"; } }\n.glyphicon-rub { &:before { content: \"\\20bd\"; } }\n.glyphicon-scale { &:before { content: \"\\e230\"; } }\n.glyphicon-ice-lolly { &:before { content: \"\\e231\"; } }\n.glyphicon-ice-lolly-tasted { &:before { content: \"\\e232\"; } }\n.glyphicon-education { &:before { content: \"\\e233\"; } }\n.glyphicon-option-horizontal { &:before { content: \"\\e234\"; } }\n.glyphicon-option-vertical { &:before { content: \"\\e235\"; } }\n.glyphicon-menu-hamburger { &:before { content: \"\\e236\"; } }\n.glyphicon-modal-window { &:before { content: \"\\e237\"; } }\n.glyphicon-oil { &:before { content: \"\\e238\"; } }\n.glyphicon-grain { &:before { content: \"\\e239\"; } }\n.glyphicon-sunglasses { &:before { content: \"\\e240\"; } }\n.glyphicon-text-size { &:before { content: \"\\e241\"; } }\n.glyphicon-text-color { &:before { content: \"\\e242\"; } }\n.glyphicon-text-background { &:before { content: \"\\e243\"; } }\n.glyphicon-object-align-top { &:before { content: \"\\e244\"; } }\n.glyphicon-object-align-bottom { &:before { content: \"\\e245\"; } }\n.glyphicon-object-align-horizontal{ &:before { content: \"\\e246\"; } }\n.glyphicon-object-align-left { &:before { content: \"\\e247\"; } }\n.glyphicon-object-align-vertical { &:before { content: \"\\e248\"; } }\n.glyphicon-object-align-right { &:before { content: \"\\e249\"; } }\n.glyphicon-triangle-right { &:before { content: \"\\e250\"; } }\n.glyphicon-triangle-left { &:before { content: \"\\e251\"; } }\n.glyphicon-triangle-bottom { &:before { content: \"\\e252\"; } }\n.glyphicon-triangle-top { &:before { content: \"\\e253\"; } }\n.glyphicon-console { &:before { content: \"\\e254\"; } }\n.glyphicon-superscript { &:before { content: \"\\e255\"; } }\n.glyphicon-subscript { &:before { content: \"\\e256\"; } }\n.glyphicon-menu-left { &:before { content: \"\\e257\"; } }\n.glyphicon-menu-right { &:before { content: \"\\e258\"; } }\n.glyphicon-menu-down { &:before { content: \"\\e259\"; } }\n.glyphicon-menu-up { &:before { content: \"\\e260\"; } }\n", - "//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// http://getbootstrap.com/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: http://a11yproject.com/posts/how-to-hide-content\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0,0,0,0);\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n// Useful for \"Skip to main content\" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n// Credit: HTML5 Boilerplate\n\n.sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n }\n}\n\n\n// iOS \"clickable elements\" fix for role=\"button\"\n//\n// Fixes \"clickability\" issue (and more generally, the firing of events such as focus as well)\n// for traditionally non-focusable elements with role=\"button\"\n// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n\n[role=\"button\"] {\n cursor: pointer;\n}\n", - "// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n", - "// WebKit-style focus\n\n.tab-focus() {\n // WebKit-specific. Other browsers will keep their default outline style.\n // (Initially tried to also force default via `outline: initial`,\n // but that seems to erroneously remove the outline in Firefox altogether.)\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n", - "// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size. Note that the\n// spelling of `min--moz-device-pixel-ratio` is intentional.\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n", - "//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: normal;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 300;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: (12px small font / 14px base font) * 100% = about 85%\nsmall,\n.small {\n font-size: floor((100% * @font-size-small / @font-size-base));\n}\n\nmark,\n.mark {\n background-color: @state-warning-bg;\n padding: .2em;\n}\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n.text-nowrap { white-space: nowrap; }\n\n// Transformation\n.text-lowercase { text-transform: lowercase; }\n.text-uppercase { text-transform: uppercase; }\n.text-capitalize { text-transform: capitalize; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// -------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n.dl-horizontal {\n dd {\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n\n @media (min-width: @dl-horizontal-breakpoint) {\n dt {\n float: left;\n width: (@dl-horizontal-offset - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @dl-horizontal-offset;\n }\n }\n}\n\n\n// Misc\n// -------------------------\n\n// Abbreviations and acronyms\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted @abbr-border-color;\n}\n.initialism {\n font-size: 90%;\n .text-uppercase();\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: '\\2014 \\00A0'; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n text-align: right;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: ''; }\n &:after {\n content: '\\00A0 \\2014'; // nbsp, em dash\n }\n }\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n", - "// Typography\n\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover,\n a&:focus {\n color: darken(@color, 10%);\n }\n}\n", - "// Contextual backgrounds\n\n.bg-variant(@color) {\n background-color: @color;\n a&:hover,\n a&:focus {\n background-color: darken(@color, 10%);\n }\n}\n", - "// Text overflow\n// Requires inline-block or block for proper styling\n\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n", - "//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n }\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n word-break: break-all;\n word-wrap: break-word;\n color: @pre-color;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n", - "//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n .container-fixed();\n\n @media (min-width: @screen-sm-min) {\n width: @container-sm;\n }\n @media (min-width: @screen-md-min) {\n width: @container-md;\n }\n @media (min-width: @screen-lg-min) {\n width: @container-lg;\n }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n .make-row();\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n .make-grid(lg);\n}\n", - "// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n margin-right: auto;\n margin-left: auto;\n padding-left: floor((@gutter / 2));\n padding-right: ceil((@gutter / 2));\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: ceil((@gutter / -2));\n margin-right: floor((@gutter / -2));\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n", - "// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-left: ceil((@grid-gutter-width / 2));\n padding-right: floor((@grid-gutter-width / 2));\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {\n .col-@{class}-push-0 {\n left: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {\n .col-@{class}-pull-0 {\n right: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n", - "//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n background-color: @table-bg;\n}\ncaption {\n padding-top: @table-cell-padding;\n padding-bottom: @table-cell-padding;\n color: @text-muted;\n text-align: left;\n}\nth {\n text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: @line-height-computed;\n // Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-cell-padding;\n line-height: @line-height-base;\n vertical-align: top;\n border-top: 1px solid @table-border-color;\n }\n }\n }\n // Bottom align for column headings\n > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid @table-border-color;\n }\n // Remove top border from thead by default\n > caption + thead,\n > colgroup + thead,\n > thead:first-child {\n > tr:first-child {\n > th,\n > td {\n border-top: 0;\n }\n }\n }\n // Account for multiple tbody instances\n > tbody + tbody {\n border-top: 2px solid @table-border-color;\n }\n\n // Nesting\n .table {\n background-color: @body-bg;\n }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-condensed-cell-padding;\n }\n }\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: 1px solid @table-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @table-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n > tbody > tr:nth-of-type(odd) {\n background-color: @table-bg-accent;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n > tbody > tr:hover {\n background-color: @table-bg-hover;\n }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-column;\n}\ntable {\n td,\n th {\n &[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-cell;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)\n\n @media screen and (max-width: @screen-xs-max) {\n width: 100%;\n margin-bottom: (@line-height-computed * 0.75);\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid @table-border-color;\n\n // Tighten up spacing\n > .table {\n margin-bottom: 0;\n\n // Ensure the content doesn't wrap\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n white-space: nowrap;\n }\n }\n }\n }\n\n // Special overrides for the bordered tables\n > .table-bordered {\n border: 0;\n\n // Nuke the appropriate borders so that the parent can handle them\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n\n // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n // chances are there will be only one `tr` in a `thead` and that would\n // remove the border altogether.\n > tbody,\n > tfoot {\n > tr:last-child {\n > th,\n > td {\n border-bottom: 0;\n }\n }\n }\n\n }\n }\n}\n", - "// Tables\n\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &:hover > .@{state},\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n", - "//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n // Chrome and Firefox set a `min-width: min-content;` on fieldsets,\n // so we reset that to ensure it behaves more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359.\n min-width: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: @line-height-computed;\n font-size: (@font-size-base * 1.5);\n line-height: inherit;\n color: @legend-color;\n border: 0;\n border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n display: inline-block;\n max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)\n margin-bottom: 5px;\n font-weight: bold;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9; // IE8-9\n line-height: normal;\n}\n\ninput[type=\"file\"] {\n display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n .tab-focus();\n}\n\n// Adjust output element\noutput {\n display: block;\n padding-top: (@padding-base-vertical + 1);\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n display: block;\n width: 100%;\n height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n background-color: @input-bg;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid @input-border;\n border-radius: @input-border-radius; // Note: This has no effect on s in CSS.\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n // Customize the `:focus` state to imitate native WebKit styles.\n .form-control-focus();\n\n // Placeholder\n .placeholder();\n\n // Unstyle the caret on ``\n// element gets special love because it's special, and that's a fact!\n.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n height: @input-height;\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n\n select& {\n height: @input-height;\n line-height: @input-height;\n }\n\n textarea&,\n select[multiple]& {\n height: auto;\n }\n}\n", - "//\n// Buttons\n// --------------------------------------------------\n\n\n// Base styles\n// --------------------------------------------------\n\n.btn {\n display: inline-block;\n margin-bottom: 0; // For input.btn\n font-weight: @btn-font-weight;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n white-space: nowrap;\n .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @btn-border-radius-base);\n .user-select(none);\n\n &,\n &:active,\n &.active {\n &:focus,\n &.focus {\n .tab-focus();\n }\n }\n\n &:hover,\n &:focus,\n &.focus {\n color: @btn-default-color;\n text-decoration: none;\n }\n\n &:active,\n &.active {\n outline: 0;\n background-image: none;\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n .opacity(.65);\n .box-shadow(none);\n }\n\n a& {\n &.disabled,\n fieldset[disabled] & {\n pointer-events: none; // Future-proof disabling of clicks on `` elements\n }\n }\n}\n\n\n// Alternate buttons\n// --------------------------------------------------\n\n.btn-default {\n .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);\n}\n.btn-primary {\n .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);\n}\n// Success appears as green\n.btn-success {\n .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);\n}\n// Info appears as blue-green\n.btn-info {\n .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);\n}\n// Warning appears as orange\n.btn-warning {\n .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);\n}\n// Danger and error appear as red\n.btn-danger {\n .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);\n}\n\n\n// Link buttons\n// -------------------------\n\n// Make a button look and behave like a link\n.btn-link {\n color: @link-color;\n font-weight: normal;\n border-radius: 0;\n\n &,\n &:active,\n &.active,\n &[disabled],\n fieldset[disabled] & {\n background-color: transparent;\n .box-shadow(none);\n }\n &,\n &:hover,\n &:focus,\n &:active {\n border-color: transparent;\n }\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n background-color: transparent;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @btn-link-disabled-color;\n text-decoration: none;\n }\n }\n}\n\n\n// Button Sizes\n// --------------------------------------------------\n\n.btn-lg {\n // line-height: ensure even-numbered height of button next to large input\n .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @btn-border-radius-large);\n}\n.btn-sm {\n // line-height: ensure proper height of button next to small input\n .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small);\n}\n.btn-xs {\n .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small);\n}\n\n\n// Block button\n// --------------------------------------------------\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n// Vertically space out multiple block buttons\n.btn-block + .btn-block {\n margin-top: 5px;\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n &.btn-block {\n width: 100%;\n }\n}\n", - "// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 25%);\n }\n &:hover {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n\n &:hover,\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 17%);\n border-color: darken(@border, 25%);\n }\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus,\n &.focus {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n", - "// Opacity\n\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n", - "//\n// Component animations\n// --------------------------------------------------\n\n// Heads up!\n//\n// We don't use the `.opacity()` mixin here since it causes a bug with text\n// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552.\n\n.fade {\n opacity: 0;\n .transition(opacity .15s linear);\n &.in {\n opacity: 1;\n }\n}\n\n.collapse {\n display: none;\n\n &.in { display: block; }\n tr&.in { display: table-row; }\n tbody&.in { display: table-row-group; }\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n .transition-property(~\"height, visibility\");\n .transition-duration(.35s);\n .transition-timing-function(ease);\n}\n", - "//\n// Dropdown menus\n// --------------------------------------------------\n\n\n// Dropdown arrow/caret\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: @caret-width-base dashed;\n border-top: @caret-width-base solid ~\"\\9\"; // IE8\n border-right: @caret-width-base solid transparent;\n border-left: @caret-width-base solid transparent;\n}\n\n// The dropdown wrapper (div)\n.dropup,\n.dropdown {\n position: relative;\n}\n\n// Prevent the focus on the dropdown toggle when closing dropdowns\n.dropdown-toggle:focus {\n outline: 0;\n}\n\n// The dropdown menu (ul)\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: @zindex-dropdown;\n display: none; // none by default, but block on \"open\" of the menu\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0; // override default ul\n list-style: none;\n font-size: @font-size-base;\n text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)\n background-color: @dropdown-bg;\n border: 1px solid @dropdown-fallback-border; // IE8 fallback\n border: 1px solid @dropdown-border;\n border-radius: @border-radius-base;\n .box-shadow(0 6px 12px rgba(0,0,0,.175));\n background-clip: padding-box;\n\n // Aligns the dropdown menu to right\n //\n // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`\n &.pull-right {\n right: 0;\n left: auto;\n }\n\n // Dividers (basically an hr) within the dropdown\n .divider {\n .nav-divider(@dropdown-divider-bg);\n }\n\n // Links within the dropdown menu\n > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: @line-height-base;\n color: @dropdown-link-color;\n white-space: nowrap; // prevent links from randomly breaking onto new lines\n }\n}\n\n// Hover/Focus state\n.dropdown-menu > li > a {\n &:hover,\n &:focus {\n text-decoration: none;\n color: @dropdown-link-hover-color;\n background-color: @dropdown-link-hover-bg;\n }\n}\n\n// Active state\n.dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-active-color;\n text-decoration: none;\n outline: 0;\n background-color: @dropdown-link-active-bg;\n }\n}\n\n// Disabled state\n//\n// Gray out text and ensure the hover/focus state remains gray\n\n.dropdown-menu > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-disabled-color;\n }\n\n // Nuke hover/focus effects\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none; // Remove CSS gradient\n .reset-filter();\n cursor: @cursor-disabled;\n }\n}\n\n// Open state for the dropdown\n.open {\n // Show the menu\n > .dropdown-menu {\n display: block;\n }\n\n // Remove the outline when :focus is triggered\n > a {\n outline: 0;\n }\n}\n\n// Menu positioning\n//\n// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown\n// menu with the parent.\n.dropdown-menu-right {\n left: auto; // Reset the default from `.dropdown-menu`\n right: 0;\n}\n// With v3, we enabled auto-flipping if you have a dropdown within a right\n// aligned nav component. To enable the undoing of that, we provide an override\n// to restore the default dropdown menu alignment.\n//\n// This is only for left-aligning a dropdown menu within a `.navbar-right` or\n// `.pull-right` nav component.\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n\n// Dropdown section headers\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: @font-size-small;\n line-height: @line-height-base;\n color: @dropdown-header-color;\n white-space: nowrap; // as with > li > a\n}\n\n// Backdrop to catch body clicks on mobile, etc.\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: (@zindex-dropdown - 10);\n}\n\n// Right aligned dropdowns\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n//\n// Just add .dropup after the standard .dropdown class and you're set, bro.\n// TODO: abstract this so that the navbar fixed styles are not placed here?\n\n.dropup,\n.navbar-fixed-bottom .dropdown {\n // Reverse the caret\n .caret {\n border-top: 0;\n border-bottom: @caret-width-base dashed;\n border-bottom: @caret-width-base solid ~\"\\9\"; // IE8\n content: \"\";\n }\n // Different positioning for bottom up menu\n .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n }\n}\n\n\n// Component alignment\n//\n// Reiterate per navbar.less and the modified component alignment there.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-right {\n .dropdown-menu {\n .dropdown-menu-right();\n }\n // Necessary for overrides of the default right aligned menu.\n // Will remove come v4 in all likelihood.\n .dropdown-menu-left {\n .dropdown-menu-left();\n }\n }\n}\n", - "// Horizontal dividers\n//\n// Dividers (basically an hr) within dropdowns and nav lists\n\n.nav-divider(@color: #e5e5e5) {\n height: 1px;\n margin: ((@line-height-computed / 2) - 1) 0;\n overflow: hidden;\n background-color: @color;\n}\n", - "// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n", - "//\n// Button groups\n// --------------------------------------------------\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle; // match .btn alignment given font-size hack above\n > .btn {\n position: relative;\n float: left;\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active,\n &.active {\n z-index: 2;\n }\n }\n}\n\n// Prevent double borders when buttons are next to each other\n.btn-group {\n .btn + .btn,\n .btn + .btn-group,\n .btn-group + .btn,\n .btn-group + .btn-group {\n margin-left: -1px;\n }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n margin-left: -5px; // Offset the first child's margin\n &:extend(.clearfix all);\n\n .btn,\n .btn-group,\n .input-group {\n float: left;\n }\n > .btn,\n > .btn-group,\n > .input-group {\n margin-left: 5px;\n }\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n\n// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match\n.btn-group > .btn:first-child {\n margin-left: 0;\n &:not(:last-child):not(.dropdown-toggle) {\n .border-right-radius(0);\n }\n}\n// Need .dropdown-toggle since :last-child doesn't apply, given that a .dropdown-menu is used immediately after it\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n .border-left-radius(0);\n}\n\n// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-right-radius(0);\n }\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-left-radius(0);\n}\n\n// On active and open, don't show outline\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-xs > .btn { &:extend(.btn-xs); }\n.btn-group-sm > .btn { &:extend(.btn-sm); }\n.btn-group-lg > .btn { &:extend(.btn-lg); }\n\n\n// Split button dropdowns\n// ----------------------\n\n// Give the line between buttons some depth\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n// The clickable button for toggling the menu\n// Remove the gradient and set the same inset shadow as the :active state\n.btn-group.open .dropdown-toggle {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n\n // Show no shadow for `.btn-link` since it has no other button styles.\n &.btn-link {\n .box-shadow(none);\n }\n}\n\n\n// Reposition the caret\n.btn .caret {\n margin-left: 0;\n}\n// Carets in other button sizes\n.btn-lg .caret {\n border-width: @caret-width-large @caret-width-large 0;\n border-bottom-width: 0;\n}\n// Upside down carets for .dropup\n.dropup .btn-lg .caret {\n border-width: 0 @caret-width-large @caret-width-large;\n}\n\n\n// Vertical button groups\n// ----------------------\n\n.btn-group-vertical {\n > .btn,\n > .btn-group,\n > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n }\n\n // Clear floats so dropdown menus can be properly placed\n > .btn-group {\n &:extend(.clearfix all);\n > .btn {\n float: none;\n }\n }\n\n > .btn + .btn,\n > .btn + .btn-group,\n > .btn-group + .btn,\n > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n }\n}\n\n.btn-group-vertical > .btn {\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n &:first-child:not(:last-child) {\n .border-top-radius(@btn-border-radius-base);\n .border-bottom-radius(0);\n }\n &:last-child:not(:first-child) {\n .border-top-radius(0);\n .border-bottom-radius(@btn-border-radius-base);\n }\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-bottom-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-top-radius(0);\n}\n\n\n// Justified button groups\n// ----------------------\n\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n > .btn,\n > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n }\n > .btn-group .btn {\n width: 100%;\n }\n\n > .btn-group .dropdown-menu {\n left: auto;\n }\n}\n\n\n// Checkbox and radio options\n//\n// In order to support the browser's form validation feedback, powered by the\n// `required` attribute, we have to \"hide\" the inputs via `clip`. We cannot use\n// `display: none;` or `visibility: hidden;` as that also hides the popover.\n// Simply visually hiding the inputs via `opacity` would leave them clickable in\n// certain cases which is prevented by using `clip` and `pointer-events`.\n// This way, we ensure a DOM element is visible to position the popover from.\n//\n// See https://github.com/twbs/bootstrap/pull/12794 and\n// https://github.com/twbs/bootstrap/pull/14559 for more information.\n\n[data-toggle=\"buttons\"] {\n > .btn,\n > .btn-group > .btn {\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0,0,0,0);\n pointer-events: none;\n }\n }\n}\n", - "// Single side border-radius\n\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n", - "//\n// Input groups\n// --------------------------------------------------\n\n// Base styles\n// -------------------------\n.input-group {\n position: relative; // For dropdowns\n display: table;\n border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table\n\n // Undo padding and float of grid classes\n &[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n }\n\n .form-control {\n // Ensure that the input is always above the *appended* addon button for\n // proper border colors.\n position: relative;\n z-index: 2;\n\n // IE9 fubars the placeholder attribute in text inputs and the arrows on\n // select elements in input groups. To fix it, we float the input. Details:\n // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855\n float: left;\n\n width: 100%;\n margin-bottom: 0;\n\n &:focus {\n z-index: 3;\n }\n }\n}\n\n// Sizing options\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n .input-lg();\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n .input-sm();\n}\n\n\n// Display as table-cell\n// -------------------------\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n}\n// Addon and addon wrapper for buttons\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle; // Match the inputs\n}\n\n// Text input groups\n// -------------------------\n.input-group-addon {\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: 1;\n color: @input-color;\n text-align: center;\n background-color: @input-group-addon-bg;\n border: 1px solid @input-group-addon-border-color;\n border-radius: @input-border-radius;\n\n // Sizing\n &.input-sm {\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n border-radius: @input-border-radius-small;\n }\n &.input-lg {\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n border-radius: @input-border-radius-large;\n }\n\n // Nuke default margins from checkboxes and radios to vertically center within.\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n margin-top: 0;\n }\n}\n\n// Reset rounded corners\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n .border-right-radius(0);\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n .border-left-radius(0);\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n\n// Button input groups\n// -------------------------\n.input-group-btn {\n position: relative;\n // Jankily prevent input button groups from wrapping with `white-space` and\n // `font-size` in combination with `inline-block` on buttons.\n font-size: 0;\n white-space: nowrap;\n\n // Negative margin for spacing, position for bringing hovered/focused/actived\n // element above the siblings.\n > .btn {\n position: relative;\n + .btn {\n margin-left: -1px;\n }\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active {\n z-index: 2;\n }\n }\n\n // Negative margin to only have a 1px border between the two\n &:first-child {\n > .btn,\n > .btn-group {\n margin-right: -1px;\n }\n }\n &:last-child {\n > .btn,\n > .btn-group {\n z-index: 2;\n margin-left: -1px;\n }\n }\n}\n", - "//\n// Navs\n// --------------------------------------------------\n\n\n// Base class\n// --------------------------------------------------\n\n.nav {\n margin-bottom: 0;\n padding-left: 0; // Override default ul/ol\n list-style: none;\n &:extend(.clearfix all);\n\n > li {\n position: relative;\n display: block;\n\n > a {\n position: relative;\n display: block;\n padding: @nav-link-padding;\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: @nav-link-hover-bg;\n }\n }\n\n // Disabled state sets text to gray and nukes hover/tab effects\n &.disabled > a {\n color: @nav-disabled-link-color;\n\n &:hover,\n &:focus {\n color: @nav-disabled-link-hover-color;\n text-decoration: none;\n background-color: transparent;\n cursor: @cursor-disabled;\n }\n }\n }\n\n // Open dropdowns\n .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @nav-link-hover-bg;\n border-color: @link-color;\n }\n }\n\n // Nav dividers (deprecated with v3.0.1)\n //\n // This should have been removed in v3 with the dropping of `.nav-list`, but\n // we missed it. We don't currently support this anywhere, but in the interest\n // of maintaining backward compatibility in case you use it, it's deprecated.\n .nav-divider {\n .nav-divider();\n }\n\n // Prevent IE8 from misplacing imgs\n //\n // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989\n > li > a > img {\n max-width: none;\n }\n}\n\n\n// Tabs\n// -------------------------\n\n// Give the tabs something to sit on\n.nav-tabs {\n border-bottom: 1px solid @nav-tabs-border-color;\n > li {\n float: left;\n // Make the list-items overlay the bottom border\n margin-bottom: -1px;\n\n // Actual tabs (as links)\n > a {\n margin-right: 2px;\n line-height: @line-height-base;\n border: 1px solid transparent;\n border-radius: @border-radius-base @border-radius-base 0 0;\n &:hover {\n border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;\n }\n }\n\n // Active state, and its :hover to override normal :hover\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-tabs-active-link-hover-color;\n background-color: @nav-tabs-active-link-hover-bg;\n border: 1px solid @nav-tabs-active-link-hover-border-color;\n border-bottom-color: transparent;\n cursor: default;\n }\n }\n }\n // pulling this in mainly for less shorthand\n &.nav-justified {\n .nav-justified();\n .nav-tabs-justified();\n }\n}\n\n\n// Pills\n// -------------------------\n.nav-pills {\n > li {\n float: left;\n\n // Links rendered as pills\n > a {\n border-radius: @nav-pills-border-radius;\n }\n + li {\n margin-left: 2px;\n }\n\n // Active state\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-pills-active-link-hover-color;\n background-color: @nav-pills-active-link-hover-bg;\n }\n }\n }\n}\n\n\n// Stacked pills\n.nav-stacked {\n > li {\n float: none;\n + li {\n margin-top: 2px;\n margin-left: 0; // no need for this gap between nav items\n }\n }\n}\n\n\n// Nav variations\n// --------------------------------------------------\n\n// Justified nav links\n// -------------------------\n\n.nav-justified {\n width: 100%;\n\n > li {\n float: none;\n > a {\n text-align: center;\n margin-bottom: 5px;\n }\n }\n\n > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n }\n\n @media (min-width: @screen-sm-min) {\n > li {\n display: table-cell;\n width: 1%;\n > a {\n margin-bottom: 0;\n }\n }\n }\n}\n\n// Move borders to anchors instead of bottom of list\n//\n// Mixin for adding on top the shared `.nav-justified` styles for our tabs\n.nav-tabs-justified {\n border-bottom: 0;\n\n > li > a {\n // Override margin from .nav-tabs\n margin-right: 0;\n border-radius: @border-radius-base;\n }\n\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border: 1px solid @nav-tabs-justified-link-border-color;\n }\n\n @media (min-width: @screen-sm-min) {\n > li > a {\n border-bottom: 1px solid @nav-tabs-justified-link-border-color;\n border-radius: @border-radius-base @border-radius-base 0 0;\n }\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border-bottom-color: @nav-tabs-justified-active-link-border-color;\n }\n }\n}\n\n\n// Tabbable tabs\n// -------------------------\n\n// Hide tabbable panes to start, show them when `.active`\n.tab-content {\n > .tab-pane {\n display: none;\n }\n > .active {\n display: block;\n }\n}\n\n\n// Dropdowns\n// -------------------------\n\n// Specific dropdowns\n.nav-tabs .dropdown-menu {\n // make dropdown border overlap tab border\n margin-top: -1px;\n // Remove the top rounded corners here since there is a hard edge above the menu\n .border-top-radius(0);\n}\n", - "//\n// Navbars\n// --------------------------------------------------\n\n\n// Wrapper and base class\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n position: relative;\n min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)\n margin-bottom: @navbar-margin-bottom;\n border: 1px solid transparent;\n\n // Prevent floats from breaking the navbar\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: @navbar-border-radius;\n }\n}\n\n\n// Navbar heading\n//\n// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy\n// styling of responsive aspects.\n\n.navbar-header {\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n }\n}\n\n\n// Navbar collapse (body)\n//\n// Group your navbar content into this for easy collapsing and expanding across\n// various device sizes. By default, this content is collapsed when <768px, but\n// will expand past that for a horizontal display.\n//\n// To start (on mobile devices) the navbar links, forms, and buttons are stacked\n// vertically and include a `max-height` to overflow in case you have too much\n// content for the user's viewport.\n\n.navbar-collapse {\n overflow-x: visible;\n padding-right: @navbar-padding-horizontal;\n padding-left: @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255,255,255,.1);\n &:extend(.clearfix all);\n -webkit-overflow-scrolling: touch;\n\n &.in {\n overflow-y: auto;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border-top: 0;\n box-shadow: none;\n\n &.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0; // Override default setting\n overflow: visible !important;\n }\n\n &.in {\n overflow-y: visible;\n }\n\n // Undo the collapse side padding for navbars with containers to ensure\n // alignment of right-aligned contents.\n .navbar-fixed-top &,\n .navbar-static-top &,\n .navbar-fixed-bottom & {\n padding-left: 0;\n padding-right: 0;\n }\n }\n}\n\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n .navbar-collapse {\n max-height: @navbar-collapse-max-height;\n\n @media (max-device-width: @screen-xs-min) and (orientation: landscape) {\n max-height: 200px;\n }\n }\n}\n\n\n// Both navbar header and collapse\n//\n// When a container is present, change the behavior of the header and collapse.\n\n.container,\n.container-fluid {\n > .navbar-header,\n > .navbar-collapse {\n margin-right: -@navbar-padding-horizontal;\n margin-left: -@navbar-padding-horizontal;\n\n @media (min-width: @grid-float-breakpoint) {\n margin-right: 0;\n margin-left: 0;\n }\n }\n}\n\n\n//\n// Navbar alignment options\n//\n// Display the navbar across the entirety of the page or fixed it to the top or\n// bottom of the page.\n\n// Static top (unfixed, but 100% wide) navbar\n.navbar-static-top {\n z-index: @zindex-navbar;\n border-width: 0 0 1px;\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n\n// Fix the top/bottom navbars when screen real estate supports it\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: @zindex-navbar-fixed;\n\n // Undo the rounded corners\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0; // override .navbar defaults\n border-width: 1px 0 0;\n}\n\n\n// Brand/project name\n\n.navbar-brand {\n float: left;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-computed;\n height: @navbar-height;\n\n &:hover,\n &:focus {\n text-decoration: none;\n }\n\n > img {\n display: block;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n .navbar > .container &,\n .navbar > .container-fluid & {\n margin-left: -@navbar-padding-horizontal;\n }\n }\n}\n\n\n// Navbar toggle\n//\n// Custom button for toggling the `.navbar-collapse`, powered by the collapse\n// JavaScript plugin.\n\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: @navbar-padding-horizontal;\n padding: 9px 10px;\n .navbar-vertical-align(34px);\n background-color: transparent;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n border-radius: @border-radius-base;\n\n // We remove the `outline` here, but later compensate by attaching `:hover`\n // styles to `:focus`.\n &:focus {\n outline: 0;\n }\n\n // Bars\n .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n }\n .icon-bar + .icon-bar {\n margin-top: 4px;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n display: none;\n }\n}\n\n\n// Navbar nav links\n//\n// Builds on top of the `.nav` components with its own modifier class to make\n// the nav the full height of the horizontal nav (above 768px).\n\n.navbar-nav {\n margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;\n\n > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: @line-height-computed;\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n > li > a,\n .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n > li > a {\n line-height: @line-height-computed;\n &:hover,\n &:focus {\n background-image: none;\n }\n }\n }\n }\n\n // Uncollapse the nav\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin: 0;\n\n > li {\n float: left;\n > a {\n padding-top: @navbar-padding-vertical;\n padding-bottom: @navbar-padding-vertical;\n }\n }\n }\n}\n\n\n// Navbar form\n//\n// Extension of the `.form-inline` with some extra flavor for optimum display in\n// our navbars.\n\n.navbar-form {\n margin-left: -@navbar-padding-horizontal;\n margin-right: -@navbar-padding-horizontal;\n padding: 10px @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n\n // Mixin behavior for optimum display\n .form-inline();\n\n .form-group {\n @media (max-width: @grid-float-breakpoint-max) {\n margin-bottom: 5px;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n }\n\n // Vertically center in expanded, horizontal navbar\n .navbar-vertical-align(@input-height-base);\n\n // Undo 100% width for pull classes\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n .box-shadow(none);\n }\n}\n\n\n// Dropdown menus\n\n// Menu position and menu carets\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n .border-top-radius(0);\n}\n// Menu position and menu caret support for dropups via extra dropup class\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n .border-top-radius(@navbar-border-radius);\n .border-bottom-radius(0);\n}\n\n\n// Buttons in navbars\n//\n// Vertically center a button within a navbar (when *not* in a form).\n\n.navbar-btn {\n .navbar-vertical-align(@input-height-base);\n\n &.btn-sm {\n .navbar-vertical-align(@input-height-small);\n }\n &.btn-xs {\n .navbar-vertical-align(22);\n }\n}\n\n\n// Text in navbars\n//\n// Add a class to make any element properly align itself vertically within the navbars.\n\n.navbar-text {\n .navbar-vertical-align(@line-height-computed);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin-left: @navbar-padding-horizontal;\n margin-right: @navbar-padding-horizontal;\n }\n}\n\n\n// Component alignment\n//\n// Repurpose the pull utilities as their own navbar utilities to avoid specificity\n// issues with parents and chaining. Only do this when the navbar is uncollapsed\n// though so that navbar contents properly stack and align in mobile.\n//\n// Declared after the navbar components to ensure more specificity on the margins.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-left { .pull-left(); }\n .navbar-right {\n .pull-right();\n margin-right: -@navbar-padding-horizontal;\n\n ~ .navbar-right {\n margin-right: 0;\n }\n }\n}\n\n\n// Alternate navbars\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n background-color: @navbar-default-bg;\n border-color: @navbar-default-border;\n\n .navbar-brand {\n color: @navbar-default-brand-color;\n &:hover,\n &:focus {\n color: @navbar-default-brand-hover-color;\n background-color: @navbar-default-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-default-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-default-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n\n .navbar-toggle {\n border-color: @navbar-default-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-default-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-default-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: @navbar-default-border;\n }\n\n // Dropdown menu items\n .navbar-nav {\n // Remove background color from open dropdown\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-default-link-active-bg;\n color: @navbar-default-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n > li > a {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n }\n }\n\n\n // Links in navbars\n //\n // Add a class to ensure links outside the navbar nav are colored correctly.\n\n .navbar-link {\n color: @navbar-default-link-color;\n &:hover {\n color: @navbar-default-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n }\n }\n }\n}\n\n// Inverse navbar\n\n.navbar-inverse {\n background-color: @navbar-inverse-bg;\n border-color: @navbar-inverse-border;\n\n .navbar-brand {\n color: @navbar-inverse-brand-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-brand-hover-color;\n background-color: @navbar-inverse-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-inverse-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-inverse-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n\n // Darken the responsive nav toggle\n .navbar-toggle {\n border-color: @navbar-inverse-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-inverse-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-inverse-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: darken(@navbar-inverse-bg, 7%);\n }\n\n // Dropdowns\n .navbar-nav {\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-inverse-link-active-bg;\n color: @navbar-inverse-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display\n .open .dropdown-menu {\n > .dropdown-header {\n border-color: @navbar-inverse-border;\n }\n .divider {\n background-color: @navbar-inverse-border;\n }\n > li > a {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n }\n }\n\n .navbar-link {\n color: @navbar-inverse-link-color;\n &:hover {\n color: @navbar-inverse-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n }\n }\n }\n}\n", - "// Navbar vertical align\n//\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n\n.navbar-vertical-align(@element-height) {\n margin-top: ((@navbar-height - @element-height) / 2);\n margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n", - "//\n// Utility classes\n// --------------------------------------------------\n\n\n// Floats\n// -------------------------\n\n.clearfix {\n .clearfix();\n}\n.center-block {\n .center-block();\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n\n\n// Toggling content\n// -------------------------\n\n// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n .text-hide();\n}\n\n\n// Hide from screenreaders and browsers\n//\n// Credit: HTML5 Boilerplate\n\n.hidden {\n display: none !important;\n}\n\n\n// For Affix plugin\n// -------------------------\n\n.affix {\n position: fixed;\n}\n", - "//\n// Breadcrumbs\n// --------------------------------------------------\n\n\n.breadcrumb {\n padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;\n margin-bottom: @line-height-computed;\n list-style: none;\n background-color: @breadcrumb-bg;\n border-radius: @border-radius-base;\n\n > li {\n display: inline-block;\n\n + li:before {\n content: \"@{breadcrumb-separator}\\00a0\"; // Unicode space added since inline-block means non-collapsing white-space\n padding: 0 5px;\n color: @breadcrumb-color;\n }\n }\n\n > .active {\n color: @breadcrumb-active-color;\n }\n}\n", - "//\n// Pagination (multiple pages)\n// --------------------------------------------------\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: @line-height-computed 0;\n border-radius: @border-radius-base;\n\n > li {\n display: inline; // Remove list-style and block-level defaults\n > a,\n > span {\n position: relative;\n float: left; // Collapse white-space\n padding: @padding-base-vertical @padding-base-horizontal;\n line-height: @line-height-base;\n text-decoration: none;\n color: @pagination-color;\n background-color: @pagination-bg;\n border: 1px solid @pagination-border;\n margin-left: -1px;\n }\n &:first-child {\n > a,\n > span {\n margin-left: 0;\n .border-left-radius(@border-radius-base);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius-base);\n }\n }\n }\n\n > li > a,\n > li > span {\n &:hover,\n &:focus {\n z-index: 2;\n color: @pagination-hover-color;\n background-color: @pagination-hover-bg;\n border-color: @pagination-hover-border;\n }\n }\n\n > .active > a,\n > .active > span {\n &,\n &:hover,\n &:focus {\n z-index: 3;\n color: @pagination-active-color;\n background-color: @pagination-active-bg;\n border-color: @pagination-active-border;\n cursor: default;\n }\n }\n\n > .disabled {\n > span,\n > span:hover,\n > span:focus,\n > a,\n > a:hover,\n > a:focus {\n color: @pagination-disabled-color;\n background-color: @pagination-disabled-bg;\n border-color: @pagination-disabled-border;\n cursor: @cursor-disabled;\n }\n }\n}\n\n// Sizing\n// --------------------------------------------------\n\n// Large\n.pagination-lg {\n .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n\n// Small\n.pagination-sm {\n .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n", - "// Pagination\n\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n > li {\n > a,\n > span {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n }\n &:first-child {\n > a,\n > span {\n .border-left-radius(@border-radius);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius);\n }\n }\n }\n}\n", - "//\n// Pager pagination\n// --------------------------------------------------\n\n\n.pager {\n padding-left: 0;\n margin: @line-height-computed 0;\n list-style: none;\n text-align: center;\n &:extend(.clearfix all);\n li {\n display: inline;\n > a,\n > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: @pager-bg;\n border: 1px solid @pager-border;\n border-radius: @pager-border-radius;\n }\n\n > a:hover,\n > a:focus {\n text-decoration: none;\n background-color: @pager-hover-bg;\n }\n }\n\n .next {\n > a,\n > span {\n float: right;\n }\n }\n\n .previous {\n > a,\n > span {\n float: left;\n }\n }\n\n .disabled {\n > a,\n > a:hover,\n > a:focus,\n > span {\n color: @pager-disabled-color;\n background-color: @pager-bg;\n cursor: @cursor-disabled;\n }\n }\n}\n", - "//\n// Labels\n// --------------------------------------------------\n\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: @label-color;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n\n // Add hover effects, but only for links\n a& {\n &:hover,\n &:focus {\n color: @label-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Empty labels collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for labels in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n}\n\n// Colors\n// Contextual variations (linked labels get darker on :hover)\n\n.label-default {\n .label-variant(@label-default-bg);\n}\n\n.label-primary {\n .label-variant(@label-primary-bg);\n}\n\n.label-success {\n .label-variant(@label-success-bg);\n}\n\n.label-info {\n .label-variant(@label-info-bg);\n}\n\n.label-warning {\n .label-variant(@label-warning-bg);\n}\n\n.label-danger {\n .label-variant(@label-danger-bg);\n}\n", - "// Labels\n\n.label-variant(@color) {\n background-color: @color;\n\n &[href] {\n &:hover,\n &:focus {\n background-color: darken(@color, 10%);\n }\n }\n}\n", - "//\n// Badges\n// --------------------------------------------------\n\n\n// Base class\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: @font-size-small;\n font-weight: @badge-font-weight;\n color: @badge-color;\n line-height: @badge-line-height;\n vertical-align: middle;\n white-space: nowrap;\n text-align: center;\n background-color: @badge-bg;\n border-radius: @badge-border-radius;\n\n // Empty badges collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for badges in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n\n .btn-xs &,\n .btn-group-xs > .btn & {\n top: 0;\n padding: 1px 5px;\n }\n\n // Hover state, but only for links\n a& {\n &:hover,\n &:focus {\n color: @badge-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Account for badges in navs\n .list-group-item.active > &,\n .nav-pills > .active > a > & {\n color: @badge-active-color;\n background-color: @badge-active-bg;\n }\n\n .list-group-item > & {\n float: right;\n }\n\n .list-group-item > & + & {\n margin-right: 5px;\n }\n\n .nav-pills > li > a > & {\n margin-left: 3px;\n }\n}\n", - "//\n// Jumbotron\n// --------------------------------------------------\n\n\n.jumbotron {\n padding-top: @jumbotron-padding;\n padding-bottom: @jumbotron-padding;\n margin-bottom: @jumbotron-padding;\n color: @jumbotron-color;\n background-color: @jumbotron-bg;\n\n h1,\n .h1 {\n color: @jumbotron-heading-color;\n }\n\n p {\n margin-bottom: (@jumbotron-padding / 2);\n font-size: @jumbotron-font-size;\n font-weight: 200;\n }\n\n > hr {\n border-top-color: darken(@jumbotron-bg, 10%);\n }\n\n .container &,\n .container-fluid & {\n border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n\n .container {\n max-width: 100%;\n }\n\n @media screen and (min-width: @screen-sm-min) {\n padding-top: (@jumbotron-padding * 1.6);\n padding-bottom: (@jumbotron-padding * 1.6);\n\n .container &,\n .container-fluid & {\n padding-left: (@jumbotron-padding * 2);\n padding-right: (@jumbotron-padding * 2);\n }\n\n h1,\n .h1 {\n font-size: @jumbotron-heading-font-size;\n }\n }\n}\n", - "//\n// Thumbnails\n// --------------------------------------------------\n\n\n// Mixin and adjust the regular image class\n.thumbnail {\n display: block;\n padding: @thumbnail-padding;\n margin-bottom: @line-height-computed;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(border .2s ease-in-out);\n\n > img,\n a > img {\n &:extend(.img-responsive);\n margin-left: auto;\n margin-right: auto;\n }\n\n // Add a hover state for linked versions only\n a&:hover,\n a&:focus,\n a&.active {\n border-color: @link-color;\n }\n\n // Image captions\n .caption {\n padding: @thumbnail-caption-padding;\n color: @thumbnail-caption-color;\n }\n}\n", - "//\n// Alerts\n// --------------------------------------------------\n\n\n// Base styles\n// -------------------------\n\n.alert {\n padding: @alert-padding;\n margin-bottom: @line-height-computed;\n border: 1px solid transparent;\n border-radius: @alert-border-radius;\n\n // Headings for larger alerts\n h4 {\n margin-top: 0;\n // Specified for the h4 to prevent conflicts of changing @headings-color\n color: inherit;\n }\n\n // Provide class for links that match alerts\n .alert-link {\n font-weight: @alert-link-font-weight;\n }\n\n // Improve alignment and spacing of inner content\n > p,\n > ul {\n margin-bottom: 0;\n }\n\n > p + p {\n margin-top: 5px;\n }\n}\n\n// Dismissible alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0.\n.alert-dismissible {\n padding-right: (@alert-padding + 20);\n\n // Adjust close link position\n .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n }\n}\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n.alert-success {\n .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);\n}\n\n.alert-info {\n .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);\n}\n\n.alert-warning {\n .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);\n}\n\n.alert-danger {\n .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);\n}\n", - "// Alerts\n\n.alert-variant(@background; @border; @text-color) {\n background-color: @background;\n border-color: @border;\n color: @text-color;\n\n hr {\n border-top-color: darken(@border, 5%);\n }\n .alert-link {\n color: darken(@text-color, 10%);\n }\n}\n", - "//\n// Progress bars\n// --------------------------------------------------\n\n\n// Bar animations\n// -------------------------\n\n// WebKit\n@-webkit-keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n// Spec and IE10+\n@keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n\n// Bar itself\n// -------------------------\n\n// Outer container\n.progress {\n overflow: hidden;\n height: @line-height-computed;\n margin-bottom: @line-height-computed;\n background-color: @progress-bg;\n border-radius: @progress-border-radius;\n .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));\n}\n\n// Bar of progress\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: @font-size-small;\n line-height: @line-height-computed;\n color: @progress-bar-color;\n text-align: center;\n background-color: @progress-bar-bg;\n .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));\n .transition(width .6s ease);\n}\n\n// Striped bars\n//\n// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar-striped` class, which you just add to an existing\n// `.progress-bar`.\n.progress-striped .progress-bar,\n.progress-bar-striped {\n #gradient > .striped();\n background-size: 40px 40px;\n}\n\n// Call animation for the active one\n//\n// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar.active` approach.\n.progress.active .progress-bar,\n.progress-bar.active {\n .animation(progress-bar-stripes 2s linear infinite);\n}\n\n\n// Variations\n// -------------------------\n\n.progress-bar-success {\n .progress-bar-variant(@progress-bar-success-bg);\n}\n\n.progress-bar-info {\n .progress-bar-variant(@progress-bar-info-bg);\n}\n\n.progress-bar-warning {\n .progress-bar-variant(@progress-bar-warning-bg);\n}\n\n.progress-bar-danger {\n .progress-bar-variant(@progress-bar-danger-bg);\n}\n", - "// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n", - "// Progress bars\n\n.progress-bar-variant(@color) {\n background-color: @color;\n\n // Deprecated parent class requirement as of v3.2.0\n .progress-striped & {\n #gradient > .striped();\n }\n}\n", - ".media {\n // Proper spacing between instances of .media\n margin-top: 15px;\n\n &:first-child {\n margin-top: 0;\n }\n}\n\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n\n.media-body {\n width: 10000px;\n}\n\n.media-object {\n display: block;\n\n // Fix collapse in webkit from max-width: 100% and display: table-cell.\n &.img-thumbnail {\n max-width: none;\n }\n}\n\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n\n.media-middle {\n vertical-align: middle;\n}\n\n.media-bottom {\n vertical-align: bottom;\n}\n\n// Reset margins on headings for tighter default spacing\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n\n// Media list variation\n//\n// Undo default ul/ol styles\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n", - "//\n// List groups\n// --------------------------------------------------\n\n\n// Base class\n//\n// Easily usable on