热门目的地:
目的地,具体表示为某个地方, 可以是一个国家,可以是一个省份,也可以是一个城市,跟行政地图地点表述是一个意思。后期的景点,攻略,游记,团游,团购,酒店,门票,美食,购物等等拓展都可以围绕目的地展开。
区域是比目的地更大一级地域范围,比如亚洲,欧洲,东亚,东南亚,甚至国内,港澳台等,其下面可以挂载多个目的地(多个目的地的集合)。
区域表和目的地表的设计:
区域表:destination_region

目的地表:destination

关系:一个区域下挂载了多个目的地,区域下有refIds字段表示区域下关联的目的地。区域也是目的地的一员,只是deep的值不同(deep为1的目的地)。目的地中有字段parentId表示父级区域。
Region的CRUD和分页
实体类:
/*** 区域*/@Setter@Getter@Document("destination_region")public class Region extends BaseDomain {public static final int STATE_HOT = 1;public static final int STATE_NORMAL = 0;private String name; //地区名private String sn; //地区编码private List<String> refIds = new ArrayList<>(); //关联的idprivate int ishot = STATE_NORMAL; //是否为热点private int sequence; //序号private String info; //简介public String getJsonString(){Map<String, Object> map = new HashMap<>();map.put("id",id);map.put("name",name);map.put("sn",sn);map.put("refIds",getRefIds());map.put("ishot",ishot);map.put("sequence",sequence);map.put("info",info);return JSON.toJSONString(map);}}
QueryObject:
包含了所有实体类分页查询时共同的属性;
public class QueryObject implements Serializable {private String keyword;private int currentPage = 1;private int pageSize = 5;private Pageable pageable;//分页设置对象public Pageable getPageable() {if (pageable == null) {//没有指定分页对象,默认id倒序return PageRequest.of(currentPage - 1, pageSize, Sort.Direction.DESC, "_id");}return pageable;}public String getKeyword() {return StringUtils.hasLength(keyword) ? keyword : "";}}
创建MongoDB的持久层接口repository:
repository使用JPA的命名规则命名方法;
public interface RegionRepository extends MongoRepository<Region, String> {}
业务层代码:
业务的具体实现;
根据mgr管理页面发出的请求设计controller:
controller中负责接收前台传过来的参数;
调用业务层的方法处理业务;
共享数据,将处理的数据结果返还给前台;
控制页面跳转;
高级查询下的分页query:
使用repository.findAll()返回Page对象不能满足高查的结果:用MongoTemplate来实现负责的查询;
问题:但是MongoTemplate的find方法返回的是List集合:如何将List封装成page?
和以前封装PageResult一样:两传两查三计算7个属性值把它封装进PageImpl对象中(Page是一个接口);
@Autowiredprivate MongoTemplate template;@Overridepublic Page<Region> query(QueryObject qo) {//List<Region> list = repository.findAll(); //使用repostory对象查询的结果不满足带条件查询//使用MongoTemplate,可以满足条件查询,但是返回的是list集合,如何封装成Page对象?Query query = new Query();//PageImpl构造器的参数3:totallong total = template.count(query, Region.class);if (total == 0) {//总条数为0 就返回空的page回去return Page.empty();}//PageImpl构造器的参数2:pageablePageRequest pageable = PageRequest.of(qo.getCurrentPage() - 1, qo.getPageSize(), Sort.Direction.DESC, "_id");query.with(pageable);//PageImpl构造器的参数1:listList<Region> list = template.find(query, Region.class);return new PageImpl<Region>(list, pageable, total);}
Page是个接口:实现类的构造器需要三个参数:准备三个参数,然后进行封装。

目的地的设计
目的地的实体类设计:
表中关联了区域的parentId,MongoDB的非关系型数据库,采用冗余字段的设计将页面需要展示的数据字段设计好,方便查询。
/*** 目的地(行政地区:国家/省份/城市)*/("destination")public class Destination extends BaseDomain {private String name; //名称private String english; //英文名private String parentId; //上级目的地private String parentName; //上级目的名private String info; //简介private int deep;private String coverUrl;//子地区//忽略该字段,不将该字段添加进数据库中private List<Destination> children = new ArrayList<>();public String getJsonString(){Map<String,Object> map = new HashMap<>();map.put("id", super.getId());map.put("info", this.info);return JSON.toJSONString(map);}}
添加区域的时候,关联目的地时,下拉目的地候选:

通过前台发送添加请求的时候带到后台的deep去mongodb中查询到区域关联的目的地显示出来;前台添加是选中之后,提交保存将选中的目的地的id封装在区域对象中的refIds集合带到后台,执行保存操作,将数据保存到MongoDB中。
区域编辑:
编辑时,模态框中的数据回显,
MongoDB更新时是全量更新,要注意维护前台没有给的数据,维护的时候需要一个字段一个字段的set进
@Overridepublic void update(Region region) {//使用save方法是全量更新,如果更新部分,会出现未更新的字段被null覆盖//repository.save(region);//方案一:查询————替代————保存(效率低,用update)//方案二:显示使用update逻辑 update set...//条件对象Query query = new Query();query.addCriteria(Criteria.where("_id").is(region.getId()));/*//更新的列Update update = new Update();update.set("name", region.getName());update.set("sn", region.getSn());update.set("refIds", region.getRefIds());update.set("ishot", region.getIshot());update.set("sequence", region.getSequence());update.set("info", region.getInfo());template.updateMulti(query, update, Region.class);*///使用工具简化更新的代码DBHelper.update(template, query, region, "name", "sn", "refIds", "ishot", "sequence", "info");}
设计DBHelper类简化分页和更新操作:
方法中用到的反射来适应不同类型的对象,用方法的可变参数来适应数量不等的属性参数。方法中遍历属性的移位数组通过实体类的字节码对象拿到不同属性的get方法,拿到对应属性的值set进update对象中。
/*** 简化MongoDB中高查分页和更新是的繁琐操作*/public class DBHelper {/*** 更新* @param template* @param query 条件* @param source 数据* @param fields 更新字段*/public static void update(MongoTemplate template, Query query, Object source, String... fields){Update update = new Update();Class<?> clz = source.getClass();for (String field : fields) {try {Method method = clz.getMethod("get" + field.substring(0, 1).toUpperCase() + field.substring(1));Object o = method.invoke(source);update.set(field, o);} catch (Exception e) {e.printStackTrace();}}template.updateMulti(query,update, clz);}/*** 分页* @param template* @param clz 操作对象* @param query 条件* @param pageable 分页* @param <T>* @return*/public static <T> Page<T> query(MongoTemplate template, Class<T> clz, Query query, Pageable pageable){Long total = template.count(query, clz);if(total == 0){return Page.empty();}query.with(pageable);List<T> list = template.find(query, clz);return new PageImpl(list, pageable, total);}}
编辑的回显:
编辑按钮绑定了点击事件,当按钮被点击,事件源的json数据var data = $(this).data("json");,因为实体类设计了getJson的方法,需要回显的位置直接通过json来获取到区域对象中的属性数据。
关联的目的地:当点击地区的时候,发送异步请求get,带着当前区域的id到后台查询到区域挂载的目的地,返回挂载的目的地,前台拿到数据显示出来即可;
设置热点:
热点是两个常量0和1来表示。改变热点就是将当前区域的ishot值改变。两种方案:先查再改再保存效率低;直接更新;
@Overridepublic void changeHotValue(String id, int hot) {//修改对象的hot状态值:先查在改效率低//直接updateQuery query = new Query();query.addCriteria(Criteria.where("_id").is(id));Update update = new Update();update.set("ishot", hot);//更新template.updateMulti(query, update, Region.class);}
删除区域:
点击删除,data-url="/region/delete?id=${entity.id}"超链接发出请求,执行删除的方法,这里是直接将区域删除而不是改变区域的状态;
删除区域:一般不删除,改变状态为非正常即可,因为很多数据相关联,删除会出问题的。
目的地管理:
目的地的增删改查,观察前台发起什么请求,需要后台返回什么数据。
吐司位置导航:

根据表中的字段parentId的值来查:(写SQL语句,实际上是MQL语句)
第一位置:
select * from destination where parentId = null第二位置:
select * from destination where parentId = 中国id第三位置:
select * from destination where parentId = 广东id第四位置:
select * from destination where parentId = 广州id
当前位置:<a href="javascript:;" data-parentid="" class="clickBtn">根</a><>> <a href="javascript:;" data-parentid="${t.id}" class="clickBtn">${t.name}</a></
分页查询的条件需要包括parentId,所以在DestinationQuery中添加查询条件。
前台list除了分页之外,还需要加个吐司查出来共享到前台遍历显示;
//toasts: 导航(面包屑)model.addAttribute("toasts", destinationService.getToasts(qo.getParentId()));
查询吐司的实现:
查到当前的一个,将当前的老爸的老爸都查出来,直到parentId = null结束。将查到的数据装到list中共享:
查询的方法有两种:
while循环:效率更高
//查询吐司@Overridepublic List<Destination> getToasts(String parentId) {if (!StringUtils.hasLength(parentId)) {return Collections.emptyList();//返回空的list集合}ArrayList<Destination> list = new ArrayList<>();//查询当前目的地:方式1:递归的方式查询//createToast(list, parentId);//方式2:while循环查询while (true) {//结束条件:parentId为null是表示目的地已经为根if (parentId == null) {break;} else {Optional<Destination> optional = repository.findById(parentId);list.add(optional.get());//将刚刚查到的父级目的地的parentId覆盖原来的parentId,这样才可能满足结束条件parentId = optional.get().getParentId();}}Collections.reverse(list);//反转list集合return list;}方法的递归:
//创建toast//跟>>中国>>广东>>广州>>天河区private void createToast(ArrayList<Destination> list, String parentId) {Optional<Destination> optional = repository.findById(parentId);//通过当前目的地带过来的parentId查到的就是上一级的目的地名//如果parentId差不到值就表示为根if (!optional.isPresent()) {return;}list.add(optional.get());//如果还有上一级,就调用自己继续查if (StringUtils.hasLength(optional.get().getParentId())) {createToast(list, optional.get().getParentId());}}
使用分页工具来分页,发现DBHelper类的query方法中需要pageable对象,每次调用之前都需要创建pageable对象,太麻烦了,将pageable对象抽到QueryObject中,这样需要pageable对象的时候直接qo.getPageable()即可;
//分页public Page<Destination> query(DestinationQuery qo) {Query query = new Query();//面包屑,导航查询if (StringUtils.hasLength(qo.getParentId())) {query.addCriteria(Criteria.where("parentId").is(qo.getParentId()));} else {//没有值就查根的数据query.addCriteria(Criteria.where("parentId").is(null));}//高级查询的条件if (StringUtils.hasLength(qo.getKeyword())) {query.addCriteria(Criteria.where("name").regex(".*" + qo.getKeyword() + ".*"));}//PageRequest pageable = PageRequest.of(qo.getCurrentPage() - 1, qo.getPageSize(), Sort.Direction.ASC, "_id");//return DBHelper.query(template, Destination.class, query, pageable);return DBHelper.query(template, Destination.class, query, qo.getPageable());}
目的地的删除:
数据不可以删除,因为数据间是有关联的,不能直接删除,将状态改变(软删除);封号不是删除,而是禁用。
攻略管理:
攻略就是一篇文章,在前端将文章展示出来;
关系:目的地下有多个攻略分类,每个攻略分类有多篇文章,每个攻略对应一个主题(可以一对多)
攻略表的设计:strategy

攻略分类表的设计:strategy_catalog
关联目的地;

攻略主题表的设计:strategy_comment

攻略、攻略分类、攻略主题相关的CRUD
精髓:复制、粘贴、修改对。
攻略编辑:
封面图片上传:
前台使用uploadfive插件;
图片存放的位置:阿里云对象存储OSS服务器,将图片存储在公网上通过url定位符访问
将文件上传到到我自己开辟的buket空间中:看看帮助文档API;
使用阿里云,需要导入依赖:
文件上传的工具类:根据实际的情况修改ALI_DOMAIN,accessKeyId,accessKeySecret
/*** 文件上传工具*/public class UploadUtil {//阿里域名public static final String ALI_DOMAIN = "https://myluowowo.oss-cn-shenzhen.aliyuncs.com/";public static String uploadAli(MultipartFile file) throws Exception {//生成文件名称String uuid = UUID.randomUUID().toString();String orgFileName = file.getOriginalFilename();//获取真实文件名称 xxx.jpgString ext = "." + FilenameUtils.getExtension(orgFileName);//获取拓展名字.jpgString fileName = uuid + ext;//xxxxxsfsasa.jpg// Endpoint以杭州为例,其它Region请按实际情况填写。String endpoint = "http://oss-cn-shenzhen.aliyuncs.com";// 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,// 请登录 https://ram.console.aliyun.com 创建。String accessKeyId = "LTAI4GHWCAmcART4abdQrxhT";String accessKeySecret = "2GiiIyDMuYouF62mMqTZHZKIXictVw";// 创建OSSClient实例。OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);// 上传文件流。ossClient.putObject("myluowowo", fileName, file.getInputStream());// 关闭OSSClient。ossClient.shutdown();return ALI_DOMAIN + fileName;}}
controller:
//封面图片上传("/uploadImg")public Object uploadImg(MultipartFile pic) {//将pic数据存储到项目文件中//通过oss提供的api将图片数据上传到阿里云的oss服务器中String url = null;try {url = UploadUtil.uploadAli(pic);} catch (Exception e) {e.printStackTrace();}return url;}
攻略分类的层级显示:

攻略分内中有目的地id的字段,目的地是第一层 ,先找到目的地,在找到目的地下的分类;
如何操作:
方式一:
步骤1:通过destName对分类集合进行分组操作,得到一个目的地集合;

步骤2:遍历步骤1中目的地的地名,作为条件查询攻略分类数据;

不能使用addToSet,因为set是无序的,id和name对应不起来。使用push:

步骤3:数据结构的构建
<select class="form-control" id="catalog" name="catalogId"><option value="-1">--请选择--</option><#list catalogs as c ><optgroup label="${c.destName}"><#list c.mapList as ml><option value="${ml.id}">${ml.name}</option></#list></optgroup></#list></select>input 方法:
public String input(Model model, String id) {if (StringUtils.hasLength(id)) {//编辑//strategymodel.addAttribute("strategy", strategyService.get(id));}//catalogsmodel.addAttribute("catalogs",strategyCatalogService.groupList());//themes :查询所有主题共享model.addAttribute("themes", strategyThemeService.list());return "strategy/input";}groupList方法:
//拼数据结构:用于显示攻略分类的层次级别@Overridepublic List<CatalogVO> groupList() {List<CatalogVO> list = new ArrayList<>();TypedAggregation<StrategyCatalog> agg = Aggregation.newAggregation(StrategyCatalog.class,Aggregation.group("destName").push("name").as("names").push("id").as("ids")//addToSet("name").as("names").//使用addToSet,无序的,name和id对不上,所以使用push//addToSet("id").as("ids"));AggregationResults<Map> result = template.aggregate(agg, Map.class);List<Map> datas = result.getMappedResults();for (Map data : datas) {CatalogVO vo = new CatalogVO();vo.setDestName(data.get("_id").toString());List<Map<String, Object>> mm = new ArrayList<>();List<Object> names = (List<Object>) data.get("names");List<Object> ids = (List<Object>) data.get("ids");for (int i = 0; i < names.size(); i++) {Map<String, Object> vv = new HashMap<>();String name = names.get(i).toString();String id = ids.get(i).toString();vv.put("id", id);vv.put("name", name);mm.add(vv);}vo.setMapList(mm);list.add(vo);}System.out.println(list);return list;}CatalogVO类:数据结构:
List<Map<String,Object>>public class CatalogVO {private String destName;private List<Map<String,Object>> mapList = new ArrayList<>();}
富文本编辑框:
ckeditor
插件规定发出什么样的请求参数,响应什么样的参数。我们的controller要按要求对数据进行封装来返回参数;
读API文档:发现后台传过去的参数,要能够成功的解析(需要键值对),我们得利用map来将数据封装后传到前台:
图片上传的controller:
调用了图片上传的工具类UploadUtil的方法将图片上传带阿里云:
public class UploadController {//封面图片上传("/uploadImg")public Object uploadImg(MultipartFile pic) {//将pic数据存储到项目文件中//通过oss提供的api将图片数据上传到阿里云的oss服务器中String url = null;try {url = UploadUtil.uploadAli(pic);} catch (Exception e) {e.printStackTrace();}return url;}////https://myluowowo.oss-cn-shenzhen.aliyuncs.com/d4730640-df49-4ad0-8b76-66c3ced52538.jpeg//http://localhost:9999/strategy/myluowowo.oss-cn-shenzhen.aliyuncs.com/7eba0410-33c9-401f-a21e-80939d3d420b.jpg//处理富文本编辑框中上传图片("/uploadImg_ck")public Map<String, Object> upload(MultipartFile upload, String module){Map<String, Object> map = new HashMap<String, Object>();String imagePath= null;if(upload != null && upload.getSize() > 0){try {//图片保存, 返回路径imagePath = UploadUtil.uploadAli(upload);//将文件传给上传工具,保存到OSS中,返回文件的url//表示保存成功map.put("uploaded", 1);map.put("url",imagePath);}catch (Exception e){e.printStackTrace();map.put("uploaded", 0);Map<String, Object> mm = new HashMap<String, Object>();mm.put("message",e.getMessage() );map.put("error", mm);}}return map;}}
攻略保存和编辑:
将页面传来的数据保存到数据库中;注意维护没有给的数据;
不能直接save,还需要看看用户没有给的数据有没有设置好了,比如创建时间。
编辑注意:修改后有改变的值都要重新设置,容易漏掉数据,对比哪些需要更新。
trip-website前端热门目的地:

目的地的显示:
显示的是区域关联的目的地:
国内(前端传的regionId是-1)在数据库中不存在的,要做额外的处理:
实现类要将-1(查询所有国内的省份)和有regionId的情况区分开;
还需要将每个目的地下的子目的地显示出来;显示的目的地不用太多,显示五个(分页的方式做限制);
//查询指定区域下挂载的目的地@Overridepublic List<Destination> getDestByRegionIdForApi(String regionId) {//第一层:查询区域下挂载的子目的地List<Destination> list = null;if ("-1".equals(regionId)){//国内的regionId为-1list = repository.findByParentName("中国");}else {//查询挂载的目的地的id集合Region region = regionService.get(regionId);List<String> refIds = region.getRefIds();//查询关联的目的地集合中的所有目的地list = repository.findByIdIn(refIds);}//第二层:查询关联目的地下的字目的地//直接关联查询for (Destination destination : list) {//目的地下的子目的地太多,用JPA的方式让他只显示一部分PageRequest of = PageRequest.of(0, 5);//以父目的地的name做为条件查询子目的地List<Destination> children = repository.findByParentId(destination.getId(),of);destination.setChildren(children);}return list;}
返回参数给前台的优化:
返回参数的时候,每次都创建map,然后往map中put前台需要的数据,很麻烦。设计一个工具类来简化这种操作:
/*** 参数映射类:专门用来将数据包装成map响应给后台*/public class ParamMap extends HashMap<String, Object> {//增加一个自己的put方法,用链式编程简化操作public ParamMap put(String key, Object value) {super.put(key, value);return this;//返回this的方法可以使用链式编程}}
这样可以通过new ParamMap().put("list", list)的方法完成数据的封装,链式编程很方便.
目的地的明细:
目的地明细是一个综合页面, 以某个目的地为条件, 将关联的游记,攻略,景点,路线,跟团游,周边等等数据统统查询出来一并显示
吐司导航:

目的地下的概况:
查询当前目的地下的所有攻略分类,同时关联查询各个分类下的所有攻略;
还需查攻略分类下所有的攻略文章,这就是为什么实体类中StrategyCatalog会有一个瞬态的字段List<Strategy> list的原因;
数据量大的时候要用JPA的limit查一部分数据

//点击目的地进入详情页面("/detail")public Object detail(String id) {//toasts:[],List<Destination> toasts = destinationService.getToasts(id);// dest:{},//Destination dest = toasts.get(toasts.size() - 1);//使用get方法面包屑多了一个重复的子目的地Destination dest = toasts.remove(toasts.size() - 1);//catalogs:[],List<StrategyCatalog> catalogs = catalogService.queryByDestId(id);//strategies:[],//指定目的地下的排名前三的攻略//page:{}StrategyQuery qo = new StrategyQuery();qo.setDestId(id);//分页排序qo.setPageable(PageRequest.of(0, 3, Sort.Direction.DESC,"viewnum"));Page<Strategy> page = strategyService.query(qo);return JsonResult.success(new ParamMap().put("toasts", toasts).put("dest", dest).put("catalogs", catalogs).put("strategies", page.getContent())//strategies只需要分页结果的内容在前台展示即可);}
概况下攻略分类的超链接:

上面有吐司,左边有分类列表,右边当前分类,分类下是 | 隔开的所有攻略,展示第一篇攻略的标题和内容。
吐司导航:
//显示地址导航(吐司)("/getToast")public Object getToast(String id) {//vue.toasts = map.toasts;List<Destination> toasts = destinationService.getToasts(id);// vue.dest = map.dest;Destination dest = toasts.remove(toasts.size() - 1);return JsonResult.success(new ParamMap().put("toasts", toasts).put("dest", dest));}
左边攻略分类的显示:
//通过目的地的id查询攻略的分类@Overridepublic List<StrategyCatalog> queryByDestId(String destId) {List<StrategyCatalog> list = repository.findByDestId(destId);//攻略分类下的所有的攻略文章,只显示3条:limit 0, 3for (StrategyCatalog catalog : list) {List<Strategy> strategies = strategyService.getByCatalogId(catalog.getId());catalog.setStrategies(strategies);}return list;}
攻略明细页面展示排名前三的攻略:

//strategies:[],//指定目的地下的排名前三的攻略//page:{}StrategyQuery qo = new StrategyQuery();qo.setDestId(id);//分页排序qo.setPageable(PageRequest.of(0, 3, Sort.Direction.DESC,"viewnum"));Page<Strategy> page = strategyService.query(qo);
public Page<Strategy> query(StrategyQuery qo) {Query query = new Query();//通过目的地的 id 方式查询分页if (StringUtils.hasLength(qo.getDestId())) {query.addCriteria(Criteria.where("destId").is(qo.getDestId()));}//通过主题Id查询分页if (StringUtils.hasLength(qo.getThemeId())) {query.addCriteria(Criteria.where("themeId").is(qo.getThemeId()));}return DBHelper.query(template, Strategy.class, query, qo.getPageable());}
查看攻略详情:

统计数据的实现,之后导读说,这里主要是攻略;
//查看攻略详情("/detail")public Object detail(String id, UserInfo userInfo) {//strategyStrategy strategy = strategyService.get(id);//评论的点赞要分页StrategyCommentQuery qo = new StrategyCommentQuery();qo.setStrategyId(id);Page<StrategyComment> page = strategyCommentService.query(qo);//访问一次,阅读量就增加一次strategyStatiesVORedisService.viewnumIncrease(id, 1);//收藏状态的回显 sids 区分开登录和未登录的情况: 因为这个方法没有必须要登录,所以可能有两种情况List<String> list = Collections.emptyList();//空list集合if (userInfo != null) {//登录用户:通过用户的额id去Redis中拿到vo中的sidslist = strategyStatiesVORedisService.getSids(userInfo.getId());}//因为这个方法没有必须要登录才可以操作,所以,获取sids的情况分为登录和没登录两种List<String> sids = Collections.emptyList();//没有登录sids就是空的if (userInfo != null){sids = strategyStatiesVORedisService.getSids(userInfo.getId());}return JsonResult.success(new ParamMap().put("strategy", strategy).put("page", page).put("sids", list).put("vo", strategyStatiesVORedisService.getStrategyStatisVo(id)).put("sids", sids));}
攻略列表页面:
点击查看全部,进入攻略列表页面:(/views/strategy/list.html?destId=目的地id)

默认的查询条件是广州下的所有攻略:


前端要求必须要分页,我们参数要用qo来将destId封装
//查看攻略列表@GetMapping("/list")public Object list(StrategyQuery qo) {//vue.page = map.page; //分页对象Page<Strategy> page = strategyService.query(qo);//vue.themes = map.themes; //主题List<StrategyTheme> themes = themeService.list();//vue.toasts = map.toasts; //吐司List<Destination> toasts = destinationService.getToasts(qo.getDestId());return JsonResult.success(new ParamMap().put("page", page).put("themes", themes).put("toasts", toasts));}
主题下的所有攻略:
点击主题将该主题下的所有攻略查询展示出来;查询条件是当前主题和当前目的地;

//查看某个主题下的所有攻略public Object query(StrategyQuery qo) {//pagePage<Strategy> page = strategyService.query(qo);return JsonResult.success(page);}
小伙砸,欢迎再看分享给其他小伙伴!共同进步!
本文分享自微信公众号 - java学途(javaxty)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
来源:oschina
链接:https://my.oschina.net/u/4673060/blog/4679301


