首頁 > 軟體

解析Springboot整合Tile38使用者端之Set命令實現範例

2022-08-20 18:00:19

set命令語法

SET key id [FIELD name value ...] [EX seconds] [NX|XX] (OBJECT geojson)|(POINT lat lon z)|(BOUNDS minlat minlon maxlat maxlon)|(HASH geohash)|(STRING value)

set命令就相當於redis中的hash命令的使用,也是一個keyid的組合,但是不同的是,Tile38的set命令還可以攜帶更多的其他屬性,比如可以自定義FIELD欄位,還可以設定EX有效期等等,那麼我們需要給這個語法設計一套好用的java api,以便開發人員可以更好地使用Tile38。

語法分析

首先,根據上面提供的語法,我們可以分為三部分:

1.第一部分就是命令的啟示關鍵字SET,我們把這個關鍵字單獨作為一部分;

2.第二部分就是key id [FIELD name value ...] [EX seconds] [NX|XX],我們把這些都作為引數;

3.第三部分就是最後的目標資料物件:

(OBJECT geojson)|(POINT lat lon z)|(BOUNDS minlat minlon maxlat maxlon)|(HASH geohash)|(STRING value)

程式碼設計

1.我們把第一部分的命令關鍵字通過列舉的方式來管理:

enum Tile38Command implements ProtocolKeyword {
    SET;
    public final byte[] bytes;
    static final String UNDERSCORE = "_";
    static final String SPACE = " ";
    Tile38Command() {
      String name = StringUtils.replace(this.name(), UNDERSCORE, SPACE);
      this.bytes = name.getBytes(StandardCharsets.US_ASCII);
    }
    @Override
    public byte[] getBytes() {
      return this.bytes;
    }
}

因為redis使用者端工具在傳送命令前需要對所有命令進行編碼,所以要求所有的命令都必須實現ProtocolKeyword介面。如果命令的起始關鍵字是兩個或多個單詞,那麼我們會使用下劃線連線,轉換成bytes的時候我們可以使用空格把下劃線替換。

2.我們把命令的第二部分抽象成一個具體的class,通過相關的欄位來進行描述:

public class SetOpts {
  private String key;
  private String id;
  //欄位值必須是雙精度浮點型
  private Map<String, Double> fields;
  // 單位秒
  private int ex;
  // 建立方式:
  // NX 不存在的時候建立
  // XX 存在的時候更新
  private NxXx nxXx;
  private SetOpts(Builder builder) {
    this.key = builder.key;
    this.id = builder.id;
    this.fields = builder.fields;
    this.ex = builder.ex;
    this.nxXx = builder.nxXx;
  }
  
  // 把所有的引數按順序放到列表中
  public List<String> commandLine() {
    List<String> result = new LinkedList<>();
    result.add(this.key);
    result.add(this.id);
    // 新增所有的FIELD
    if (MapUtils.isNotEmpty(this.fields)) {
      for (Map.Entry<String, Double> entry : this.fields.entrySet()) {
        result.add("FIELD");
        result.add(entry.getKey());
        result.add(entry.getValue().toString());
      }
    }
    // 新增`EX`
    if (this.ex >= 0) {
      result.add("EX");
      result.add(String.valueOf(this.ex));
    }
    // 新增NX或XX
    if (Objects.nonNull(this.nxXx)) {
      result.add(this.nxXx.name());
    }
    // 返回結果
    return result;
  }
  
  public enum NxXx {
    NX,
    XX
  }
  // 建造者模式
  public static class Builder {
    private String key;
    private String id;
    //欄位值必須是雙精度浮點型
    private Map<String, Double> fields;
    // 單位秒
    private int ex = -1;
    // 建立方式:
    // NX 不存在的時候建立
    // XX 存在的時候更新
    private NxXx nxXx;
    public Builder key(String key) {
      this.key = key;
      return this;
    }
    public Builder id(String id) {
      this.id = id;
      return this;
    }
    public Builder field(String field, double value) {
      if (Objects.isNull(this.fields)) {
        this.fields = new LinkedHashMap<>();
      }
      this.fields.put(field, value);
      return this;
    }
    public Builder ex(int seconds) {
      this.ex = seconds;
      return this;
    }
    public Builder nxXx(NxXx nxXx) {
      this.nxXx = nxXx;
      return this;
    }
    public SetOpts build() throws AwesomeException {
      if (StringUtils.isEmpty(this.key)) {
        throw new AwesomeException(500, "key is empty");
      }
      if (StringUtils.isEmpty(this.id)) {
        throw new AwesomeException(500, "id is empty");
      }
      // 建立SetOpts物件
      return new SetOpts(this);
    }
  }
}

