解析Springboot集成Tile38客戶端之Set命令實(shí)現(xiàn)示例
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
命令就相當(dāng)于redis中的hash
命令的使用,也是一個(gè)key
和id
的組合,但是不同的是,Tile38的set
命令還可以攜帶更多的其他屬性,比如可以自定義FIELD
字段,還可以設(shè)置EX
有效期等等,那么我們需要給這個(gè)語法設(shè)計(jì)一套好用的java api
,以便開發(fā)人員可以更好地使用Tile38。
語法分析
首先,根據(jù)上面提供的語法,我們可以分為三部分:
1.第一部分就是命令的啟示關(guān)鍵字SET
,我們把這個(gè)關(guān)鍵字單獨(dú)作為一部分;
2.第二部分就是key id [FIELD name value ...] [EX seconds] [NX|XX]
,我們把這些都作為參數(shù);
3.第三部分就是最后的目標(biāo)數(shù)據(jù)對(duì)象:
(OBJECT geojson)|(POINT lat lon z)|(BOUNDS minlat minlon maxlat maxlon)|(HASH geohash)|(STRING value)
代碼設(shè)計(jì)
1.我們把第一部分的命令關(guān)鍵字通過枚舉的方式來管理:
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; } }
因?yàn)閞edis客戶端工具在發(fā)送命令前需要對(duì)所有命令進(jìn)行編碼,所以要求所有的命令都必須實(shí)現(xiàn)ProtocolKeyword
接口。如果命令的起始關(guān)鍵字是兩個(gè)或多個(gè)單詞,那么我們會(huì)使用下劃線連接,轉(zhuǎn)換成bytes的時(shí)候我們可以使用空格把下劃線替換。
2.我們把命令的第二部分抽象成一個(gè)具體的class,通過相關(guān)的字段來進(jìn)行描述:
public class SetOpts { private String key; private String id; //字段值必須是雙精度浮點(diǎn)型 private Map<String, Double> fields; // 單位秒 private int ex; // 創(chuàng)建方式: // NX 不存在的時(shí)候創(chuàng)建 // XX 存在的時(shí)候更新 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; } // 把所有的參數(shù)按順序放到列表中 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()); } // 返回結(jié)果 return result; } public enum NxXx { NX, XX } // 建造者模式 public static class Builder { private String key; private String id; //字段值必須是雙精度浮點(diǎn)型 private Map<String, Double> fields; // 單位秒 private int ex = -1; // 創(chuàng)建方式: // NX 不存在的時(shí)候創(chuàng)建 // XX 存在的時(shí)候更新 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"); } // 創(chuàng)建SetOpts對(duì)象 return new SetOpts(this); } } }
我們上面通過建造者的設(shè)計(jì)模式,把所有的參數(shù)都轉(zhuǎn)換成了SetOpts這個(gè)類當(dāng)中,開發(fā)人員就可以通過SetOpts對(duì)象的構(gòu)建來靈活地控制命令中的參數(shù)了。
3.我們需要把第三部分當(dāng)中的不同數(shù)據(jù)對(duì)象轉(zhuǎn)換成不同的類型:
POINT數(shù)據(jù)類型
Point關(guān)鍵的字段就是經(jīng)緯度,除此之外,還有一個(gè)額外的字段z
,用來存儲(chǔ)額外的業(yè)務(wù)參數(shù),可為空。
public class Point extends Element implements Serializable { // 經(jīng)度 private double lng; // 維度 private double lat; // 額外的數(shù)據(jù) 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數(shù)據(jù)類型
BOUNDS就是矩形,它的關(guān)鍵字段就是左下角和右上角兩個(gè)點(diǎn)位,我們使用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數(shù)據(jù)類型
HASH和STRING其實(shí)就是一個(gè)單獨(dú)的字符串,但是我們還是把它封裝一下,以便開發(fā)人員使用;
@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數(shù)據(jù)類型
OBJECT其實(shí)就是GeoJSON數(shù)據(jù),這一類數(shù)據(jù)比較復(fù)雜一點(diǎn),一共有六種類型,想了解的小伙伴可以看這里geojson.org/
Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon
為了開發(fā)人員能夠更好的使用這六種類型,我們同樣使用建造者模式來設(shè)計(jì)一下GeoJSON數(shù)據(jù)類型:
@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(); } } }
我們現(xiàn)在一個(gè)大類里面創(chuàng)建多個(gè)方法,每一個(gè)方法都把對(duì)應(yīng)類型的建造者給創(chuàng)造出來,這樣的話,就相當(dāng)于這個(gè)類當(dāng)中有創(chuàng)建六種對(duì)象的方式,每個(gè)建造者都只負(fù)責(zé)建造對(duì)應(yīng)的那個(gè)對(duì)象。
下面分別是六個(gè)建造者的代碼,每個(gè)對(duì)象都基于最基本的BaseGeoJson來構(gòu)造,BaseGeoJson中把公共的字段type和額外的meta字段抽出來,各個(gè)類型不同的點(diǎn)在于坐標(biāo)點(diǎn)的數(shù)量和層次不同,所以根據(jù)各自類型的特點(diǎn),代碼設(shè)計(jì)如下:
// Point類型 public static class Point extends BaseGeoJson { // 坐標(biāo)點(diǎn) 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(); // 轉(zhuǎn)換成命令參數(shù) @Override public List<String> commandArgs() { List<String> result = new LinkedList<>(); result.add("OBJECT"); result.add(toJson()); return result; } // 提供統(tǒng)一的轉(zhuǎn)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所有的數(shù)據(jù)類型 enum GeoJsonType { Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon } }
最后,再補(bǔ)充一個(gè)基類Element:
public abstract class Element implements Serializable { public abstract List<String> commandArgs(); }
如何使用
我們針對(duì)所有的數(shù)據(jù)類型全部轉(zhuǎn)換成具體的代碼設(shè)計(jì),下面我們看看如何使用:
private String setElement(SetOpts setOpts, Element element) { List<String> args1 = setOpts.commandLine(); List<String> commandArgs = element.commandArgs(); return execute(Tile38Command.SET, args1, commandArgs); } /** * 設(shè)置點(diǎn)位 * * @param setOpts * @param point * @return */ public String setPoint(SetOpts setOpts, Point point) { return setElement(setOpts, point); } /** * 設(shè)置對(duì)象 * * @param setOpts * @param geoJson * @return */ public String setObject(SetOpts setOpts, GeoJson.BaseGeoJson geoJson) { return setElement(setOpts, geoJson); } /** * 設(shè)置矩形邊界 * * @param setOpts * @param bounds * @return */ public String setBounds(SetOpts setOpts, Bounds bounds) { return setElement(setOpts, bounds); } /** * 設(shè)置geohash * * @param setOpts * @param geohash * @return */ public String setGeohash(SetOpts setOpts, Geohash geohash) { return setElement(setOpts, geohash); } /** * 設(shè)置String * * @param setOpts * @param string * @return */ public String setString(SetOpts setOpts, RawString string) { return setElement(setOpts, string); }
所有的開發(fā)人員只需要按照上面的方法來使用就可以很方便地執(zhí)行Tile38的命令了,至此,我們所有關(guān)于SET
命令的設(shè)計(jì)都已經(jīng)講解完畢。
以上就是解析Springboot集成Tile38客戶端之Set命令實(shí)現(xiàn)示例的詳細(xì)內(nèi)容,更多關(guān)于Springboot集成Tile客戶端Set命令的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Springboot如何使用@Async實(shí)現(xiàn)異步任務(wù)
這篇文章主要介紹了Springboot如何使用@Async實(shí)現(xiàn)異步任務(wù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09java數(shù)據(jù)庫連接池的特點(diǎn)及步驟
大家好,本篇文章主要講的是數(shù)據(jù)庫連接池的特點(diǎn)及步驟,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12java之scan.next()與scan.nextline()函數(shù)的使用及區(qū)別
這篇文章主要介紹了java之scan.next()與scan.nextline()函數(shù)的使用及區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04Java實(shí)現(xiàn)Huffman編碼的示例代碼
Huffman編碼是一種編碼方式,本文主要介紹了Java實(shí)現(xiàn)Huffman編碼的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08SpringBoot+EasyPoi實(shí)現(xiàn)excel導(dǎo)出功能
最新小編遇到這樣一個(gè)需求,根據(jù)檢索條件查詢列表并將結(jié)果導(dǎo)出到excel,實(shí)現(xiàn)過程也非常簡單,感興趣的朋友跟隨小編一起看看吧2021-09-09淺談一下Java為什么不能使用字符流讀取非文本的二進(jìn)制文件
這篇文章主要介紹了淺談一下為什么不能使用字符流讀取非文本的二進(jìn)制文件,剛學(xué)Java的IO流部分時(shí),書上說只能使用字節(jié)流去讀取圖片、視頻等非文本二進(jìn)制文件,不能使用字符流,否則文件會(huì)損壞,需要的朋友可以參考下2023-04-04