From 02f86f93d2ea61f65108e6ac7c583285b8bbcd52 Mon Sep 17 00:00:00 2001 From: zhangzhiyong Date: Mon, 27 Nov 2023 11:48:02 +0800 Subject: [PATCH 1/3] update --- .../openai/src/main/java/run/mone/openai/OpenaiCall.java | 2 +- .../src/test/java/run/mone/openapi/OpenApiTest.java | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/jcommon/openai/src/main/java/run/mone/openai/OpenaiCall.java b/jcommon/openai/src/main/java/run/mone/openai/OpenaiCall.java index 2c65952ee..8f4fafa1d 100644 --- a/jcommon/openai/src/main/java/run/mone/openai/OpenaiCall.java +++ b/jcommon/openai/src/main/java/run/mone/openai/OpenaiCall.java @@ -265,7 +265,7 @@ public void onClosed(EventSource eventSource) { @Override public void onFailure(EventSource eventSource, @Nullable Throwable t, @Nullable Response response) { - log.error("on failure error:" + t, t); + log.error("on failure error:" + response, t); } }); } diff --git a/jcommon/openai/src/test/java/run/mone/openapi/OpenApiTest.java b/jcommon/openai/src/test/java/run/mone/openapi/OpenApiTest.java index c37f33d55..1c25e6a22 100644 --- a/jcommon/openai/src/test/java/run/mone/openapi/OpenApiTest.java +++ b/jcommon/openai/src/test/java/run/mone/openapi/OpenApiTest.java @@ -26,6 +26,7 @@ import io.reactivex.disposables.Disposable; import lombok.Data; import lombok.SneakyThrows; +import okhttp3.Response; import okhttp3.logging.HttpLoggingInterceptor; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; @@ -168,7 +169,7 @@ private OpenAiClient client() { public void testCallStream() { String key = System.getenv("open_api_key"); CountDownLatch latch = new CountDownLatch(1); - OpenaiCall.callStream(key, null, "天空为什么是蓝色的", new String[]{}, new StreamListener() { + OpenaiCall.callStream(key, "", "天空为什么是蓝色的", new String[]{}, new StreamListener() { @Override public void onEvent(String str) { System.out.println(str); @@ -178,6 +179,11 @@ public void onEvent(String str) { public void end() { latch.countDown(); } + + @Override + public void onFailure(Throwable t, Response response) { + System.out.println(t + "" + response); + } }); latch.await(); } From 45628ab39a7c3f94beb930b9c5f7092a5dbac689 Mon Sep 17 00:00:00 2001 From: zhangzhiyong Date: Sun, 10 Mar 2024 10:20:11 +0800 Subject: [PATCH 2/3] The vertices of the graph support the map data structure clsoe(#810) --- jcommon/ai/pom.xml | 24 ++++ jcommon/ai/src/main/resources/prompt.txt | 80 ++++++++++++ jcommon/ai/src/main/resources/prompt2.txt | 46 +++++++ jcommon/ai/src/main/resources/prompt3.txt | 1 + jcommon/ai/zhipu/pom.xml | 38 ++++++ .../ai/zhipu/src/main/java/run/mone/Main.java | 57 +++++++++ .../java/com/xiaomi/youpin/docean/Ioc.java | 23 ++++ .../youpin/docean/anno/IocConfiguration.java | 16 +++ .../xiaomi/youpin/docean/test/TestRun.java | 21 ++++ .../main/java/run/mone/openai/OpenaiCall.java | 6 +- .../java/run/mone/openapi/OpenApiTest.java | 31 +++++ jcommon/pom.xml | 1 + .../com/xiaomi/data/push/graph/Graph.java | 12 ++ .../com/xiaomi/data/push/graph/Graph2.java | 114 ++++++++++++++++++ .../java/run/mone/struct/test/GraphTest.java | 22 ++++ 15 files changed, 491 insertions(+), 1 deletion(-) create mode 100644 jcommon/ai/pom.xml create mode 100644 jcommon/ai/src/main/resources/prompt.txt create mode 100644 jcommon/ai/src/main/resources/prompt2.txt create mode 100644 jcommon/ai/src/main/resources/prompt3.txt create mode 100644 jcommon/ai/zhipu/pom.xml create mode 100644 jcommon/ai/zhipu/src/main/java/run/mone/Main.java create mode 100644 jcommon/docean/src/main/java/com/xiaomi/youpin/docean/anno/IocConfiguration.java create mode 100644 jcommon/docean/src/test/java/com/xiaomi/youpin/docean/test/TestRun.java create mode 100644 jcommon/struct/src/main/java/com/xiaomi/data/push/graph/Graph2.java diff --git a/jcommon/ai/pom.xml b/jcommon/ai/pom.xml new file mode 100644 index 000000000..1fe47397e --- /dev/null +++ b/jcommon/ai/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + run.mone + jcommon + 1.4-jdk20-SNAPSHOT + + + ai + pom + + zhipu + + + + 21 + 21 + UTF-8 + + + \ No newline at end of file diff --git a/jcommon/ai/src/main/resources/prompt.txt b/jcommon/ai/src/main/resources/prompt.txt new file mode 100644 index 000000000..51956345a --- /dev/null +++ b/jcommon/ai/src/main/resources/prompt.txt @@ -0,0 +1,80 @@ +你是一名高级groovy工程师. +你要完成的好,我会给你100$小费. +我会给你提供一些代码只是和业务需求,请帮我生成相应的$code(groovy代码)代码和$params和$out. +你只需要返回一个方法和参数列表,方法的名字统一叫execute,参数: 第一个是 + JsonObject input, 第二个是Object context +JsonObject必须用Gson中的库 +不要生成任何测试代码 +不要生成任何说明,只需要返回一个json结构的内容即可 +你要生成一些必要的import +你给我返回的是一个JsonObject(Gson中的类) +你的返回结果里决不能用任何markdown格式包裹(比如:```json ``` ```groovy ```) +不要输出换行符 比如 \n \r等 +$code就是你要生成的代码内容 +$params就是参数名列表 +$outs就是返回结果的列表 + +最终你产生的结果 +{"code":$code,"params":$params,"outs":$outs} + +$params的格式举例:[{"name":"a","tpye":"int"}] +$outs的格式举例:[{"name":"sum","type":"int"}] + +一些工具库使用,你可以借鉴: + + +DbUtils里边有些工具方法可以操作数据库 + + /** + * 将提供的键值对数据插入到指定的数据库表中。 + */ + public Long insert(String tableName, Map data) + + /** + * 更新指定表的指定ID的记录,通过传入的键值对映射来设置新的列值 + */ + public void update(String tableName, String primaryKeyName, Map data) + + // 根据id列表批量删除 + public void deleteByIds(String tableName, String idName, List ids) + + +你尽量使用我给你提供的工具类,DbUtils 直接可以从context中获取 context.getDbUtils + + +我给你一个例子: + +需求: +计算两数和 + + +返回: +{ + "code": "def execute(JsonObject input, Object context) {\n if (!input.has('a') || !input.has('b')) {\n throw new IllegalArgumentException(\"JSON对象必须包含键'a'和'b'。\");\n }\n int a = input.get('a').getAsInt();\n int b = input.get('b').getAsInt();\n int sum = a + b;\n JsonObject result = new JsonObject();\n result.addProperty(\"sum\", sum);\n return result;\n}", + "params": [ + { + "name": "a", + "type": "int" + }, + { + "name": "b", + "type": "int" + } + ], + "outs": [ + { + "name": "sum", + "type": "int" + } + ] +} + +例子结束 + + + +需求: +给定一个List,返回这个list中的最大值和最小值 + +返回: + diff --git a/jcommon/ai/src/main/resources/prompt2.txt b/jcommon/ai/src/main/resources/prompt2.txt new file mode 100644 index 000000000..995f150f7 --- /dev/null +++ b/jcommon/ai/src/main/resources/prompt2.txt @@ -0,0 +1,46 @@ +你是一名高级groovy工程师. +你要完成的好,我会给你100$小费. +我会给你提供一个groovy方法,你帮我生成这个方法的描述(必须少于15个字). +你的返回结果永远是一个json格式的数据. +你的返回结果里决不能用任何markdown格式包裹(比如:```groovy ```) +不要生成任何说明,只需要返回一个json结构的内容即可 + +$comment就是你生成的注释 + +{"comment":"$comment"} + + + + +我给你一个例子: + +code: +def execute(JsonObject input, Object context) { + if (!input.has('a') || !input.has('b')) { + throw new IllegalArgumentException("JSON对象必须包含键'a'和'b'。"); + } + int a = input.get('a').getAsInt(); + int b = input.get('b').getAsInt(); + int sum = a + b; + JsonObject result = new JsonObject(); + result.addProperty("result", sum); + return result; +} + + +你返回的: +{"comment":"计算两数和"} + + + +例子结束 + + + + + + +code: +int a(int a, int b) { return a * b; } + +你的返回: diff --git a/jcommon/ai/src/main/resources/prompt3.txt b/jcommon/ai/src/main/resources/prompt3.txt new file mode 100644 index 000000000..3713b852e --- /dev/null +++ b/jcommon/ai/src/main/resources/prompt3.txt @@ -0,0 +1 @@ +李清照最好的6首词,并且给我解读 \ No newline at end of file diff --git a/jcommon/ai/zhipu/pom.xml b/jcommon/ai/zhipu/pom.xml new file mode 100644 index 000000000..5bebe7c32 --- /dev/null +++ b/jcommon/ai/zhipu/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + run.mone + ai + 1.4-jdk20-SNAPSHOT + + + zhipu + + + 21 + 21 + UTF-8 + + + + + + cn.bigmodel.openapi + oapi-java-sdk + release-V4-2.0.0 + + + + com.google.code.gson + gson + 2.8.5 + + + + + + + \ No newline at end of file diff --git a/jcommon/ai/zhipu/src/main/java/run/mone/Main.java b/jcommon/ai/zhipu/src/main/java/run/mone/Main.java new file mode 100644 index 000000000..c0800b973 --- /dev/null +++ b/jcommon/ai/zhipu/src/main/java/run/mone/Main.java @@ -0,0 +1,57 @@ +package run.mone; + +import com.zhipu.oapi.ClientV4; +import com.zhipu.oapi.Constants; +import com.zhipu.oapi.service.v4.model.ChatCompletionRequest; +import com.zhipu.oapi.service.v4.model.ChatMessage; +import com.zhipu.oapi.service.v4.model.ChatMessageRole; +import com.zhipu.oapi.service.v4.model.ModelApiResponse; +import lombok.SneakyThrows; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +/** + * @author goodjava@qq.com + * @date 2024/3/10 07:55 + */ +public class Main { + + private static final String requestIdTemplate = "myoz-%d"; + + + private static final String KEY = System.getenv("zhipu"); + + private static final String API_KEY = KEY.split("\\.")[0]; + + private static final String API_SECRET = KEY.split("\\.")[1]; + + private static final ClientV4 client = new ClientV4.Builder(API_KEY, API_SECRET).build(); + + + @SneakyThrows + public static void main(String[] args) { + List messages = new ArrayList<>(); + + String promptName = "prompt.txt"; + + String content = Files.readString(Paths.get("/Users/zhangzhiyong/IdeaProjects/goodjava/mone/jcommon/ai/zhipu/src/main/resources/" + promptName)); + + ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), content); + messages.add(chatMessage); + String requestId = String.format(requestIdTemplate, System.currentTimeMillis()); + ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() + .model(Constants.ModelChatGLM4) + .stream(Boolean.FALSE) + .invokeMethod(Constants.invokeMethod) + .messages(messages) + .requestId(requestId) + .build(); + ModelApiResponse invokeModelApiResp = client.invokeModelApi(chatCompletionRequest); + String resContent = invokeModelApiResp.getData().getChoices().get(0).getMessage().getContent().toString(); + System.out.println(resContent); +// System.out.println("model output:"+ new Gson().toJson(invokeModelApiResp)); + } +} \ No newline at end of file diff --git a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/Ioc.java b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/Ioc.java index 308737084..e02212203 100644 --- a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/Ioc.java +++ b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/Ioc.java @@ -26,6 +26,7 @@ import com.google.gson.reflect.TypeToken; import com.xiaomi.youpin.docean.anno.Component; import com.xiaomi.youpin.docean.anno.Controller; +import com.xiaomi.youpin.docean.anno.IocConfiguration; import com.xiaomi.youpin.docean.anno.Service; import com.xiaomi.youpin.docean.bo.Bean; import com.xiaomi.youpin.docean.common.*; @@ -69,6 +70,9 @@ public class Ioc { @Getter private String[] scanPackages; + @Getter + private Class primarySource; + /** * It needs to be used when interacting with containers like spring */ @@ -357,6 +361,25 @@ public Ioc classLoader(ClassLoader classLoader) { return this; } + public static Ioc run(Class primarySource, String... args) { + IocConfiguration configuration = primarySource.getAnnotation(IocConfiguration.class); + Ioc ioc = Ioc.ins(); + ioc.primarySource = primarySource; + parseArgumentsAndPopulateIoc(args, ioc); + return ioc.init(configuration.basePackage()); + } + + private static void parseArgumentsAndPopulateIoc(String[] args, Ioc ioc) { + //Determine if args is an even number; if so, place it into a map. + Map argsMap = new HashMap<>(); + if (args.length % 2 == 0) { + for (int i = 0; i < args.length; i += 2) { + argsMap.put(args[i], args[i + 1]); + } + } + argsMap.entrySet().forEach(entry -> ioc.putBean("$" + entry.getKey(), entry.getValue())); + } + public Ioc init(String... scanPackages) { this.scanPackages = scanPackages; this.publishEvent(new Event(EventType.initBegin)); diff --git a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/anno/IocConfiguration.java b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/anno/IocConfiguration.java new file mode 100644 index 000000000..e2c51adaf --- /dev/null +++ b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/anno/IocConfiguration.java @@ -0,0 +1,16 @@ +package com.xiaomi.youpin.docean.anno; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author goodjava@qq.com + * @date 2024/3/3 09:19 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) // 应用于类 +public @interface IocConfiguration { + String[] basePackage(); +} diff --git a/jcommon/docean/src/test/java/com/xiaomi/youpin/docean/test/TestRun.java b/jcommon/docean/src/test/java/com/xiaomi/youpin/docean/test/TestRun.java new file mode 100644 index 000000000..1d168dc94 --- /dev/null +++ b/jcommon/docean/src/test/java/com/xiaomi/youpin/docean/test/TestRun.java @@ -0,0 +1,21 @@ +package com.xiaomi.youpin.docean.test; + +import com.xiaomi.youpin.docean.Ioc; +import com.xiaomi.youpin.docean.anno.IocConfiguration; +import com.xiaomi.youpin.docean.test.demo.DemoDao; + +/** + * @author goodjava@qq.com + * @date 2024/3/5 14:47 + *

+ * Evaluating the efficacy of @IocConfiguration. + */ +@IocConfiguration(basePackage = {"com.xiaomi.youpin.docean.test.demo"}) +public class TestRun { + + public static void main(String[] args) { + DemoDao demoA = Ioc.run(TestRun.class, args).getBean(DemoDao.class); + System.out.println(demoA.get()); + } + +} diff --git a/jcommon/openai/src/main/java/run/mone/openai/OpenaiCall.java b/jcommon/openai/src/main/java/run/mone/openai/OpenaiCall.java index 8f4fafa1d..8644f7415 100644 --- a/jcommon/openai/src/main/java/run/mone/openai/OpenaiCall.java +++ b/jcommon/openai/src/main/java/run/mone/openai/OpenaiCall.java @@ -270,6 +270,9 @@ public void onFailure(EventSource eventSource, @Nullable Throwable t, @Nullable }); } + public static void callStream2(String req, StreamListener sl, ReqConfig config) { + callStream2(req, sl, config, null); + } /** * 原生的调用,底层只依赖okhttp @@ -278,10 +281,11 @@ public void onFailure(EventSource eventSource, @Nullable Throwable t, @Nullable * @param sl * @param config */ - public static void callStream2(String req, StreamListener sl, ReqConfig config) { + public static void callStream2(String req, StreamListener sl, ReqConfig config, Headers headers) { MediaType mediaType = MediaType.parse("application/json; charset=utf-8"); Request request = new Request.Builder() + .headers(headers) .url(config.getAskUrl()) .post(RequestBody.create(mediaType, req.getBytes(Charset.forName("utf8")))) .build(); diff --git a/jcommon/openai/src/test/java/run/mone/openapi/OpenApiTest.java b/jcommon/openai/src/test/java/run/mone/openapi/OpenApiTest.java index 1c25e6a22..dcb862c5b 100644 --- a/jcommon/openai/src/test/java/run/mone/openapi/OpenApiTest.java +++ b/jcommon/openai/src/test/java/run/mone/openapi/OpenApiTest.java @@ -26,6 +26,7 @@ import io.reactivex.disposables.Disposable; import lombok.Data; import lombok.SneakyThrows; +import okhttp3.Headers; import okhttp3.Response; import okhttp3.logging.HttpLoggingInterceptor; import okhttp3.sse.EventSource; @@ -298,6 +299,36 @@ public void test4() { System.out.println(res.getChoices().get(0).getMessage().getContent()); } + @SneakyThrows + @Test + public void testMoonshot() { + Stopwatch stopwatch = Stopwatch.createStarted(); + String question = Files.readString(Paths.get("/Users/zhangzhiyong/IdeaProjects/goodjava/mone/jcommon/ai/src/main/resources/prompt.txt")); + OpenAiClient client = OpenaiCall.client(System.getenv("moonshot_token"), "https://api.moonshot.cn/"); + ChatCompletionResponse res = client.chatCompletion(ChatCompletion.builder().model("moonshot-v1-8k").messages(Lists.newArrayList(Message.builder() + .role(Message.Role.USER).content(question) + .build())).build()); + System.out.println(res.getChoices().get(0).getMessage().getContent()); + System.out.println("use time:" + stopwatch.elapsed(TimeUnit.MILLISECONDS)); + } + + @SneakyThrows + @Test + public void testMoonshot2() { + String question = Files.readString(Paths.get("/Users/zhangzhiyong/IdeaProjects/goodjava/mone/jcommon/ai/src/main/resources/prompt3.txt")); + ChatCompletion completion = ChatCompletion.builder().stream(true).model("moonshot-v1-8k").messages(Lists.newArrayList(Message.builder() + .role(Message.Role.USER).content(question) + .build())).build(); + + OpenaiCall.callStream2(new Gson().toJson(completion), new StreamListener() { + @Override + public void onEvent(String str) { + System.out.println(str); + } + }, ReqConfig.builder().model("moonshot-v1-8k").askUrl("https://api.moonshot.cn/v1/chat/completions").build(), Headers.of("Authorization", "Bearer " + System.getenv("moonshot_token"))); + System.in.read(); + } + /** * 测试使用Azure的openai * POST https://b2c-mione-gpt35.openai.azure.com/openai/deployments/gpt-35-turbo/completions?api-version=2023-05-15 diff --git a/jcommon/pom.xml b/jcommon/pom.xml index 5cdfcac09..e96e2bf0c 100644 --- a/jcommon/pom.xml +++ b/jcommon/pom.xml @@ -86,6 +86,7 @@ match infra-common docean-spring-starter + ai diff --git a/jcommon/struct/src/main/java/com/xiaomi/data/push/graph/Graph.java b/jcommon/struct/src/main/java/com/xiaomi/data/push/graph/Graph.java index 548604199..b14bb9cec 100644 --- a/jcommon/struct/src/main/java/com/xiaomi/data/push/graph/Graph.java +++ b/jcommon/struct/src/main/java/com/xiaomi/data/push/graph/Graph.java @@ -62,6 +62,18 @@ public void addVertex(Vertex vertex) { this.vertexMap.put(vertex.getV(), vertex.getData()); } + //删除这个顶点,且删除和它相关的所有边(class) + public void removeVertex(int v) { + // 删除顶点数据 + vertexMap.remove(v); + // 删除所有出边 + adj[v].clear(); + // 删除所有入边 + for (List edges : adj) { + edges.removeIf(edge -> edge == v); + } + } + public List dependList(int v) { List result = Lists.newArrayList(); diff --git a/jcommon/struct/src/main/java/com/xiaomi/data/push/graph/Graph2.java b/jcommon/struct/src/main/java/com/xiaomi/data/push/graph/Graph2.java new file mode 100644 index 000000000..c601477f2 --- /dev/null +++ b/jcommon/struct/src/main/java/com/xiaomi/data/push/graph/Graph2.java @@ -0,0 +1,114 @@ +package com.xiaomi.data.push.graph; + +import com.google.common.collect.Maps; + +import java.util.*; + +/** + * @author goodjava@qq.com + * @date 2024/3/12 14:49 + */ +public class Graph2 { + + + //存储顶点的map + private Map vertexMap = Maps.newHashMap(); + + //存储边 + private Map> adjMap = Maps.newHashMap(); + + + /** + * 添加顶点 + * + * @param vertex + */ + public void addVertex(Vertex vertex) { + this.vertexMap.put(vertex.getV(), vertex.getData()); + } + + public void addEdge(int u, int v) { + //添加边 + if (!adjMap.containsKey(u)) { + adjMap.put(u, new ArrayList<>()); + } + adjMap.get(u).add(v); + } + + //删除这个顶点,且删除和它相关的所有边(class) + public void removeVertex(int vertex) { + // Remove the vertex from vertexMap + if (!vertexMap.containsKey(vertex)) { + throw new IllegalArgumentException("Vertex does not exist."); + } + vertexMap.remove(vertex); + + // Remove all edges associated with this vertex from adjMap + if (adjMap.containsKey(vertex)) { + adjMap.remove(vertex); + } + + // Remove the vertex from all adjacency lists + for (List edges : adjMap.values()) { + edges.removeIf(edge -> edge.equals(vertex)); + } + } + + public D getVertexData(int id) { + return vertexMap.get(id); + } + + + //帮我实现下拓扑排序(class) + public List topologicalSort() { + List result = new ArrayList<>(); + Map inDegree = new HashMap<>(); + Queue queue = new LinkedList<>(); + + // Initialize in-degree of all vertices + for (Integer v : vertexMap.keySet()) { + inDegree.put(v, 0); + } + + // Calculate in-degree of each vertex + for (List edges : adjMap.values()) { + for (Integer edge : edges) { + inDegree.put(edge, inDegree.get(edge) + 1); + } + } + + // Find all vertices with in-degree 0 + for (Map.Entry entry : inDegree.entrySet()) { + if (entry.getValue() == 0) { + queue.add(entry.getKey()); + } + } + + // Process vertices with in-degree 0 + while (!queue.isEmpty()) { + Integer vertex = queue.poll(); + result.add(vertex); + + // Decrease in-degree by 1 for all adjacent vertices + if (adjMap.containsKey(vertex)) { + for (Integer adjVertex : adjMap.get(vertex)) { + inDegree.put(adjVertex, inDegree.get(adjVertex) - 1); + + // If in-degree becomes 0, add it to the queue + if (inDegree.get(adjVertex) == 0) { + queue.add(adjVertex); + } + } + } + } + + // Check if there was a cycle + if (result.size() != vertexMap.size()) { + throw new IllegalStateException("Graph has a cycle, topological sort not possible"); + } + + return result; + } + + +} diff --git a/jcommon/struct/src/test/java/run/mone/struct/test/GraphTest.java b/jcommon/struct/src/test/java/run/mone/struct/test/GraphTest.java index 92fd594ca..f523339a5 100644 --- a/jcommon/struct/src/test/java/run/mone/struct/test/GraphTest.java +++ b/jcommon/struct/src/test/java/run/mone/struct/test/GraphTest.java @@ -1,6 +1,7 @@ package run.mone.struct.test; import com.xiaomi.data.push.graph.Graph; +import com.xiaomi.data.push.graph.Graph2; import com.xiaomi.data.push.graph.Vertex; import org.junit.Test; @@ -12,6 +13,27 @@ */ public class GraphTest { + + @Test + public void test1() { + Graph2 graph = new Graph2<>(); + + graph.addVertex(new Vertex<>(44, VertexData.builder().data("执行").id(0).build())); + graph.addVertex(new Vertex<>(88, VertexData.builder().data("aaa").id(0).build())); + graph.addVertex(new Vertex<>(33, VertexData.builder().data("开始").id(0).build())); + graph.addVertex(new Vertex<>(22, VertexData.builder().data("结束").id(0).build())); + + graph.addEdge(33,44); + graph.addEdge(44,22); + graph.addEdge(22,88); + + +// graph.removeVertex(44); + + List list = graph.topologicalSort(); + System.out.println(list); + } + @Test public void initializeAndTopologicallySortGraph() { Graph graph = new Graph<>(5); From d41a22089f56058a659215de8f559d245fb186cc Mon Sep 17 00:00:00 2001 From: zhangzhiyong Date: Tue, 23 Apr 2024 16:25:35 +0800 Subject: [PATCH 3/3] upport for Abstract Service and Controller in MongoDB Framework #831 --- .../docean/plugin/mongodb/MongodbPlugin.java | 18 +- .../src/main/java/run/mone/auth/Auth.java | 33 ++++ .../src/main/java/run/mone/auth/AuthAop.java | 87 ++++++++ .../main/java/run/mone/auth/AuthListener.java | 65 ++++++ .../src/main/java/run/mone/bo/MongoBo.java | 29 +++ .../src/main/java/run/mone/bo/Page.java | 25 +++ .../src/main/java/run/mone/bo/PathAuth.java | 28 +++ .../src/main/java/run/mone/bo/User.java | 62 ++++++ .../mone/controller/MongodbController.java | 187 ++++++++++++++++++ .../java/run/mone/service/MongoService.java | 118 +++++++++++ .../java/com/xiaomi/youpin/docean/Aop.java | 5 +- .../java/com/xiaomi/youpin/docean/Mvc.java | 61 ++++-- .../youpin/docean/anno/ModelAttribute.java | 16 ++ .../docean/aop/ProceedingJoinPoint.java | 4 + .../docean/listener/event/EventType.java | 1 + .../com/xiaomi/youpin/docean/mvc/Post.java | 84 +++++++- .../xiaomi/youpin/docean/test/CommonTest.java | 19 +- .../com/xiaomi/youpin/docean/test/bo/M.java | 5 + .../youpin/docean/common/MethodInvoker.java | 33 +++- 19 files changed, 823 insertions(+), 57 deletions(-) create mode 100644 jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/auth/Auth.java create mode 100644 jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/auth/AuthAop.java create mode 100644 jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/auth/AuthListener.java create mode 100644 jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/MongoBo.java create mode 100644 jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/Page.java create mode 100644 jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/PathAuth.java create mode 100644 jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/User.java create mode 100644 jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/controller/MongodbController.java create mode 100644 jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/service/MongoService.java create mode 100644 jcommon/docean/src/main/java/com/xiaomi/youpin/docean/anno/ModelAttribute.java diff --git a/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/com/xiaomi/youpin/docean/plugin/mongodb/MongodbPlugin.java b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/com/xiaomi/youpin/docean/plugin/mongodb/MongodbPlugin.java index 23983b8f8..4d6209c48 100644 --- a/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/com/xiaomi/youpin/docean/plugin/mongodb/MongodbPlugin.java +++ b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/com/xiaomi/youpin/docean/plugin/mongodb/MongodbPlugin.java @@ -24,8 +24,6 @@ import com.xiaomi.youpin.docean.plugin.config.Config; import dev.morphia.Datastore; import dev.morphia.Morphia; -import dev.morphia.mapping.Mapper; -import dev.morphia.mapping.MapperOptions; import lombok.extern.slf4j.Slf4j; import java.util.Set; @@ -41,24 +39,12 @@ public class MongodbPlugin implements IPlugin { @Override public void init(Set> classSet, Ioc ioc) { log.info("init mongodb plugin"); - MongoDb mongoDb = new MongoDb(); Config config = ioc.getBean(Config.class); - mongoDb.setMongoDbClient(config.get("mongodb.client", "")); - mongoDb.setMongoDatabase(config.get("mongodb.database", "")); - mongoDb.setCatEnabled(config.get("mongodb.cat.enabled", "false").equals("true")); - mongoDb.init(); - ioc.putBean(mongoDb); - - - MongoClient mongoClient = MongoClients.create(mongoDb.getMongoDbClient()); - Datastore datastore = Morphia.createDatastore(mongoClient, mongoDb.getMongoDatabase()); - - + MongoClient mongoClient = MongoClients.create(config.get("mongodb.client", "")); + Datastore datastore = Morphia.createDatastore(mongoClient, config.get("mongodb.database", "")); String packagePath = config.get("mongodb.package", "run.mone.bo"); datastore.getMapper().mapPackage(packagePath); datastore.ensureIndexes(); - - ioc.putBean(Datastore.class.getName(), datastore); } diff --git a/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/auth/Auth.java b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/auth/Auth.java new file mode 100644 index 000000000..80b0238db --- /dev/null +++ b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/auth/Auth.java @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Xiaomi + * + * 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 run.mone.auth; + +import java.lang.annotation.*; + +/** + * @author goodjava@qq.com + * @date 2020/7/5 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Auth { + + String name() default "name"; + + String role() default "admin"; +} diff --git a/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/auth/AuthAop.java b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/auth/AuthAop.java new file mode 100644 index 000000000..f9ea28bfa --- /dev/null +++ b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/auth/AuthAop.java @@ -0,0 +1,87 @@ +/* + * Copyright 2020 Xiaomi + * + * 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 run.mone.auth; + +import com.xiaomi.youpin.docean.Ioc; +import com.xiaomi.youpin.docean.anno.RequestMapping; +import com.xiaomi.youpin.docean.aop.ProceedingJoinPoint; +import com.xiaomi.youpin.docean.aop.anno.Aspect; +import com.xiaomi.youpin.docean.aop.anno.Before; +import com.xiaomi.youpin.docean.common.StringUtils; +import com.xiaomi.youpin.docean.mvc.ContextHolder; +import com.xiaomi.youpin.docean.mvc.MvcContext; +import lombok.extern.slf4j.Slf4j; +import run.mone.bo.PathAuth; +import run.mone.bo.User; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.concurrent.ConcurrentMap; + +/** + * @author goodjava@qq.com + */ +@Aspect +@Slf4j +public class AuthAop { + + /** + * 在方法执行前进行权限验证的切面方法 + * 根据方法上的 @Auth 注解和当前用户的角色进行权限验证 + * 如果用户没有相应的权限,则抛出 RuntimeException + * 同时记录了一些日志信息,包括用户名、角色、请求路径等 + */ + @Before(anno = Auth.class) + public void before(ProceedingJoinPoint point) { + log.info("before:" + Arrays.toString(point.getArgs())); + MvcContext context = ContextHolder.getContext().get(); + User user = (User) context.session().getAttribute("user"); + Method method = point.getMethod(); + RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); + log.info("name:{} role:{} path:{}", user.getUsername(), user.getRole(), requestMapping.path()); + Auth auth = method.getAnnotation(Auth.class); + + ConcurrentMap map = Ioc.ins().getBean("authMap"); + String path = context.getPath(); + //这里其实是数据库设置的(每次启动抓取一次) + PathAuth pa = map.get(path); + log.info("{}", pa); + + String role = auth.role(); + if (null != pa) { + if (StringUtils.isNotEmpty(pa.getRole())) { + role = pa.getRole(); + } + } + + //必须有后台管理权限 + if (role.equals("admin")) { + if (null == user || !user.getRole().equals("admin")) { + throw new RuntimeException("role error"); + } + } + //必须登录 + if (role.equals("user")) { + if (null == user) { + throw new RuntimeException("role error"); + } + } + + } + + +} diff --git a/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/auth/AuthListener.java b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/auth/AuthListener.java new file mode 100644 index 000000000..b4baaafd4 --- /dev/null +++ b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/auth/AuthListener.java @@ -0,0 +1,65 @@ +package run.mone.auth; + +import com.xiaomi.youpin.docean.Ioc; +import com.xiaomi.youpin.docean.listener.Listener; +import com.xiaomi.youpin.docean.listener.event.Event; +import com.xiaomi.youpin.docean.listener.event.EventType; +import com.xiaomi.youpin.docean.mvc.HttpRequestMethod; +import dev.morphia.Datastore; +import dev.morphia.query.filters.Filters; +import lombok.extern.slf4j.Slf4j; +import run.mone.bo.PathAuth; + +import java.lang.reflect.Method; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * @author goodjava@qq.com + * @date 2024/4/23 09:30 + */ +@Slf4j +public class AuthListener implements Listener { + + ConcurrentMap map = new ConcurrentHashMap<>(); + + public AuthListener(ConcurrentMap map) { + this.map = map; + } + + /** + * 重写onEvent方法,用于处理事件 + * 如果事件类型为initControllerFinish,则获取事件数据中的请求方法映射 + * 遍历请求方法映射,获取每个请求方法的路径和注解信息 + * 根据注解信息确定该路径所需的角色权限 + * 如果数据库中不存在该路径的权限记录,则创建一条新记录并插入数据库 + * 将路径和权限记录存入map中 + */ + @Override + public void onEvent(Event event) { + if (event.getEventType().equals(EventType.initControllerFinish)) { + ConcurrentHashMap requestMethodMap = event.getData(); + log.info("map size:{}", requestMethodMap.size()); + Datastore datastore = Ioc.ins().getBean(Datastore.class); + requestMethodMap.values().forEach(it -> { + try { + String path = it.getPath(); + Method method = it.getMethod(); + Auth auth = method.getAnnotation(Auth.class); + String role = "user"; + PathAuth pa = datastore.find(PathAuth.class).filter(Filters.eq("path", path)).first(); + if (null == pa) { + if (null != auth) { + role = auth.role(); + } + pa = PathAuth.builder().path(path).role(role).build(); + datastore.insert(pa); + } + map.put(path, pa); + } catch (Throwable ex) { + ex.printStackTrace(); + } + }); + } + } +} diff --git a/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/MongoBo.java b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/MongoBo.java new file mode 100644 index 000000000..a660ee568 --- /dev/null +++ b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/MongoBo.java @@ -0,0 +1,29 @@ +package run.mone.bo; + +/** + * @author goodjava@qq.com + * @date 2024/4/19 23:10 + */ +public interface MongoBo { + + String getId(); + + default String getUid() { + return ""; + } + + default void setUid(String uid) { + + } + + int getVersion(); + + void setState(int state); + + void setUtime(long utime); + + void setCtime(long ctime); + + void setVersion(int version); + +} diff --git a/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/Page.java b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/Page.java new file mode 100644 index 000000000..fd5446eeb --- /dev/null +++ b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/Page.java @@ -0,0 +1,25 @@ +package run.mone.bo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author goodjava@qq.com + * @date 2024/4/19 22:36 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Page { + + private List content; + private int page; + private int size; + private long total; + +} diff --git a/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/PathAuth.java b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/PathAuth.java new file mode 100644 index 000000000..9bf5c568d --- /dev/null +++ b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/PathAuth.java @@ -0,0 +1,28 @@ +package run.mone.bo; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author goodjava@qq.com + * @date 2024/4/22 21:35 + */ +@Entity("pathAuth") +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PathAuth { + + @Id + private String id; + + private String path; + + private String role; + +} diff --git a/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/User.java b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/User.java new file mode 100644 index 000000000..721b6498d --- /dev/null +++ b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/bo/User.java @@ -0,0 +1,62 @@ +package run.mone.bo; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户 + * + * @author mone + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Entity("user") +public class User implements MongoBo{ + + @Id + private String id; + + //用户名 + private String username; + + //密码(加密存储) + private String password; + + //角色 (user admin) + private String role; + + //邮箱地址 + private String email; + + //手机号 + private String mobile; + + //头像URL + private String avatarUrl; + + //个人简介 + private String bio; + + //创建时间 + private long ctime; + + //更新时间 + private long utime; + + //状态(0:正常 1:冻结 2:注销等) + private int state; + + //版本(用于乐观锁) + private int version; + + public User(String username, String password) { + this.username = username; + this.password = password; + } +} \ No newline at end of file diff --git a/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/controller/MongodbController.java b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/controller/MongodbController.java new file mode 100644 index 000000000..e0240bd55 --- /dev/null +++ b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/controller/MongodbController.java @@ -0,0 +1,187 @@ +package run.mone.controller; + +import com.xiaomi.youpin.docean.anno.RequestMapping; +import com.xiaomi.youpin.docean.anno.RequestParam; +import com.xiaomi.youpin.docean.mvc.ContextHolder; +import com.xiaomi.youpin.docean.mvc.MvcContext; +import dev.morphia.Datastore; +import dev.morphia.query.FindOptions; +import dev.morphia.query.Query; +import dev.morphia.query.filters.Filter; +import dev.morphia.query.filters.Filters; +import run.mone.auth.Auth; +import run.mone.bo.MongoBo; +import run.mone.bo.Page; +import run.mone.bo.User; + +import javax.annotation.Resource; +import java.util.List; + +/** + * @author goodjava@qq.com + * @date 2024/3/22 17:10 + */ +public class MongodbController { + + @Resource + protected Datastore datastore; + + private Class clazz; + + public MongodbController(Class clazz) { + this.clazz = clazz; + } + + //查询一条记录 + //{"name":"$eq","field":"name","value":"bbb"} + @Auth + @RequestMapping(path = "/one", method = "get") + public T one(Filter filter) { + return this.datastore.find(this.clazz).filter(filter).first(); + } + + @Auth + @RequestMapping(path = "/getById", method = "get") + public T getById(@RequestParam("id") String id) { + return datastore.find(this.clazz).filter(Filters.eq("id", id)).first(); + } + + //按id删除(class) + @Auth + @RequestMapping(path = "/deleteById", method = "get") + public boolean deleteById(@RequestParam("id") String id) { + this.datastore.find(this.clazz).filter(Filters.eq("id", id)).delete(); + return true; + } + + @Auth(role = "user") + @RequestMapping(path = "/deleteByIdAndUid", method = "get") + public boolean deleteByIdAndUid(@RequestParam("id") String id) { + User user = getCurrentUser(); + this.datastore.find(this.clazz).filter(Filters.and(Filters.eq("id", id), Filters.eq("uid", user.getId()))).delete(); + return true; + } + + public User getCurrentUser() { + MvcContext context = ContextHolder.getContext().get(); + User user = (User) context.session().getAttribute("user"); + if (null == user) { + throw new RuntimeException("user is null"); + } + return user; + } + + //查询所有记录 + @Auth + @RequestMapping(path = "/all", method = "get") + public List all() { + return this.datastore.find(this.clazz).filter(Filters.eq("state", 0)).iterator().toList(); + } + + //按filter条件搜索 + @Auth + @RequestMapping(path = "/search") + public List search(Filter filter) { + return this.datastore.find(this.clazz).filter(filter).iterator().toList(); + } + + //按filter和uid搜索 + @Auth(role = "user") + @RequestMapping(path = "/searchByFilterAndUid") + public List searchWithUid(Filter filter) { + User user = getCurrentUser(); + return this.datastore.find(this.clazz) + .filter(Filters.and(filter, Filters.eq("uid", user.getId()))) + .iterator() + .toList(); + } + + //带分页的search(class) + @Auth + @RequestMapping(path = "/searchWithPaging") + public Page searchWithPaging(Filter filter, int page, int size) { + Query query = this.datastore.find(this.clazz).filter(filter); + List list = query.iterator(new FindOptions().skip(page * size).limit(size)).toList(); + long total = query.count(); + return new Page<>(list, page, size, total); + } + + + //删除 + @Auth + @RequestMapping(path = "/delete") + public boolean delete(T t) { + this.datastore.delete(t); + return true; + } + + //删除 + @Auth(role = "user") + @RequestMapping(path = "/delete") + public boolean deleteWithUid(T t) { + User user = getCurrentUser(); + t.setUid(user.getUid()); + this.datastore.delete(t); + return true; + } + + //更新 + @Auth + @RequestMapping(path = "/update") + public boolean update(T t) { + t.setUtime(System.currentTimeMillis()); + this.datastore.merge(t); + return true; + } + + //更新 + @Auth(role = "user") + @RequestMapping(path = "/update") + public boolean updateWithUid(T t) { + User user = getCurrentUser(); + t.setUtime(System.currentTimeMillis()); + t.setUid(user.getUid()); + this.datastore.merge(t); + return true; + } + + //添加 + @Auth + @RequestMapping(path = "/add") + public boolean add(T t) { + long now = System.currentTimeMillis(); + t.setState(0); + t.setCtime(now); + t.setUtime(now); + this.datastore.insert(t); + return true; + } + + @Auth(role = "user") + @RequestMapping(path = "/add") + public boolean addWithUid(T t) { + User user = getCurrentUser(); + long now = System.currentTimeMillis(); + t.setState(0); + t.setUid(user.getUid()); + t.setCtime(now); + t.setUtime(now); + this.datastore.insert(t); + return true; + } + + + //添加并返回 + @Auth + @RequestMapping(path = "/addAndReturnDetail") + public T addAndReturnDetail(T t) { + long now = System.currentTimeMillis(); + t.setState(0); + t.setCtime(now); + t.setUtime(now); + this.datastore.insert(t); + return t; + } + + +} diff --git a/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/service/MongoService.java b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/service/MongoService.java new file mode 100644 index 000000000..7b0171630 --- /dev/null +++ b/jcommon/docean-plugin/docean-plugin-mongodb/src/main/java/run/mone/service/MongoService.java @@ -0,0 +1,118 @@ +package run.mone.service; + +import com.xiaomi.youpin.docean.anno.Service; +import dev.morphia.Datastore; +import dev.morphia.UpdateOptions; +import dev.morphia.query.filters.Filter; +import dev.morphia.query.filters.Filters; +import dev.morphia.query.updates.UpdateOperator; +import dev.morphia.query.updates.UpdateOperators; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; +import run.mone.bo.MongoBo; + +import javax.annotation.Resource; +import java.util.List; +import java.util.function.Consumer; + +/** + * @author goodjava@qq.com + */ +@Service +@Data +@Slf4j +public class MongoService { + + @Resource + private Datastore datastore; + + private Class clazz; + + public MongoService() { + } + + public MongoService(Class clazz) { + this.clazz = clazz; + } + + public T findFirst() { + return datastore.find(clazz).first(); + } + + public T findFirst(Filter filter) { + return datastore.find(this.clazz).filter(filter).first(); + } + + public long count() { + return datastore.find(this.clazz).count(); + } + + public long count(Filter filter) { + return datastore.find(this.clazz).filter(filter).count(); + } + + //实现findById,返回Document + public T findById(String id) { + return datastore.find(this.clazz).filter(Filters.eq("_id", id)).first(); + } + + public T find(Document nativeQuery) { + return datastore.find(this.clazz, nativeQuery).first(); + } + + public List findAll(Filter filter) { + return datastore.find(this.clazz).filter(filter).iterator().toList(); + } + + public boolean delete(T t) { + datastore.delete(t); + return true; + } + + public boolean deleteById(String id) { + return datastore.find(this.clazz).filter(Filters.eq("_id", id)).delete().getDeletedCount() == 1; + } + + public boolean delete(Filter filter) { + datastore.find(this.clazz).filter(filter).delete(); + return true; + } + + public boolean update(T t) { + t.setUtime(System.currentTimeMillis()); + datastore.merge(t); + return true; + } + + public boolean update(String id, UpdateOperator... updateOperators) { + return datastore.find(this.clazz).filter(Filters.eq("_id", id)).update(new UpdateOptions(), updateOperators).getModifiedCount() > 0; + } + + public boolean updateWithVersion(String id, Consumer consumer) { + for (; ; ) { + T data = this.findById(id); + if (null == data) { + return false; + } + int version = data.getVersion(); + consumer.accept(data); + data.setVersion(version + 1); + UpdateOperator setUpdateOperator = UpdateOperators.set(data); + boolean b = datastore.find(this.clazz).filter(Filters.and(Filters.eq("_id", id), Filters.eq("version", version))) + .update(new UpdateOptions(), setUpdateOperator).getModifiedCount() > 0; + if (b) { + break; + } else { + log.info("retry:{}", id); + } + } + return false; + } + + public boolean save(T t) { + datastore.insert(t); + return true; + } + +} diff --git a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/Aop.java b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/Aop.java index 44d80fc67..88a771e0a 100644 --- a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/Aop.java +++ b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/Aop.java @@ -73,14 +73,15 @@ public Aop init(LinkedHashMap map) { public Aop useAspect(Ioc ioc, String... packages) { Ioc.create(Thread.currentThread().getContextClassLoader()).cleanAnnos().setAnnos(Aspect.class).init(packages).getBeansWithAnnotation(Aspect.class).values().forEach(it -> { Arrays.stream(it.getClass().getDeclaredMethods()).forEach(m -> { - Optional.ofNullable(m.getAnnotation(Before.class)).ifPresent(a -> { + log.info("aop before class:{}", it.getClass()); EnhanceInterceptor interceptor = new EnhanceInterceptor() { @SneakyThrows @Override public void before(AopContext aopContext, Method method, Object[] args) { ProceedingJoinPoint point = new ProceedingJoinPoint(); point.setArgs(args); + point.setMethod(method); m.invoke(it, point); } }; @@ -93,6 +94,7 @@ public void before(AopContext aopContext, Method method, Object[] args) { @Override public Object after(AopContext context, Method method, Object res) { ProceedingJoinPoint point = new ProceedingJoinPoint(); + point.setMethod(method); point.setRes(res); return m.invoke(it, point); } @@ -156,6 +158,7 @@ public T enhance(Class clazz) { } } if (interceptors.size() > 0) { + log.info("enhance class:{}", clazz); return enhance(clazz, interceptors); } return (T) Optional.ofNullable(obj).orElse(ReflectUtils.getInstance(clazz)); diff --git a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/Mvc.java b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/Mvc.java index 6df887eaf..8d5437d5b 100644 --- a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/Mvc.java +++ b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/Mvc.java @@ -112,6 +112,7 @@ private void initHttpRequestMethod() { initializeControllerMapping(bean); } }); + ioc.publishEvent(new Event(EventType.initControllerFinish, this.requestMethodMap)); log.info("requestMethodMap size:{}", this.requestMethodMap.size()); } @@ -223,26 +224,7 @@ public List mapMethodParametersToClasses(Method method, Map result, HttpRequestMethod method) { Safe.run(() -> { - Object[] params = new Object[]{null}; - //If there is only one parameter and it is a String, no further parsing is necessary; it can be used directly. - if (isSingleStringParameterMethod(method) && request.getMethod().toUpperCase().equals("POST")) { - params[0] = new String(request.getBody()); - } else { - JsonElement args = getArgs(method, request.getMethod().toLowerCase(Locale.ROOT), request, context); - if (isSingleMvcContextParameterMethod(method)) { - params[0] = context; - } else { - try { - //可能方法中有泛型,这里给fix调,用实际的Class - List list = mapMethodParametersToClasses(method.getMethod(), method.getGenericSuperclassTypeArguments()); - Class[] types = list.toArray(new Class[]{}); - params = methodInvoker.getMethodParams(args, types); - } catch (Exception e) { - log.error("getMethodParams error,path:{},params:{},method:{}", context.getPath(), - GsonUtils.gson.toJson(context.getParams()), request.getMethod().toLowerCase(Locale.ROOT), e); - } - } - } + Object[] params = getMethodParams(context, request, method); Object data = invokeControllerMethod(method, params); @@ -294,6 +276,45 @@ public void callMethod(MvcContext context, MvcRequest request, MvcResponse respo }); } + private Object[] getMethodParams(MvcContext context, MvcRequest request, HttpRequestMethod method) { + Object[] params = new Object[]{null}; + //If there is only one parameter and it is a String, no further parsing is necessary; it can be used directly. + if (isSingleStringParameterMethod(method) && request.getMethod().toUpperCase().equals("POST")) { + params[0] = new String(request.getBody()); + } else { + JsonElement args = getArgs(method, request.getMethod().toLowerCase(Locale.ROOT), request, context); + if (isSingleMvcContextParameterMethod(method)) { + params[0] = context; + } else { + try { + Class[] types = getClasses(method); + //参数有可能从session中取 + params = methodInvoker.getMethodParams(args, types, name -> { + Object obj = context.session().getAttribute(name); + //提取失败,用户需要登录 + if (null == obj) { + throw new DoceanException("You are required to log in first."); + } + return obj; + }); + } catch (DoceanException doceanException) { + throw doceanException; + } catch (Exception e) { + log.error("getMethodParams error,path:{},params:{},method:{}", context.getPath(), + GsonUtils.gson.toJson(context.getParams()), request.getMethod().toLowerCase(Locale.ROOT), e); + } + } + } + return params; + } + + private Class[] getClasses(HttpRequestMethod method) { + //可能方法中有泛型,这里给fix调,用实际的Class + List list = mapMethodParametersToClasses(method.getMethod(), method.getGenericSuperclassTypeArguments()); + Class[] types = list.toArray(new Class[]{}); + return types; + } + private Object invokeControllerMethod(HttpRequestMethod method, Object[] params) { Object data = this.mvcConfig.isUseCglib() ? methodInvoker.invokeFastMethod(method.getObj(), method.getMethod(), params) : methodInvoker.invokeMethod(method.getObj(), method.getMethod(), params); diff --git a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/anno/ModelAttribute.java b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/anno/ModelAttribute.java new file mode 100644 index 000000000..87927d2ab --- /dev/null +++ b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/anno/ModelAttribute.java @@ -0,0 +1,16 @@ +package com.xiaomi.youpin.docean.anno; + +import java.lang.annotation.*; + +/** + * @author goodjava@qq.com + * @date 2024/4/23 10:55 + */ +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ModelAttribute { + + String value() default ""; + +} diff --git a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/aop/ProceedingJoinPoint.java b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/aop/ProceedingJoinPoint.java index e89512f1f..7f95c9b61 100644 --- a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/aop/ProceedingJoinPoint.java +++ b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/aop/ProceedingJoinPoint.java @@ -18,6 +18,8 @@ import lombok.Data; +import java.lang.reflect.Method; + /** * @author goodjava@qq.com * @date 5/14/22 @@ -27,6 +29,8 @@ public class ProceedingJoinPoint { private Object[]args; + private Method method; + private Object res; } diff --git a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/listener/event/EventType.java b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/listener/event/EventType.java index 3848acaad..794b7770e 100644 --- a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/listener/event/EventType.java +++ b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/listener/event/EventType.java @@ -14,6 +14,7 @@ public enum EventType { initFinish("initFinish"), mvcBegin("mvcBegin"), initController("initController"), + initControllerFinish("initControllerFinish"), mvcUploadFinish("mvcUploadFinish"), custom("custom"); diff --git a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/mvc/Post.java b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/mvc/Post.java index 09656a1a9..ac3f0e43c 100644 --- a/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/mvc/Post.java +++ b/jcommon/docean/src/main/java/com/xiaomi/youpin/docean/mvc/Post.java @@ -16,11 +16,19 @@ package com.xiaomi.youpin.docean.mvc; +import com.google.common.collect.Lists; import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.xiaomi.youpin.docean.anno.ModelAttribute; import com.xiaomi.youpin.docean.mvc.httpmethod.HttpMethodUtils; import com.xiaomi.youpin.docean.mvc.util.GsonUtils; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; + /** * @author goodjava@qq.com */ @@ -28,31 +36,89 @@ public abstract class Post { public static JsonArray getParams(HttpRequestMethod method, byte[] data, MvcContext context) { - JsonElement arguments = (null == data || data.length == 0) ? null : GsonUtils.gson.fromJson(new String(data), JsonElement.class); + JsonArray arrayRes = new JsonArray(); + JsonElement arguments = (null == data || data.length == 0) ? null : GsonUtils.gson.fromJson(new String(data), JsonElement.class); context.setParams(arguments); - JsonArray array = new JsonArray(); - HttpMethodUtils.addMvcContext(method, array); + Parameter[] methodParameters = method.getMethod().getParameters(); + boolean hasModelAttribute = Arrays.stream(methodParameters).filter(it -> it.getAnnotation(ModelAttribute.class) != null).findAny().isPresent(); + + if (hasModelAttribute) { + //没有传递任何参数 + if (null == arguments) { + Arrays.stream(methodParameters).forEach(it -> { + if (it.getAnnotation(ModelAttribute.class) != null) { + arrayRes.add(obj(it.getAnnotation(ModelAttribute.class).value())); + } + }); + return arrayRes; + } + if (arguments.isJsonArray()) { + JsonArray array = arguments.getAsJsonArray(); + ArrayList list = Lists.newArrayList(array.iterator()); + AtomicInteger i = new AtomicInteger(0); + Arrays.stream(methodParameters).forEach(it -> { + if (it.getAnnotation(ModelAttribute.class) != null) { + arrayRes.add(obj(it.getAnnotation(ModelAttribute.class).value())); + } else { + arrayRes.add(list.get(i.get())); + i.incrementAndGet(); + } + }); + return arrayRes; + } + //只传递过来一个参数 + if (arguments.isJsonObject()) { + Arrays.stream(methodParameters).forEach(it -> { + if (it.getAnnotation(ModelAttribute.class) != null) { + arrayRes.add(obj(it.getAnnotation(ModelAttribute.class).value())); + } else { + arrayRes.add(arguments.getAsJsonObject()); + } + }); + return arrayRes; + } + + if (arguments.isJsonPrimitive()) { + Arrays.stream(methodParameters).forEach(it -> { + if (it.getAnnotation(ModelAttribute.class) != null) { + arrayRes.add(obj(it.getAnnotation(ModelAttribute.class).value())); + } else { + arrayRes.add(arguments.getAsJsonPrimitive()); + } + }); + return arrayRes; + } + } + + HttpMethodUtils.addMvcContext(method, arrayRes); if (null == arguments) { - context.setParams(array); - return array; + context.setParams(arrayRes); + return arrayRes; } if (arguments.isJsonObject()) { - array.add(arguments); + arrayRes.add(arguments); } if (arguments.isJsonArray()) { - arguments.getAsJsonArray().forEach(it -> array.add(it)); + arguments.getAsJsonArray().forEach(it -> arrayRes.add(it)); } if (arguments.isJsonPrimitive()) { - array.add(arguments.getAsJsonPrimitive()); + arrayRes.add(arguments.getAsJsonPrimitive()); } - return array; + return arrayRes; + } + + private static JsonObject obj(String name) { + JsonObject obj = new JsonObject(); + obj.addProperty("__type__", "session"); + obj.addProperty("__name__", name); + return obj; } } diff --git a/jcommon/docean/src/test/java/com/xiaomi/youpin/docean/test/CommonTest.java b/jcommon/docean/src/test/java/com/xiaomi/youpin/docean/test/CommonTest.java index 86c916042..65d89fc05 100644 --- a/jcommon/docean/src/test/java/com/xiaomi/youpin/docean/test/CommonTest.java +++ b/jcommon/docean/src/test/java/com/xiaomi/youpin/docean/test/CommonTest.java @@ -20,10 +20,13 @@ import com.google.common.reflect.TypeToken; import com.google.gson.JsonArray; import com.xiaomi.youpin.docean.anno.Component; +import com.xiaomi.youpin.docean.anno.ModelAttribute; import com.xiaomi.youpin.docean.anno.Service; import com.xiaomi.youpin.docean.bo.Bean; import com.xiaomi.youpin.docean.common.ReflectUtils; +import com.xiaomi.youpin.docean.test.bo.M; import com.xiaomi.youpin.docean.test.demo.DemoVo; +import lombok.SneakyThrows; import net.sf.cglib.beans.BeanGenerator; import net.sf.cglib.beans.BeanMap; import net.sf.cglib.proxy.Mixin; @@ -49,6 +52,20 @@ public void testBoolean() { } + @SneakyThrows + @Test + public void testMethod() { + Method method = M.class.getMethod("sum", int.class, int.class); + Arrays.stream(method.getParameters()).forEach(it -> { + System.out.println(it.getName()); + ModelAttribute ma = it.getAnnotation(ModelAttribute.class); + if (null != ma) { + System.out.println(ma.value()); + } + }); + } + + @Test public void testTypeToken() { TypeToken> typeToken = new TypeToken>() { @@ -60,7 +77,7 @@ public void testTypeToken() { @Test public void testOptional() { - Optional.ofNullable(null).ifPresent(it->{ + Optional.ofNullable(null).ifPresent(it -> { System.out.println(it); }); } diff --git a/jcommon/docean/src/test/java/com/xiaomi/youpin/docean/test/bo/M.java b/jcommon/docean/src/test/java/com/xiaomi/youpin/docean/test/bo/M.java index 012520b02..20a482aba 100644 --- a/jcommon/docean/src/test/java/com/xiaomi/youpin/docean/test/bo/M.java +++ b/jcommon/docean/src/test/java/com/xiaomi/youpin/docean/test/bo/M.java @@ -1,5 +1,6 @@ package com.xiaomi.youpin.docean.test.bo; +import com.xiaomi.youpin.docean.anno.ModelAttribute; import io.netty.util.Recycler; import lombok.Data; @@ -15,4 +16,8 @@ public class M { private String name; private Recycler.Handle handle; + + public int sum(@ModelAttribute("a") int a, int b) { + return a+b; + } } diff --git a/jcommon/easy/src/main/java/com/xiaomi/youpin/docean/common/MethodInvoker.java b/jcommon/easy/src/main/java/com/xiaomi/youpin/docean/common/MethodInvoker.java index 31b6d5ec4..6a6c3f071 100644 --- a/jcommon/easy/src/main/java/com/xiaomi/youpin/docean/common/MethodInvoker.java +++ b/jcommon/easy/src/main/java/com/xiaomi/youpin/docean/common/MethodInvoker.java @@ -16,10 +16,7 @@ package com.xiaomi.youpin.docean.common; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; +import com.google.gson.*; import com.xiaomi.youpin.docean.adapter.DoubleDefaultAdapter; import com.xiaomi.youpin.docean.adapter.IntegerDefaultAdapter; import com.xiaomi.youpin.docean.adapter.LongDefaultAdapter; @@ -37,6 +34,7 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -166,23 +164,38 @@ public Object[] getMethodParams(Object obj, String methodName, JsonElement param public Object[] getMethodParams(Method method, JsonElement params) { Class[] types = method.getParameterTypes(); - return getMethodParams(params, types); + return getMethodParams(params, types, str -> null); } - public Object[] getMethodParams(JsonElement params, Class[] types) { + public Object[] getMethodParams(JsonElement params, Class[] types, Function function) { + //没有参数 if (types.length == 0) { return new Object[]{}; } - //一个参数,不需要用参数列表 + //一个参数 if (params.isJsonObject()) { - return Stream.of(gson.fromJson(gson.toJson(params), types[0])).toArray(); + return Stream.of(getObj(params, types[0], function)).toArray(); } //参数列表 if (params.isJsonArray()) { JsonArray array = params.getAsJsonArray(); - return IntStream.range(0, types.length).mapToObj(i -> gson.fromJson(gson.toJson(array.get(i)), types[i])).collect(Collectors.toList()).toArray(); + return IntStream.range(0, types.length).mapToObj(i -> { + JsonElement ele = array.get(i); + return getObj(ele, types[i], function); + }).collect(Collectors.toList()).toArray(); } - throw new DoceanException(); + throw new DoceanException("getMethodParams error"); + } + + private Object getObj(JsonElement params, Class type, Function function) { + if (params.isJsonObject()) { + JsonObject obj = params.getAsJsonObject(); + if (obj.has("__type__") && obj.get("__type__").getAsString().equals("session")) { + String name = obj.get("__name__").getAsString(); + return function.apply(name); + } + } + return gson.fromJson(params, type); }