我們上面通過建造者的設計模式,把所有的引數都轉換成了SetOpts這個類當中,開發人員就可以通過SetOpts物件的構建來靈活地控制命令中的引數了。

3.我們需要把第三部分當中的不同資料物件轉換成不同的型別:

POINT資料型別

Point關鍵的欄位就是經緯度,除此之外,還有一個額外的欄位z,用來儲存額外的業務引數,可為空。

public class Point extends Element implements Serializable {
  // 經度
  private double lng;
  // 維度
  private double lat;
  // 額外的資料
  private double z;
  public Point(double lng, double lat, double z) {
    this.lat = lat;
    this.lng = lng;
    this.z = z;
  }
  public Point(double lng, double lat) {
    this(lng, lat, Integer.MIN_VALUE);
  }
  @Override
  public List<String> commandArgs() {
    List<String> result = new LinkedList<>();
    result.add("POINT");
    result.add(String.valueOf(this.lng));
    result.add(String.valueOf(this.lat));
    if (this.z != Integer.MIN_VALUE) {
      result.add(String.valueOf(this.z));
    }
    return result;
  }
}

BOUNDS資料型別

BOUNDS就是矩形,它的關鍵欄位就是左下角和右上角兩個點位,我們使用coordinate1和coordinate2來表示左下角和右上角;

@AllArgsConstructor
public class Bounds extends Element {
  private double[] coordinate1;
  private double[] coordinate2;
  @Override
  public List<String> commandArgs() {
    List<String> result = new LinkedList<>();
    result.add("BOUNDS");
    result.add(String.valueOf(coordinate1[0]));
    result.add(String.valueOf(coordinate1[1]));
    result.add(String.valueOf(coordinate2[0]));
    result.add(String.valueOf(coordinate2[1]));
    return result;
  }
}

HASH和STRING資料型別

HASH和STRING其實就是一個單獨的字串,但是我們還是把它封裝一下,以便開發人員使用;

@AllArgsConstructor
public class Geohash extends Element {
  private String hash;
  @Override
  public List<String> commandArgs() {
    List<String> result = new LinkedList<>();
    result.add("HASH");
    result.add(this.hash);
    return result;
  }
}
@AllArgsConstructor
public class RawString extends Element {
  private String raw;
  @Override
  public List<String> commandArgs() {
    List<String> result = new LinkedList<>();
    result.add("STRING");
    result.add(this.raw);
    return result;
  }
}

OBJECT資料型別

OBJECT其實就是GeoJSON資料,這一類資料比較複雜一點,一共有六種型別,想了解的小夥伴可以看這裡geojson.org/

Point,
LineString,
Polygon,
MultiPoint,
MultiLineString,
MultiPolygon

為了開發人員能夠更好的使用這六種型別,我們同樣使用建造者模式來設計一下GeoJSON資料型別:

@Data
public class GeoJson {
  public static class Builder {
    public Point.Builder point() {
      return new Point.Builder();
    }
    public MultiPoint.Builder multPoint() {
      return new MultiPoint.Builder();
    }
    public LineString.Builder lineString() {
      return new LineString.Builder();
    }
    public MultiLineString.Builder multiLineString() {
      return new MultiLineString.Builder();
    }
    public Polygon.Builder polygon() {
      return new Polygon.Builder();
    }
    public MultiPolygon.Builder multiPolygon() {
      return new MultiPolygon.Builder();
    }
  }
}

我們現在一個大類裡面建立多個方法,每一個方法都把對應型別的建造者給創造出來,這樣的話,就相當於這個類當中有建立六種物件的方式,每個建造者都只負責建造對應的那個物件。

下面分別是六個建造者的程式碼,每個物件都基於最基本的BaseGeoJson來構造,BaseGeoJson中把公共的欄位type和額外的meta欄位抽出來,各個型別不同的點在於座標點的數量和層次不同,所以根據各自型別的特點,程式碼設計如下:

// Point型別
  public static class Point extends BaseGeoJson {
    // 座標點
    private double[] coordinates;
    Point(Builder builder) {
      super(builder);
      this.type = GeoJsonType.Point;
      this.coordinates = builder.coordinates;
    }
    @Override
    protected Object coordinates() {
      return this.coordinates;
    }
    public static class Builder extends BaseGeoJson.Builder {
      private double[] coordinates;
      public Builder coordinate(double lon, double lat) {
        coordinates = new double[]{lat, lon};
        return this;
      }
      public Point build() {
        return new Point(this);
      }
    }
  }
