关于注解式的分布式Elasticsearch的封装案例

原生的Rest Level Client不好用,构建检索等很多重复操作。

对bboss-elasticsearch进行了部分增强:通过注解配合实体类进行自动构建索引和自动刷入文档,复杂的业务检索需要自己在xml中写Dsl。用法与mybatis-plus如出一辙。

依赖

<dependency>
			<groupId>org.elasticsearch</groupId>
			<artifactId>elasticsearch</artifactId>
		</dependency>
		<dependency>
			<groupId>com.bbossgroups.plugins</groupId>
			<artifactId>bboss-elasticsearch-spring-boot-starter</artifactId>
			<version>5.9.5</version>
			<exclusions>
				<exclusion>
					<artifactId>slf4j-log4j12</artifactId>
					<groupId>org.slf4j</groupId>
				</exclusion>
			</exclusions>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.6</version>
			<scope>provided</scope>
		</dependency>

配置:


import com.rz.config.ElsConfig;
import org.frameworkset.elasticsearch.boot.ElasticSearchBoot;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;

/**
 * 启动时初始化bBoss
 *
 * @author sunziwen
 * @version 1.0
 * @date 2019/12/12 16:54
 **/
@Component
@Order(value = 1)
public class StartElastic implements ApplicationRunner {
 @Autowired
 private ElsConfig config;

 @Override
 public void run(ApplicationArguments args) throws Exception {
 Map properties = new HashMap();
 properties.put("elasticsearch.rest.hostNames", config.getElsClusterNodes());
 ElasticSearchBoot.boot(properties);
 }
}

注解和枚举:

package com.rz.szwes.annotations;

import java.lang.annotation.*;

/**
 * 标识实体对应的索引信息
 *
 * @author sunziwen
 * 2019/12/13 10:14
 * @version 1.0
 **/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ESDsl {
 /**
 * xml的位置
 */
 String value();

 String indexName();

 /**
 * elasticsearch7.x版本已经删除该属性
 */
 String indexType() default "";
}
package com.rz.szwes.annotations;

import java.lang.annotation.*;

/**
 * 为字段指定映射类型
 *
 * @author sunziwen
 * 2019/12/14 10:06
 * @version 1.0
 **/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ESMapping {
 //映射类型
 ESMappingType value();

 //加权
 int boost() default 1;

 //分词标识analyzed、not_analyzed
 String index() default "analyzed";

 //分词器ik_max_word、standard
 String analyzer() default "ik_max_word";

 //String作为分组聚合字段的时候需要设置为true
 boolean fildData() default false;
}
package com.rz.szwes.annotations;

/**
 * Es映射类型枚举(定义了大部分,有缺失请使用者补全)当前版本基于elasticsearch 6.8
 *
 * @author sunziwen
 * 2019/12/14 10:09
 * @version 1.0
 **/
public enum ESMappingType {
 /**
 * 全文搜索。
 */
 text("text"),
 /**
 * keyword类型适用于索引结构化(排序、过滤、聚合),只能通过精确值搜索到。
 */
 keyword("keyword"),

 /

 /**
 * -128~127 在满足需求的情况下,尽可能选择范围小的数据类型。
 */
 _byte("byte"),
 /**
 * -32768~32767
 */
 _short("short"),
 /**
 * -2^31~2^31-1
 */
 _integer("integer"),
 /**
 * -2^63~2^63-1
 */
 _long("long"),

 /
 /**
 * 64位双精度IEEE 754浮点类型
 */
 _doule("doule"),
 /**
 * 32位单精度IEEE 754浮点类型
 */
 _float("float"),
 /**
 * 16位半精度IEEE 754浮点类型
 */
 half_float("half_float"),
 /**
 * 缩放类型的的浮点数
 */
 scaled_float("scaled_float"),

 /
 /**
 * 时间类型
 */
 date("date"),

