From 7d74352cbf9cc8da05dfe8b11a37765650c61a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E5=8B=87?= Date: Tue, 23 Apr 2024 17:27:24 +0800 Subject: [PATCH] support for Abstract Service and Controller in MongoDB Framework #831 (#832) * update * The vertices of the graph support the map data structure clsoe(#810) * 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); }