// MultiPoint型別
  public static class MultiPoint extends BaseGeoJson {
    private double[][] coordinates;
    MultiPoint(Builder builder) {
      super(builder);
      this.type = GeoJsonType.MultiPoint;
      this.coordinates = builder.convert2Array();
    }
    @Override
    protected Object coordinates() {
      return this.coordinates;
    }
    public static class Builder extends BaseGeoJson.Builder {
      private List<Coordinate> coordinates;
      public Builder coordinate(double lon, double lat) {
        if (CollectionUtils.isEmpty(this.coordinates)) {
          this.coordinates = new LinkedList<>();
        }
        this.coordinates.add(new Coordinate(lat, lon));
        return this;
      }
      protected double[][] convert2Array() {
        int length = this.coordinates.size();
        double[][] result = new double[length][];
        for (int i = 0; i < length; i++) {
          result[i] = this.coordinates.get(i).convertToArray();
        }
        return result;
      }
      @Override
      public MultiPoint build() {
        return new MultiPoint(this);
      }
    }
  }
// LineString型別
  public static class LineString extends MultiPoint {
    private double[][] coordinates;
    LineString(Builder builder) {
      super(builder);
      this.type = GeoJsonType.LineString;
    }
    public static class Builder extends MultiPoint.Builder {
      @Override
      public LineString build() {
        return new LineString(this);
      }
    }
  }
// MultiLineString型別
  public static class MultiLineString extends BaseGeoJson {
    private double[][][] coordinates;
    MultiLineString(Builder builder) {
      super(builder);
      this.type = GeoJsonType.MultiLineString;
      this.coordinates = builder.convertToArray();
    }
    @Override
    protected Object coordinates() {
      return this.coordinates;
    }
    public static class Builder extends BaseGeoJson.Builder {
      private List<Line> lines = new LinkedList<>();
      public Line line() {
        return new Line(this);
      }
      void addLine(Line line) {
        lines.add(line);
      }
      double[][][] convertToArray() {
        int length = this.lines.size();
        double[][][] result = new double[length][][];
        for (int i = 0; i < length; i++) {
          Line line = this.lines.get(i);
          result[i] = line.convert2Array();
        }
        return result;
      }
      @Override
      public BaseGeoJson build() {
        return new MultiLineString(this);
      }
    }
    static class Line {
      private List<Coordinate> coordinates;
      private Builder builder;
      Line(Builder builder) {
        this.builder = builder;
        this.builder.addLine(this);
      }
      private double[][] convert2Array() {
        int length = this.coordinates.size();
        double[][] result = new double[length][];
        for (int i = 0; i < length; i++) {
          result[i] = this.coordinates.get(i).convertToArray();
        }
        return result;
      }
      public Line coordinate(double lon, double lat) {
        if (CollectionUtils.isEmpty(this.coordinates)) {
          this.coordinates = new LinkedList<>();
        }
        this.coordinates.add(new Coordinate(lat, lon));
        return this;
      }
      public Line nextLine() {
        return new Line(this.builder);
      }
      public Builder end() {
        return this.builder;
      }
    }
  }
// Polygon型別
  public static class Polygon extends MultiPoint {
    private double[][][] coordinates;
    Polygon(Builder builder) {
      super(builder);
      this.type = GeoJsonType.Polygon;
      this.coordinates = new double[][][]{builder.convert2Array()};
    }
    public static class Builder extends MultiPoint.Builder {
      @Override
      public Polygon build() {
        return new Polygon(this);
      }
    }
  }
// MultiPolygon型別
  public static class MultiPolygon extends BaseGeoJson {
    private double[][][][] coordinates;
    MultiPolygon(Builder builder) {
      super(builder);
      this.type = GeoJsonType.MultiPolygon;
      this.coordinates = new double[][][][]{builder.convert2Array()};
    }
    @Override
    protected Object coordinates() {
      return this.coordinates;
    }
    public static class Builder extends BaseGeoJson.Builder {
      private List<Polygon> polygons = new LinkedList<>();
      @Override
      public BaseGeoJson build() {
        return new MultiPolygon(this);
      }
      void addPolygon(Polygon polygon) {
        polygons.add(polygon);
      }
      private double[][][] convert2Array() {
        int length = this.polygons.size();
        double[][][] result = new double[length][][];
        for (int i = 0; i < length; i++) {
          result[i] = this.polygons.get(i).convert2Array();
        }
        return result;
      }
    }
    static class Polygon {
      private List<Coordinate> coordinates;
      private Builder builder;
      Polygon(Builder builder) {
        this.builder = builder;
        this.builder.addPolygon(this);
      }
      private double[][] convert2Array() {
        int length = this.coordinates.size();
        double[][] result = new double[length][];
        for (int i = 0; i < length; i++) {
          result[i] = this.coordinates.get(i).convertToArray();
        }
        return result;
      }
      public Polygon coordinate(double lon, double lat) {
        if (CollectionUtils.isEmpty(this.coordinates)) {
          this.coordinates = new LinkedList<>();
        }
        this.coordinates.add(new Coordinate(lat, lon));
        return this;
      }
      public Polygon nextLine() {
        return new Polygon(this.builder);
      }
      public Builder end() {
        return this.builder;
      }
    }
  }