 _boolean("boolean"),
 /**
 * 范围类型
 */
 range("range"),
 /**
 * 嵌套类型
 */
 nested("nested"),
 /**
 * 地理坐标
 */
 geo_point("geo_point"),
 /**
 * 地理地图
 */
 geo_shape("geo_shape"),
 /**
 * 二进制类型
 */
 binary("binary"),
 /**
 * ip 192.168.1.2
 */
 ip("ip");

 private String value;

 ESMappingType(String value) {
 this.value = value;
 }

 public String getValue() {
 return value;
 }
}

工具类:对HashMap进行了增强

package com.rz.szwes.util;
import java.util.HashMap;
import java.util.function.Supplier;

/**
 * 原始HashMap不支持Lambda表达式,特此包装一个
 *
 * @author sunziwen
 * @version 1.0
 * @date 2019/12/13 11:09
 **/
public class LambdaHashMap<K, V> extends HashMap<K, V> {
 public static <K, V> LambdaHashMap<K, V> builder() {
 return new LambdaHashMap<>();
 }

 public LambdaHashMap<K, V> put(K key, Supplier<V> supplier) {
 super.put(key, supplier.get());
 //流式
 return this;
 }
}

核心类两个:

package com.rz.szwes.core;
import cn.hutool.core.util.ClassUtil;
import com.alibaba.fastjson.JSON;
import com.frameworkset.orm.annotation.ESId;
import com.rz.szwes.annotations.ESDsl;
import com.rz.szwes.annotations.ESMapping;
import com.rz.szwes.util.LambdaHashMap;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;

/**
 * 抽象类解析泛型
 *
 * @author sunziwen
 * 2019/12/14 16:04
 * @version 1.0
 **/
public abstract class AbstractElasticBase<T> {
 { //初始化解析
 ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
 // 获取第一个类型参数的真实类型
 Class<T> clazz = (Class<T>) pt.getActualTypeArguments()[0];
 parseMapping(clazz);
 }

 /**
 * 索引名称
 */
 protected String indexName;
 /**
 * 索引类型
 */
 protected String indexType;
 /**
 * es写dsl的文件路径
 */
 protected String xmlPath;
 /**
 * 索引映射
 */
 protected String mapping;

 //将Class解析成映射JSONString
 private void parseMapping(Class<T> clazz) {
 if (clazz.isAnnotationPresent(ESDsl.class)) {
  ESDsl esDsl = clazz.getAnnotation(ESDsl.class);
  this.xmlPath = esDsl.value();
  this.indexName = esDsl.indexName();
  //如果类型为空,则采用索引名作为其类型
  this.indexType = StringUtils.isEmpty(esDsl.indexType()) ? esDsl.indexName() : esDsl.indexType();
 } else {
  throw new RuntimeException(clazz.getName() + "缺失注解[@ESDsl]");
 }
 //构建索引映射
 LambdaHashMap<Object, Object> put = LambdaHashMap.builder()
        .put("mappings", () -> LambdaHashMap.builder()
            .put(indexType, () -> LambdaHashMap.builder()
                .put("properties", () -> {
                 Field[] fields = clazz.getDeclaredFields();
                 LambdaHashMap<Object, Object> builder = LambdaHashMap.builder();
                 for (Field field : fields) {
                 builder.put(field.getName(), () -> toEsjson(field));
                 }
                 return builder;
                })))
 ;
 this.mapping = JSON.toJSONString(put);
 }

 private LambdaHashMap<Object, Object> toEsjson(Field field) {
 //基本数据类型
 if (ClassUtil.isSimpleTypeOrArray(field.getType())) {
  //对字符串做大小限制、分词设置
  if (new ArrayList<Class>(Collections.singletonList(String.class)).contains(field.getType())) {
  LambdaHashMap<Object, Object> put = LambdaHashMap.builder()
         .put("type", () -> "text")
         .put("fields", () -> LambdaHashMap.builder()
             .put("keyword", () -> LambdaHashMap.builder()
                 .put("type", () -> "keyword")
                 .put("ignore_above", () -> 256)));
  if (field.isAnnotationPresent(ESMapping.class)) {
   ESMapping esMapping = field.getAnnotation(ESMapping.class);
   //设置聚合分组
   if (esMapping.fildData()) {
   put.put("fildData", () -> true);
   }
   //设置加权
   if (esMapping.boost() != 1) {
   put.put("boost", esMapping::boost);
   }
   //设置是否进行分词
   if (!"analyzed".equals(esMapping.index())) {
   put.put("analyzed", esMapping::analyzer);
   }
   //分词器
   put.put("analyzer", esMapping::analyzer);
  }
  return put;
  }
  //设置默认类型
  return LambdaHashMap.builder().put("type", () -> {
  if (field.isAnnotationPresent(ESMapping.class)) {
   ESMapping esMapping = field.getAnnotation(ESMapping.class);
   return esMapping.value().getValue();
  }
  if (new ArrayList<Class>(Arrays.asList(byte.class, Byte.class, short.class, Short.class, int.class, Integer.class, long.class, Long.class)).contains(field.getType())) {
   return "long";
  } else if (new ArrayList<Class>(Arrays.asList(double.class, Double.class, float.class, Float.class)).contains(field.getType())) {
   return "double";
  } else if (new ArrayList<Class>(Arrays.asList(Date.class, java.sql.Date.class, LocalDate.class, LocalDateTime.class, LocalTime.class)).contains(field.getType())) {
   return "date";
  } else if (new ArrayList<Class>(Arrays.asList(boolean.class, Boolean.class)).contains(field.getType())) {
   return "boolean";
  }
  return "text";
  });
 } else {
  //设置对象类型
  LambdaHashMap<Object, Object> properties = LambdaHashMap.builder()
         .put("properties", () -> {
         Field[] fields = field.getType().getDeclaredFields();
         LambdaHashMap<Object, Object> builder = LambdaHashMap.builder();
         for (Field field01 : fields) {
          builder.put(field01.getName(), toEsjson(field01));
         }
         return builder;
         });
  if (field.isAnnotationPresent(ESMapping.class)) {
  ESMapping esMapping = field.getAnnotation(ESMapping.class);
  properties.put("type", esMapping.value().getValue());
  }
  return properties;
 }
 }
}
package com.rz.szwes.core;
import lombok.extern.slf4j.Slf4j;
import org.frameworkset.elasticsearch.boot.BBossESStarter;
import org.frameworkset.elasticsearch.client.ClientInterface;
import org.frameworkset.elasticsearch.client.ClientUtil;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.*;

/**
 * Elastic基础函数
 *
 * @author sunziwen
 * @version 1.0
 * @date 2019/12/13 9:56
 **/
@Slf4j
public class ElasticBaseService<T> extends AbstractElasticBase<T> {

 @Autowired
 private BBossESStarter starter;

 /**
 * Xml创建索引
 */
 protected String createIndexByXml(String xmlName) {
 ClientInterface restClient = starter.getConfigRestClient(xmlPath);
 boolean existIndice = restClient.existIndice(this.indexName);
 if (existIndice) {
  restClient.dropIndice(indexName);
 }
 return restClient.createIndiceMapping(indexName, xmlName);
 }

 /**
 * 自动创建索引
 */
 protected String createIndex() {
 ClientInterface restClient = starter.getRestClient();
 boolean existIndice = restClient.existIndice(this.indexName);
 if (existIndice) {
  restClient.dropIndice(indexName);
 }
 log.debug("创建索引:" + this.mapping);
 return restClient.executeHttp(indexName, this.mapping, ClientUtil.HTTP_PUT);
 }

 /**
 * 删除索引
 */
 protected String delIndex() {
 return starter.getRestClient().dropIndice(this.indexName);
 }

 /**
 * 添加文档
 *
 * @param t 实体类
 * @param refresh 是否强制刷新
 */
 protected String addDocument(T t, Boolean refresh) {
 return starter.getRestClient().addDocument(indexName, indexType, t, "refresh=" + refresh);
 }

 /**
 * 添加文档
 *
 * @param ts 实体类集合
 * @param refresh 是否强制刷新
 */
 protected String addDocuments(List<T> ts, Boolean refresh) {
 return starter.getRestClient().addDocuments(indexName, indexType, ts, "refresh=" + refresh);
 }