// 基礎類別BaseGeoJson
  public abstract static class BaseGeoJson extends Element {
    // 公共欄位type
    protected GeoJsonType type;
    // 公共欄位metadata
    private Map<String, String> metadata;
    BaseGeoJson(Builder builder) {
      this.metadata = builder.metadata;
    }
    protected abstract Object coordinates();
    // 轉換成命令引數
    @Override
    public List<String> commandArgs() {
      List<String> result = new LinkedList<>();
      result.add("OBJECT");
      result.add(toJson());
      return result;
    }
    // 提供統一的轉json方法
    protected String toJson() {
      Map<String, Object> map = new LinkedHashMap<>();
      map.put("type", this.type);
      map.put("coordinates", coordinates());
      if (!CollectionUtils.isEmpty(this.metadata)) {
        for (Map.Entry<String, String> entry : this.metadata.entrySet()) {
          map.put(entry.getKey(), entry.getValue());
        }
      }
      return JsonUtil.obj2String(map);
    }
    abstract static class Builder {
      private Map<String, String> metadata;
      public Builder meta(String key, String value) {
        if (MapUtils.isEmpty(this.metadata)) {
          this.metadata = new LinkedHashMap<>();
        }
        this.metadata.put(key, value);
        return this;
      }
      public abstract BaseGeoJson build();
    }
    static class Coordinate {
      private double lat;
      private double lon;
      Coordinate(double lat, double lon) {
        this.lat = lat;
        this.lon = lon;
      }
      public double[] convertToArray() {
        return new double[]{this.lat, this.lon};
      }
    }
    // GeoJSON所有的資料型別
    enum GeoJsonType {
      Point,
      LineString,
      Polygon,
      MultiPoint,
      MultiLineString,
      MultiPolygon
    }
  }

最後,再補充一個基礎類別Element:

public abstract class Element implements Serializable {
  public abstract List<String> commandArgs();
}

如何使用

我們針對所有的資料型別全部轉換成具體的程式碼設計,下面我們看看如何使用:

private String setElement(SetOpts setOpts, Element element) {
    List<String> args1 = setOpts.commandLine();
    List<String> commandArgs = element.commandArgs();
    return execute(Tile38Command.SET, args1, commandArgs);
}
/**
   * 設定點位
   *
   * @param setOpts
   * @param point
   * @return
   */
  public String setPoint(SetOpts setOpts, Point point) {
    return setElement(setOpts, point);
  }
  /**
   * 設定物件
   *
   * @param setOpts
   * @param geoJson
   * @return
   */
  public String setObject(SetOpts setOpts, GeoJson.BaseGeoJson geoJson) {
    return setElement(setOpts, geoJson);
  }
  /**
   * 設定矩形邊界
   *
   * @param setOpts
   * @param bounds
   * @return
   */
  public String setBounds(SetOpts setOpts, Bounds bounds) {
    return setElement(setOpts, bounds);
  }
  /**
   * 設定geohash
   *
   * @param setOpts
   * @param geohash
   * @return
   */
  public String setGeohash(SetOpts setOpts, Geohash geohash) {
    return setElement(setOpts, geohash);
  }
  /**
   * 設定String
   *
   * @param setOpts
   * @param string
   * @return
   */
  public String setString(SetOpts setOpts, RawString string) {
    return setElement(setOpts, string);
  }

所有的開發人員只需要按照上面的方法來使用就可以很方便地執行Tile38的命令了,至此,我們所有關於SET命令的設計都已經講解完畢。

以上就是解析Springboot整合Tile38使用者端之Set命令實現範例的詳細內容,更多關於Springboot整合Tile使用者端Set命令的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com