 /**
 * 分页-添加文档集合
 *
 * @param ts 实体类集合
 * @param refresh 是否强制刷新
 */
 protected void addDocumentsOfPage(List<T> ts, Boolean refresh) {
 this.delIndex();
 this.createIndex();
 int start = 0;
 int rows = 100;
 Integer size;
 do {
  List<T> list = pageDate(start, rows);
  if (list.size() > 0) {
  //批量同步信息
  starter.getRestClient().addDocuments(indexName, indexType, ts, "refresh=" + refresh);
  }
  size = list.size();
  start += size;
 } while (size > 0);
 }

 /**
 * 使用分页添加文档必须重写该类
 *
 * @param start 起始
 * @param rows 项数
 * @return
 */
 protected List<T> pageDate(int start, int rows) {
 return null;
 }

 /**
 * 删除文档
 *
 * @param id id
 * @param refresh 是否强制刷新
 * @return
 */
 protected String delDocument(String id, Boolean refresh) {
 return starter.getRestClient().deleteDocument(indexName, indexType, id, "refresh=" + refresh);
 }

 /**
 * 删除文档
 *
 * @param ids id集合
 * @param refresh 是否强制刷新
 * @return
 */
 protected String delDocuments(String[] ids, Boolean refresh) {
 return starter.getRestClient().deleteDocumentsWithrefreshOption(indexName, indexType, "refresh=" + refresh, ids);
 }

 /**
 * id获取文档
 *
 * @param id
 * @return
 */
 protected T getDocument(String id, Class<T> clazz) {
 return starter.getRestClient().getDocument(indexName, indexType, id, clazz);
 }

 /**
 * id更新文档
 *
 * @param t 实体
 * @param refresh 是否强制刷新
 * @return
 */
 protected String updateDocument(String id, T t, Boolean refresh) {
 return starter.getRestClient().updateDocument(indexName, indexType, id, t, "refresh=" + refresh);
 }
}

写复杂Dsl的xml:(如何写Dsl请参考bBoss-elasticsearch文档,用法类似mybatis标签)

<properties>
</properties>

框架集成完毕,以下是使用示例:

定义数据模型:

package com.rz.dto;

import com.frameworkset.orm.annotation.ESId;
import com.rz.szwes.annotations.ESDsl;
import com.rz.szwes.annotations.ESMapping;
import com.rz.szwes.annotations.ESMappingType;
import lombok.Data;
import java.util.List;

/**
 * 对应elasticsearch服务器的数据模型
 *
 * @author sunziwen
 * @version 1.0
 * @date 2019/12/16 11:08
 **/
@ESDsl(value = "elasticsearch/zsInfo.xml", indexName = "zsInfo")
@Data
public class ElasticZsInfoDto {
 @ESMapping(ESMappingType._byte)
 private int least_hit;
 private int is_must_zz;
 private int zs_level;
 private int cur_zs_ct;
 private int least_score_yy;
 private int least_score_yw;
 private int area_id;
 private String coll_name;
 private String coll_code;
 private long coll_pro_id;
 private int is_must_wl;
 private int cur_year;
 private int is_two;
 private long logo;
 @ESId
 private int id;
 private String area;
 private int college_id;
 private String is_must_yy;
 private int is_double;
 private int least_score_zz;
 private int least_score_wl;
 private String grade;
 private int is_nine;
 private String pro_name;
 private int least_score_sx;
 private int relevanceSort;
 private int pre_avg;
 private String is_must_dl;
 private String profession_code;
 private int least_score_sw;
 private String is_must_ls;
 private int grade_zk;
 private int least_score_wy;
 private int is_must_hx;
 private int profession_id;
 private String is_grad;
 private String is_must_yw;
 private int is_must_sw;
 private int least_score_ls;
 private int least_score_dl;
 private String zs_memo;
 private String is_must_sx;
 private String introduce;
 private int is_must_wy;
 private int grade_bk;
 private String pre_name;
 private int least_score_hx;
 private String coll_domain;
 private int pre_wch;
 private List<String> courses;
}

定义服务

package com.rz.service;
import com.rz.dto.ElasticZsInfoDto;
import com.rz.szwes.core.ElasticBaseService; 

/**
 * 招生索引操作服务
 *
 * @author sunziwen
 * @version 1.0
 * @date 2019/12/16 11:02
 **/
public class ElasticZsInfoService extends ElasticBaseService<ElasticZsInfoDto> {
}

完毕。

已经可以进行索引和文档的crud操作了,至于复杂的检索操作就需要在xml中定义了。这里只介绍了我增强的功能,大部分功能都在bBoss中定义好了,读者可以去看bBoss文档(笔者认为的他的唯一缺陷是不能通过实体配合注解实现自动索引,还要每次手动指定xml位置,手动写mapping是很痛苦的事情,特此进行了增强)。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • Django利用elasticsearch(搜索引擎)实现搜索功能

     1.在Django配置搜索结果页的路由映射 """pachong URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.10/topics/http/urls/ Examples: Function views 1. Add an import: from my_ap

  • docker镜像访问本地elasticsearch端口操作

    使用docker stack部署的镜像服务,进入镜像之后,理论上,应该可以通过下面的指令访问本地的elasticsearch服务 curl 本机ip/9200 但是却提示拒绝访问. 后来本机使用上述指令查看之后发现,本机也是拒绝访问. 之后发现,9200端口的elasticsearch服务,是通过打洞的方式连接的远程服务器上的elasticsearch服务,只能通过下面指令访问端口 curl 127.0.0.1:9200 如果想要通过本机ip访问9200端口,则需要在打洞指令的后面加上 -g.

  • Elasticsearch使用常见问题解决方案

    一.和redis一起使用会造成netty启动冲突问题,所以需要在***Application入口文件中添加方法: @PostConstruct public void init() { // see Netty4Utils.setAvailableProcessors() System.setProperty("es.set.netty.runtime.available.processors", "false"); } 二.NoNodeAvailableExcep

  • docker 启动elasticsearch镜像,挂载目录后报错的解决

    从docker hub下载了一个es的镜像,版本为6.4.2,详细信息如下: 比较重要的就是这两条,第一个是工作目录,挂载目录也需要和这里对应:第二个是启动命令,这里是指定了一个预先写好的启动脚本.所以我启动了一个空容器去查看了下容器内的情况: 容器内部目录结构如上图,data是用来存放数据,logs用来存放日志. 接着查看下启动脚本 /usr/local/bin/docker-entrypoint.sh 前半部分我也是看的一知半解,不过真正和挂载目录相关的是最后这部分,这里处理了挂载目录后的操

  • 关于注解式的分布式Elasticsearch的封装案例

    原生的Rest Level Client不好用,构建检索等很多重复操作. 对bboss-elasticsearch进行了部分增强:通过注解配合实体类进行自动构建索引和自动刷入文档,复杂的业务检索需要自己在xml中写Dsl.用法与mybatis-plus如出一辙. 依赖 <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> &

  • Spring使用AspectJ的注解式实现AOP面向切面编程

    1.认识Spring AOP 1.1 AOP的简介 AOP:面向切面编程,相对于OOP面向对象编程. Spring的AOP的存在目的是为了解耦.AOP可以让一组类共享相同的行为.在OOP中只能通过继承类和实现接口,来使代码的耦合度增强,而且类的继承只能为单继承,阻碍更多行为添加到一组类上,AOP弥补了OOP的不足. 1.2 AOP中的概念 切入点(pointcut): 切入点(pointcut):在哪些类.哪些方法上切入. 通知(advice):在方法前.方法后.方法前后做什么. 切面(aspe

  • SpringMVC实现注解式权限验证的实例

    对大部分系统来说都需要权限管理来决定不同用户可以看到哪些内容,那么如何在Spring MVC中实现权限验证呢?当然我们可以继续使用servlet中的过滤器Filter来实现.但借助于Spring MVC中的action拦截器我们可以实现注解式的权限验证. 一.首先介绍一下action拦截器: HandlerInterceptor是Spring MVC为我们提供的拦截器接口,来让我们实现自己的处理逻辑,HandlerInterceptor 的内容如下: public interface Handl

  • Spring MVC注解式开发使用详解

    MVC注解式开发即处理器基于注解的类开发, 对于每一个定义的处理器, 无需在xml中注册. 只需在代码中通过对类与方法的注解, 即可完成注册. 定义处理器 @Controller: 当前类为处理器 @RequestMapping: 当前方法为处理器方法, 方法名随意, 对于请求进行处理与响应. @Controller public class MyController { @RequestMapping(value = "/hello.do") public ModelAndView

  • Spring AOP如何实现注解式的Mybatis多数据源切换详解

    一.为什么要使用多数据源切换? 多数据源切换是为了满足什么业务场景?正常情况下,一个微服务或者说一个WEB项目,在使用Mybatis作为数据库链接和操作框架的情况下通常只需要构建一个系统库,在该系统库创建业务表来满足需求,当然也有分为测试库和正式库dev/prod,不过这俩库的切换是使用配置文件进行切分的,在项目启动时或者打成maven JAR包指定environment-dev.properties或者environment-prod.properties. 那么当程序运行过程中,比如一个co

  • Spring boot通过切面,实现超灵活的注解式数据校验过程

    目录 通过切面,实现超灵活的注解式数据校验 Spring MVC的校验方式 通过切面实现自己的注解式数据校验 Spring boot aop注解数据权限校验 注解类 AOP切面 使用 通过切面,实现超灵活的注解式数据校验 在企业系统的开发中,用户表单输入的场景是会经常遇见的,如何让数据校验脱离于业务代码逻辑,谁也不想在逻辑代码里对字段逐一判断.... Spring MVC的校验方式 在使用Spring MVC时的时候,直接使用hibernate-validator的注解,如下: public c

  • 使用自定义注解实现redisson分布式锁

    目录 自定义注解实现redisson分布式锁 自定义注解 aop解析注解 service中使用注解加锁使用 redisson分布式锁应用 应用场景 Redisson管理类 分布式锁 测试类 自定义注解实现redisson分布式锁 自定义注解 package com.example.demo.annotation; import java.lang.annotation.*; /** * desc: 自定义 redisson 分布式锁注解 * * @author: 邢阳 * @mail: xyde

  • 自定义注解+Spel实现分布式锁方式

    目录 自定义注解+Spel实现分布式锁 依赖 RedisLockRegistryConfig 自定义注解 自定义切面 测试类 执行结果 基于注解的方式实现分布式锁 redis分布式锁的实现 测试 自定义注解+Spel实现分布式锁 依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xs

  • Java Spring框架的注解式开发你了解吗

    目录 1.Spring框架的注解式开发 1.1开启注解扫描配置 1.2实例化相关注解 1.3控制对象的创建次数的注解 1.4注入相关的注解 1.5控制事务的相关注解 总结 1. Spring框架的注解式开发 # Spring框架的注解式(Annotation)开发 1. 注解式开发 定义:通过Spring框架提供的一系列注解来完成项目中快速开发 注解:Annotation是java中一种特殊的类 类似于interface 使用时:@注解类名(属性=参数) @Param(Mybatis中做参数绑定

  • Vue axios获取token临时令牌封装案例

    前言 为什么非要写这个博客呢?因为这件事让我有一种蛋蛋的优疼.剩下的都别问,反正问我也不会说.因为流程图我都不想(懒得)画. 开发架构 前端页面:Vue 网络请求:Axios;方式:vue add axios 缓存方案 全局变量:Vuex 本地缓存:LocalStorage 技术依赖 你猜? 背景 公司开发一个嵌入App的Web页面,安全方面使用老套路:App通过URL传参给前端(包含签名),前端把参数透传给H5后端验签,完事儿之后前端再决定用户是否合法.另外定义了N个JS方法前端根据固定GET

随机推荐