当前位置: 首页 > 工具软件 > JRediSearch > 使用案例 >

Jredisearch的中文处理

饶志
2023-12-01

Jredisearch进行编码与解码使用的是默认的系统编码,不是utf-8,所以在开发时,中文可以正常存入也可以正常通过文档id获取中文内容,但是如果将中文作为条件查询则查询不到

注意:

使用jredisearch的updateDocument()与replaceDocument()方法时可能会出现新存入的数据或之前存入且未被更新的数据无法通过

searchQuery()方法查询到,但是通过文档id是可以获取到整个文档内容,只能先deleteDocument()再addDocumet()

引入jredisearch的依赖

        <dependency>
            <groupId>com.redislabs</groupId>
            <artifactId>jredisearch</artifactId>
            <version>0.22.0</version>
        </dependency>

注意:

如果同时使用redis与redisearch可能会与spring-data-redis产生依赖冲突

复制document、query、result、client类,将字符串编码改为utf-8

RedisearchDocument:

public class RedisearchDocument implements Serializable {

	private String id;
	private double score;
	private byte[] payload;
	private Map<String, Object> properties;

	public RedisearchDocument(String id, Double score) {
		this(id, new HashMap<>(), score.floatValue());
	}

	public RedisearchDocument(String id) {
		this(id, 1.0);
	}

	public RedisearchDocument(String id, Map<String, Object> fields) {
		this(id, fields, 1.0f);
	}

	public RedisearchDocument(String id, Map<String, Object> fields, double score) {
		this(id, fields, score, null);
	}

	public RedisearchDocument(String id, Map<String, Object> fields, double score, byte[] payload) {
		this.id = id;
		this.properties = new HashMap<>(fields);
		this.score = score;
		this.payload = payload;
	}

	public Iterable<Map.Entry<String, Object>> getProperties() {
		return properties.entrySet();
	}


	public static RedisearchDocument load(String id, Double score, byte[] payload, List<byte[]> fields) {
		RedisearchDocument ret = new RedisearchDocument(id, score);
		ret.payload = payload;
		if (fields != null) {
			for (int i = 0; i < fields.size(); i += 2) {
				try {
					ret.set(new String(fields.get(i), "utf-8"), new String(fields.get(i + 1), "utf-8"));
				} catch (Exception e) {
					System.out.println(e.getMessage());
				}
			}
		}
		return ret;
	}


	public RedisearchDocument set(String key, Object value) {
		properties.put(key, value);
		return this;
	}

	/**
	 * return the property value inside a key
	 *
	 * @param key key of the property
	 * @return the property value
	 */
	public Object get(String key) {
		return properties.get(key);
	}


	/**
	 * @return the document's score
	 */
	public double getScore() {
		return score;
	}

	public byte[] getPayload() {
		return payload;
	}

	/**
	 * Set the document's score
	 *
	 * @param score new score to set
	 * @return the document itself
	 */
	public RedisearchDocument setScore(float score) {
		this.score = score;
		return this;
	}

	/**
	 * @return the document's id
	 */
	public String getId() {
		return id;
	}

	private static Gson gson = new Gson();

	@Override
	public String toString() {
		return gson.toJson(this);
	}

	public boolean hasProperty(String key) {
		return properties.containsKey(key);
	}
	
}

RedisearchQuery:

public class RedisearchQuery {


	/**
	 * Filter represents a filtering rules in a query
	 */
	private abstract static class Filter {

		public final String property;

		public abstract void serializeRedisArgs(List<byte[]> args);

		public Filter(String property) {
			this.property = property;
		}

	}

	/**
	 * NumericFilter wraps a range filter on a numeric field. It can be inclusive or exclusive
	 */
	public static class NumericFilter extends Filter {

		private final double min;
		private final boolean exclusiveMin;
		private final double max;
		private final boolean exclusiveMax;

		public NumericFilter(String property, double min, boolean exclusiveMin, double max, boolean exclusiveMax) {
			super(property);
			this.min = min;
			this.max = max;
			this.exclusiveMax = exclusiveMax;
			this.exclusiveMin = exclusiveMin;
		}

		public NumericFilter(String property, double min, double max) {
			this(property, min, false, max, false);
		}

		private String formatNum(double num, boolean exclude) {
			if (num == Double.POSITIVE_INFINITY) {
				return "+inf";
			}
			if (num == Double.NEGATIVE_INFINITY) {
				return "-inf";
			}
			return String.format("%s%f", exclude ? "(" : "", num);
		}

		@Override
		public void serializeRedisArgs(List<byte[]> args) {
			args.addAll(Arrays.asList("FILTER".getBytes(), property.getBytes(),
					formatNum(min, exclusiveMin).getBytes(),
					formatNum(max, exclusiveMax).getBytes()));

		}
	}

	/**
	 * GeoFilter encapsulates a radius filter on a geographical indexed fields
	 */
	public static class GeoFilter extends Filter {

		public static final String KILOMETERS = "km";
		public static final String METERS = "m";
		public static final String FEET = "ft";
		public static final String MILES = "mi";

		private final double lon;
		private final double lat;
		private final double radius;
		private final String unit;

		public GeoFilter(String property, double lon, double lat, double radius, String unit) {
			super(property);
			this.lon = lon;
			this.lat = lat;
			this.radius = radius;
			this.unit = unit;
		}

		@Override
		public void serializeRedisArgs(List<byte[]> args) {
			args.addAll(Arrays.asList("GEOFILTER".getBytes(),
					property.getBytes(), Double.toString(lon).getBytes(),
					Double.toString(lat).getBytes(),
					Double.toString(radius).getBytes(),
					unit.getBytes()));
		}
	}

	public static class Paging {
		int offset;
		int num;

		public Paging(int offset, int num) {
			this.offset = offset;
			this.num = num;
		}
	}

	public static class HighlightTags {
		private final String open;
		private final String close;

		public HighlightTags(String open, String close) {
			this.open = open;
			this.close = close;
		}
	}

	/**
	 * The query's filter list. We only support AND operation on all those filters
	 */
	protected final List<Filter> _filters = new LinkedList<>();

	/**
	 * The textual part of the query
	 */
	protected final String _queryString;

	/**
	 * The sorting parameters
	 */
	protected final Paging _paging = new Paging(0, 10);

	protected boolean _verbatim = false;
	protected boolean _noContent = false;
	protected boolean _noStopwords = false;
	protected boolean _withScores = false;
	protected boolean _withPayloads = false;
	protected String _language = null;
	protected String[] _fields = null;
	protected String[] _keys = null;
	protected String[] _returnFields = null;
	protected String[] highlightFields = null;
	protected String[] summarizeFields = null;
	protected String[] highlightTags = null;
	protected String summarizeSeparator = null;
	protected int summarizeNumFragments = -1;
	protected int summarizeFragmentLen = -1;
	protected byte[] _payload = null;
	protected String _sortBy = null;
	protected boolean _sortAsc = true;
	protected boolean wantsHighlight = false;
	protected boolean wantsSummarize = false;

	/**
	 * Create a new index
	 *
	 * @param queryString the textual part of the query
	 */
	public RedisearchQuery(String queryString) {
		_queryString = queryString;
	}

	public void serializeRedisArgs(List<byte[]> args) {
		try {
			args.add(_queryString.getBytes("utf-8"));
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}

		if (_verbatim) {
			args.add("VERBATIM".getBytes());
		}
		if (_noContent) {
			args.add("NOCONTENT".getBytes());
		}
		if (_noStopwords) {
			args.add("NOSTOPWORDS".getBytes());
		}
		if (_withScores) {
			args.add("WITHSCORES".getBytes());
		}
		if (_withPayloads) {
			args.add("WITHPAYLOADS".getBytes());
		}
		if (_language != null) {
			try {
				args.add("LANGUAGE".getBytes("utf-8"));
				args.add(_language.getBytes("utf-8"));
			} catch (Exception e) {
				System.out.println(e.getMessage());
			}

		}
		if (_fields != null && _fields.length > 0) {
			try {
				args.add("INFIELDS".getBytes());
				args.add(String.format("%d", _fields.length).getBytes("utf-8"));
				for (String f : _fields) {
					args.add(f.getBytes("utf-8"));
				}
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}

		if (_sortBy != null) {
			args.add("SORTBY".getBytes());
			args.add(_sortBy.getBytes());
			args.add((_sortAsc ? "ASC" : "DESC").getBytes());
		}

		if (_payload != null) {
			args.add("PAYLOAD".getBytes());
			args.add(_payload);
		}

		if (_paging.offset != 0 || _paging.num != 10) {
			args.addAll(Arrays.asList("LIMIT".getBytes(),
					Integer.toString(_paging.offset).getBytes(),
					Integer.toString(_paging.num).getBytes()
			));
		}

		if (_filters != null && !_filters.isEmpty()) {
			for (Filter f : _filters) {
				f.serializeRedisArgs(args);
			}
		}

		if (wantsHighlight) {
			args.add("HIGHLIGHT".getBytes());
			if (highlightFields != null) {
				args.add("FIELDS".getBytes());
				args.add(Integer.toString(highlightFields.length).getBytes());
				for (String s : highlightFields) {
					args.add(s.getBytes());
				}
			}
			if (highlightTags != null) {
				args.add("TAGS".getBytes());
				for (String t : highlightTags) {
					args.add(t.getBytes());
				}
			}
		}
		if (wantsSummarize) {
			args.add("SUMMARIZE".getBytes());
			if (summarizeFields != null) {
				args.add("FIELDS".getBytes());
				args.add(Integer.toString(summarizeFields.length).getBytes());
				for (String s : summarizeFields) {
					args.add(s.getBytes());
				}
			}
			if (summarizeNumFragments != -1) {
				args.add("FRAGS".getBytes());
				args.add(Integer.toString(summarizeNumFragments).getBytes());
			}
			if (summarizeFragmentLen != -1) {
				args.add("LEN".getBytes());
				args.add(Integer.toString(summarizeFragmentLen).getBytes());
			}
			if (summarizeSeparator != null) {
				args.add("SEPARATOR".getBytes());
				args.add(summarizeSeparator.getBytes());
			}
		}

		if (_keys != null && _keys.length > 0) {
			args.add("INKEYS".getBytes());
			args.add(String.format("%d", _keys.length).getBytes());
			for (String f : _keys) {
				args.add(f.getBytes());
			}
		}

		if (_returnFields != null && _returnFields.length > 0) {
			args.add("RETURN".getBytes());
			args.add(String.format("%d", _returnFields.length).getBytes());
			for (String f : _returnFields) {
				args.add(f.getBytes());
			}
		}
	}

	/**
	 * Limit the results to a certain offset and limit
	 *
	 * @param offset the first result to show, zero based indexing
	 * @param limit  how many results we want to show
	 * @return the query itself, for builder-style syntax
	 */
	public RedisearchQuery limit(Integer offset, Integer limit) {
		_paging.offset = offset;
		_paging.num = limit;
		return this;
	}

	/**
	 * Add a filter to the query's filter list
	 *
	 * @param f either a numeric or geo filter object
	 * @return the query itself
	 */
	public RedisearchQuery addFilter(Filter f) {
		_filters.add(f);
		return this;
	}

	/* Set the query payload to be evaluated by the scoring function */
	public RedisearchQuery setPayload(byte[] payload) {
		_payload = payload;
		return this;
	}

	/**
	 * Set the query to verbatim mode, disabling stemming and query expansion
	 *
	 * @return the query object
	 */
	public RedisearchQuery setVerbatim() {
		this._verbatim = true;
		return this;
	}

	public boolean getNoContent() {
		return _noContent;
	}

	/**
	 * Set the query not to return the contents of documents, and rather just return the ids
	 *
	 * @return the query itself
	 */
	public RedisearchQuery setNoContent() {
		this._noContent = true;
		return this;
	}

	/**
	 * Set the query not to filter for stopwords. In general this should not be used
	 *
	 * @return the query object
	 */
	public RedisearchQuery setNoStopwords() {
		this._noStopwords = true;
		return this;
	}

	public boolean getWithScores() {
		return _withScores;
	}

	/**
	 * Set the query to return a factored score for each results. This is useful to merge results from multiple queries.
	 *
	 * @return the query object itself
	 */
	public RedisearchQuery setWithScores() {
		this._withScores = true;
		return this;
	}


	public boolean getWithPayloads() {
		return _withPayloads;
	}

	/**
	 * @deprecated {@link #setWithPayload()}
	 */
	@Deprecated
	public RedisearchQuery setWithPaload() {
		return this.setWithPaload();
	}

	/**
	 * Set the query to return object payloads, if any were given
	 *
	 * @return the query object itself
	 */
	public RedisearchQuery setWithPayload() {
		this._withPayloads = true;
		return this;
	}

	/**
	 * Set the query language, for stemming purposes
	 *
	 * @param language a language. see http://redisearch.io for documentation on languages and stemming
	 * @return the query object itself
	 */
	public RedisearchQuery setLanguage(String language) {
		this._language = language;
		return this;
	}

	/**
	 * Limit the query to results that are limited to a specific set of fields
	 *
	 * @param fields a list of TEXT fields in the schemas
	 * @return the query object itself
	 */
	public RedisearchQuery limitFields(String... fields) {
		this._fields = fields;
		return this;
	}

	/**
	 * Limit the query to results that are limited to a specific set of keys
	 *
	 * @param keys a list of TEXT fields in the schemas
	 * @return the query object itself
	 */
	public RedisearchQuery limitKeys(String... keys) {
		this._keys = keys;
		return this;
	}

	/**
	 * Result's projection - the fields to return by the query
	 *
	 * @param fields a list of TEXT fields in the schemas
	 * @return the query object itself
	 */
	public RedisearchQuery returnFields(String... fields) {
		this._returnFields = fields;
		return this;
	}


	public RedisearchQuery highlightFields(HighlightTags tags, String... fields) {
		if (fields == null || fields.length > 0) {
			highlightFields = fields;
		}
		if (tags != null) {
			highlightTags = new String[]{tags.open, tags.close};
		} else {
			highlightTags = null;
		}
		wantsHighlight = true;
		return this;
	}

	public RedisearchQuery highlightFields(String... fields) {
		return highlightFields(null, fields);
	}

	public RedisearchQuery summarizeFields(int contextLen, int fragmentCount, String separator, String... fields) {
		if (fields == null || fields.length > 0) {
			summarizeFields = fields;
		}
		summarizeFragmentLen = contextLen;
		summarizeNumFragments = fragmentCount;
		summarizeSeparator = separator;
		wantsSummarize = true;
		return this;
	}

	public RedisearchQuery summarizeFields(String... fields) {
		return summarizeFields(-1, -1, null, fields);
	}

	/**
	 * Set the query to be sorted by a sortable field defined in the schem
	 *
	 * @param field     the sorting field's name
	 * @param ascending if set to true, the sorting order is ascending, else descending
	 * @return the query object itself
	 */
	public RedisearchQuery setSortBy(String field, boolean ascending) {
		_sortBy = field;
		_sortAsc = ascending;
		return this;
	}
}

RedisearchClient:

public class RedisearchClient {
	String INCREMENT_FLAG = "INCR";
	String PAYLOAD_FLAG = "PAYLOAD";
	String MAX_FLAG = "MAX";
	String FUZZY_FLAG = "FUZZY";
	String DELETE_DOCUMENT = "DD";
	private final String indexName;
	private final Pool<Jedis> pool;

	protected Commands.CommandProvider commands;

	/**
	 * Create a new client to a RediSearch index
	 *
	 * @param indexName the name of the index we are connecting to or creating
	 * @param pool      jedis connection pool to be used
	 */
	public RedisearchClient(String indexName, Pool<Jedis> pool) {
		this.indexName = indexName;
		this.pool = pool;
	}

	/**
	 * Create a new client to a RediSearch index
	 *
	 * @param indexName the name of the index we are connecting to or creating
	 * @param host      the redis host
	 * @param port      the redis pot
	 */
	public RedisearchClient(String indexName, String host, int port) {
		this(indexName, host, port, 500, 100);
	}

	/**
	 * Create a new client to a RediSearch index
	 *
	 * @param indexName the name of the index we are connecting to or creating
	 * @param host      the redis host
	 * @param port      the redis pot
	 */
	public RedisearchClient(String indexName, String host, int port, int timeout, int poolSize) {
		this(indexName, host, port, timeout, poolSize, null);
	}

	/**
	 * Create a new client to a RediSearch index
	 *
	 * @param indexName the name of the index we are connecting to or creating
	 * @param host      the redis host
	 * @param port      the redis pot
	 * @param password  the password for authentication in a password protected Redis server
	 */
	public RedisearchClient(String indexName, String host, int port, int timeout, int poolSize, String password) {
		JedisPoolConfig conf = initPoolConfig(poolSize);

		this.pool = new JedisPool(conf, host, port, timeout, password);
		this.indexName = indexName;
		this.commands = new Commands.SingleNodeCommands();
	}

	/**
	 * Create a new client to a RediSearch index with JediSentinelPool implementation. JedisSentinelPool
	 * takes care of reconfiguring the Pool when there is a failover of master node thus providing high
	 * availability and automatic failover.
	 *
	 * @param indexName the name of the index we are connecting to or creating
	 * @param master    the masterName to connect from list of masters monitored by sentinels
	 * @param sentinels the set of sentinels monitoring the cluster
	 * @param timeout   the timeout in milliseconds
	 * @param poolSize  the poolSize of JedisSentinelPool
	 * @param password  the password for authentication in a password protected Redis server
	 */
	public RedisearchClient(String indexName, String master, Set<String> sentinels, int timeout, int poolSize, String password) {
		JedisPoolConfig conf = initPoolConfig(poolSize);

		this.pool = new JedisSentinelPool(master, sentinels, conf, timeout, password);
		this.indexName = indexName;
		this.commands = new Commands.SingleNodeCommands();
	}

	/**
	 * Create a new client to a RediSearch index with JediSentinelPool implementation. JedisSentinelPool
	 * takes care of reconfiguring the Pool when there is a failover of master node thus providing high
	 * availability and automatic failover.
	 *
	 * <p>The Client is initialized with following default values for {@link JedisSentinelPool}
	 * <ul><li> password - NULL, no authentication required to connect to Redis Server</li></ul>
	 *
	 * @param indexName  the name of the index we are connecting to or creating
	 * @param masterName the masterName to connect from list of masters monitored by sentinels
	 * @param sentinels  the set of sentinels monitoring the cluster
	 * @param timeout    the timeout in milliseconds
	 * @param poolSize   the poolSize of JedisSentinelPool
	 */
	public RedisearchClient(String indexName, String masterName, Set<String> sentinels, int timeout, int poolSize) {
		this(indexName, masterName, sentinels, timeout, poolSize, null);
	}

	/**
	 * Create a new client to a RediSearch index with JediSentinelPool implementation. JedisSentinelPool
	 * takes care of reconfiguring the Pool when there is a failover of master node thus providing high
	 * availability and automatic failover.
	 *
	 * <p>The Client is initialized with following default values for {@link JedisSentinelPool}
	 * <ul> <li>timeout - 500 mills</li>
	 * <li> poolSize - 100 connections</li>
	 * <li> password - NULL, no authentication required to connect to Redis Server</li></ul>
	 *
	 * @param indexName  the name of the index we are connecting to or creating
	 * @param masterName the masterName to connect from list of masters monitored by sentinels
	 * @param sentinels  the set of sentinels monitoring the cluster
	 */
	public RedisearchClient(String indexName, String masterName, Set<String> sentinels) {
		this(indexName, masterName, sentinels, 500, 100);
	}


	Jedis _conn() {
		return pool.getResource();
	}

	private BinaryClient sendCommand(Jedis conn, ProtocolCommand provider, String... args) {
		BinaryClient client = conn.getClient();
		client.sendCommand(provider, args);
		return client;
	}

	private BinaryClient sendCommand(Jedis conn, ProtocolCommand provider, byte[][] args) {
		BinaryClient client = conn.getClient();
		client.sendCommand(provider, args);
		return client;
	}

	/**
	 * Constructs JedisPoolConfig object.
	 *
	 * @param poolSize size of the JedisPool
	 * @return {@link JedisPoolConfig} object with a few default settings
	 */
	private JedisPoolConfig initPoolConfig(int poolSize) {
		JedisPoolConfig conf = new JedisPoolConfig();
		conf.setMaxTotal(poolSize);
		conf.setTestOnBorrow(false);
		conf.setTestOnReturn(false);
		conf.setTestOnCreate(false);
		conf.setTestWhileIdle(false);
		conf.setMinEvictableIdleTimeMillis(60000);
		conf.setTimeBetweenEvictionRunsMillis(30000);
		conf.setNumTestsPerEvictionRun(-1);
		conf.setFairness(true);

		return conf;
	}

	/**
	 * Create the index definition in redis
	 *
	 * @param schema  a schema definition, see {@link Schema}
	 * @param options index option flags, see {@link io.redisearch.client.Client.IndexOptions}
	 * @return true if successful
	 */
	public boolean createIndex(Schema schema, io.redisearch.client.Client.IndexOptions options) {

		ArrayList<String> args = new ArrayList<String>();

		args.add(indexName);

		options.serializeRedisArgs(args);

		args.add("SCHEMA");

		for (Schema.Field f : schema.fields) {
			f.serializeRedisArgs(args);
		}

		try (Jedis conn = _conn()) {
			String rep = sendCommand(conn, commands.getCreateCommand(), args.toArray(new String[args.size()]))
					.getStatusCodeReply();
			return rep.equals("OK");
		}
	}


	/**
	 * Search the index
	 *
	 * @param q a {@link Query} object with the query string and optional parameters
	 * @return a {@link SearchResult} object with the results
	 */
	public RedisearchResult search(RedisearchQuery q) {
		ArrayList<byte[]> args = new ArrayList<>(4);
		args.add(indexName.getBytes());
		q.serializeRedisArgs(args);

		try (Jedis conn = _conn()) {
			List<Object> resp =
					sendCommand(conn, commands.getSearchCommand(),
							args.toArray(new byte[args.size()][])).getObjectMultiBulkReply();
			return new RedisearchResult(resp, !q.getNoContent(), q.getWithScores(), q.getWithPayloads());
		}
	}

	public AggregationResult aggregate(AggregationRequest q) {
		ArrayList<byte[]> args = new ArrayList<>();
		args.add(indexName.getBytes());
		q.serializeRedisArgs(args);

		try (Jedis conn = _conn()) {
			List<Object> resp = sendCommand(conn, commands.getAggregateCommand(), args.toArray(new byte[args.size()][]))
					.getObjectMultiBulkReply();
			return new AggregationResult(resp);
		}
	}

	/**
	 * Generate an explanatory textual query tree for this query string
	 *
	 * @param q The query to explain
	 * @return A string describing this query
	 */
	public String explain(Query q) {
		ArrayList<byte[]> args = new ArrayList<>(4);
		args.add(indexName.getBytes());
		q.serializeRedisArgs(args);

		try (Jedis conn = _conn()) {
			return sendCommand(conn, commands.getExplainCommand(), args.toArray(new byte[args.size()][])).getStatusCodeReply();
		}
	}


	/**
	 * Add a document to the index
	 *
	 * @param doc The document to add
	 * @return true on success
	 */
	public boolean addDocument(Document doc) {
		try (Jedis conn = _conn()) {
			return addDocument(doc, conn).getStatusCodeReply().equals("OK");
		}
	}

	public boolean addRedisearchDocument(RedisearchDocument doc) {
		try (Jedis conn = _conn()) {
			return addRedisearchDocument(doc, conn).getStatusCodeReply().equals("OK");
		}
	}

	/**
	 */
	public boolean[] addDocuments(Document... docs) {
		return addDocuments(docs);
	}

	/**
	 * Add a batch of documents to the index
	 *
	 * @param docs The documents to add
	 * @return true on success for each document
	 */
	public boolean[] addDocuments(List<Document> docs) {
		try (Jedis conn = _conn()) {
			for (Document doc : docs) {
				addDocument(doc, conn);
			}
			List<Object> objects = conn.getClient().getMany(docs.size());
			boolean[] results = new boolean[docs.size()];
			int i = 0;
			for (Object obj : objects) {
				results[i++] = !(obj instanceof JedisDataException) &&
						SafeEncoder.encode((byte[]) obj).equals("OK");
			}
			return results;
		}
	}

	private BinaryClient addDocument(Document doc, Jedis conn) {
		ArrayList<byte[]> args = new ArrayList<>(
				Arrays.asList(indexName.getBytes(), doc.getId().getBytes(), Double.toString(doc.getScore()).getBytes()));

		try {
			args.add("LANGUAGE".getBytes("utf-8"));
			args.add("chinese".getBytes("utf-8"));
			if (doc.getPayload() != null) {
				args.add("PAYLOAD".getBytes());
				// TODO: Fix this
				args.add(doc.getPayload());
			}

			args.add("FIELDS".getBytes());
			for (Map.Entry<String, Object> ent : doc.getProperties()) {
				args.add(ent.getKey().getBytes("utf-8"));
				args.add(ent.getValue().toString().getBytes("utf-8"));
			}
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}


		return sendCommand(conn, commands.getAddCommand(), args.toArray(new byte[args.size()][]));
	}

	private BinaryClient addRedisearchDocument(RedisearchDocument doc, Jedis conn) {
		ArrayList<byte[]> args = new ArrayList<>(
				Arrays.asList(indexName.getBytes(), doc.getId().getBytes(), Double.toString(doc.getScore()).getBytes()));
		try {
			args.add("LANGUAGE".getBytes("utf-8"));
			args.add("chinese".getBytes("utf-8"));
			if (doc.getPayload() != null) {
				args.add("PAYLOAD".getBytes());
				// TODO: Fix this
				args.add(doc.getPayload());
			}

			args.add("FIELDS".getBytes());
			for (Map.Entry<String, Object> ent : doc.getProperties()) {
				args.add(ent.getKey().getBytes("utf-8"));
				args.add(ent.getValue().toString().getBytes("utf-8"));
			}
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return sendCommand(conn, commands.getAddCommand(), args.toArray(new byte[args.size()][]));
	}


	/**
	 * Index a document already in redis as a HASH key.
	 *
	 * @param docId   the id of the document in redis. This must match an existing, unindexed HASH key
	 * @param score   the document's index score, between 0 and 1
	 * @param replace if set, and the document already exists, we reindex and update it
	 * @return true on success
	 */
	public boolean addHash(String docId, double score, boolean replace) {
		ArrayList<String> args = new ArrayList<String>(Arrays.asList(indexName, docId, Double.toString(score)));

		if (replace) {
			args.add("REPLACE");
		}

		try (Jedis conn = _conn()) {
			String resp = sendCommand(conn, commands.getAddHashCommand(), args.toArray(new String[args.size()])).getStatusCodeReply();
			return resp.equals("OK");
		}
	}


	/**
	 * Delete a documents from the index
	 *
	 * @param deleteDocuments if <code>true</code> also deletes the actual document ifs it is in the index
	 * @param docIds          the document's ids
	 * @return true on success for each document if it has been deleted, false if it did not exist
	 */
	public boolean[] deleteDocuments(boolean deleteDocuments, String... docIds) {
		try (Jedis conn = _conn()) {
			for (String docId : docIds) {
				deleteDocument(docId, deleteDocuments, conn);
			}
			List<Object> objects = conn.getClient().getMany(docIds.length);
			boolean[] results = new boolean[docIds.length];
			int i = 0;
			for (Object obj : objects) {
				results[i++] = !(obj instanceof JedisDataException) &&
						((Long) obj) == 1L;
			}
			return results;
		}
	}

	/**
	 * Delete a document from the index (doesn't delete the document).
	 *
	 * @param docId the document's id
	 * @return true if it has been deleted, false if it did not exist
	 * @see #deleteDocument(String, boolean)
	 */
	public boolean deleteDocument(String docId) {
		return deleteDocument(docId, false);
	}

	/**
	 * Delete a document from the index.
	 *
	 * @param docId          the document's id
	 * @param deleteDocument if <code>true</code> also deletes the actual document if it is in the index
	 * @return true if it has been deleted, false if it did not exist
	 */
	public boolean deleteDocument(String docId, boolean deleteDocument) {
		try (Jedis conn = _conn()) {
			return deleteDocument(docId, deleteDocument, conn).getIntegerReply() == 1;
		}
	}


	/**
	 * Delete a document from the index.
	 *
	 * @param docId          the document's id
	 * @param deleteDocument if <code>true</code> also deletes the actual document if it is in the index
	 * @param conn           client connection to be used
	 * @return reference to the {@link BinaryClient} too allow chaining
	 */
	private BinaryClient deleteDocument(String docId, boolean deleteDocument, Jedis conn) {
		if (deleteDocument) {
			return sendCommand(conn, commands.getDelCommand(), this.indexName, docId, DELETE_DOCUMENT);
		} else {
			return sendCommand(conn, commands.getDelCommand(), this.indexName, docId);
		}
	}

	/**
	 * Get a document from the index
	 *
	 * @param docId The document ID to retrieve
	 * @return The document as stored in the index. If the document does not exist, null is returned.
	 */
	public Document getDocument(String docId) {
		Document d = new Document(docId);
		try (Jedis conn = _conn()) {
			List<Object> res = sendCommand(conn, commands.getGetCommand(), indexName, docId).getObjectMultiBulkReply();
			if (res == null) {
				return null;
			}
			handleListMapping(res, d::set);
			return d;
		}
	}

	public RedisearchDocument getRedisearchDocument(String docId) {
		RedisearchDocument d = new RedisearchDocument(docId);
		try (Jedis conn = _conn()) {
			List<Object> res = sendCommand(conn, commands.getGetCommand(), indexName, docId).getObjectMultiBulkReply();
			if (res == null) {
				return null;
			}
			handleListMapping(res, d::set);
			return d;
		}
	}

	private static void handleListMapping(List<Object> items, KVHandler handler) {
		for (int i = 0; i < items.size(); i += 2) {
			try {
				String key = new String(((byte[]) items.get(i)), "utf-8");
				Object val = items.get(i + 1);
				if (val.getClass().equals((new byte[]{}).getClass())) {
					val = new String((byte[]) val, "utf-8");
				}
				handler.apply(key, val);
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Drop the index and all associated keys, including documents
	 *
	 * @return true on success
	 */
	public boolean dropIndex() {
		return dropIndex(false);
	}

	/**
	 * Drop the index and associated keys, including documents
	 *
	 * @param missingOk If the index does not exist, don't throw an exception, but return false instead
	 * @return True if the index was dropped, false if it did not exist (or some other error occurred).
	 */
	public boolean dropIndex(boolean missingOk) {
		String r;
		try (Jedis conn = _conn()) {
			r = sendCommand(conn, commands.getDropCommand(), this.indexName).getStatusCodeReply();
		} catch (JedisDataException ex) {
			if (missingOk && ex.getMessage().toLowerCase().contains("unknown")) {
				return false;
			} else {
				throw ex;
			}
		}
		return r.equals("OK");
	}

	public Long addSuggestion(Suggestion suggestion, boolean increment) {
		List<String> args = new ArrayList<String>(
				Arrays.asList(this.indexName, suggestion.getString(), Double.toString(suggestion.getScore())));

		if (increment) {
			args.add(INCREMENT_FLAG);
		}
		if (suggestion.getPayload() != null) {
			args.add(PAYLOAD_FLAG);
			args.add(suggestion.getPayload());
		}

		try (Jedis conn = _conn()) {
			return sendCommand(conn, AutoCompleter.Command.SUGADD, args.toArray(new String[args.size()])).getIntegerReply();
		}
	}

	public List<Suggestion> getSuggestion(String prefix, SuggestionOptions suggestionOptions) {
		ArrayList<String> args = new ArrayList<>(Arrays.asList(this.indexName, prefix, MAX_FLAG, Integer.toString(suggestionOptions.getMax())));

		if (suggestionOptions.isFuzzy()) {
			args.add(FUZZY_FLAG);
		}

		Optional<With> options = suggestionOptions.getWith();
		if (!options.isPresent()) {
			return getSuggestions(args);
		}

		With with = options.get();
		args.addAll(Arrays.asList(with.getFlags()));
		switch (with) {
			case PAYLOAD_AND_SCORES:
				return getSuggestionsWithPayloadAndScores(args);
			case PAYLOAD:
				return getSuggestionsWithPayload(args);
			default:
				return getSuggestionsWithScores(args);
		}
	}

	private List<Suggestion> getSuggestions(List<String> args) {
		final List<Suggestion> list = new ArrayList<>();
		try (Jedis conn = _conn()) {
			final List<String> result = sendCommand(conn, AutoCompleter.Command.SUGGET, args.toArray(new String[args.size()])).getMultiBulkReply();
			result.forEach(str -> list.add(Suggestion.builder().str(str).build()));
		}
		return list;
	}

	private List<Suggestion> getSuggestionsWithScores(List<String> args) {
		final List<Suggestion> list = new ArrayList<>();
		try (Jedis conn = _conn()) {
			final List<String> result = sendCommand(conn, AutoCompleter.Command.SUGGET, args.toArray(new String[args.size()])).getMultiBulkReply();
			for (int i = 1; i < result.size() + 1; i++) {
				if (i % 2 == 0) {
					Suggestion.Builder builder = Suggestion.builder();
					builder.str(result.get(i - 2));
					builder.score(Double.parseDouble(result.get(i - 1)));
					list.add(builder.build());
				}
			}
		}
		return list;
	}

	private List<Suggestion> getSuggestionsWithPayload(List<String> args) {
		final List<Suggestion> list = new ArrayList<>();
		try (Jedis conn = _conn()) {
			final List<String> result = sendCommand(conn, AutoCompleter.Command.SUGGET, args.toArray(new String[args.size()])).getMultiBulkReply();
			for (int i = 1; i < result.size() + 1; i++) {
				if (i % 2 == 0) {
					Suggestion.Builder builder = Suggestion.builder();
					builder.str(result.get(i - 2));
					builder.payload(result.get(i - 1));
					list.add(builder.build());
				}
			}
		}
		return list;
	}

	private List<Suggestion> getSuggestionsWithPayloadAndScores(List<String> args) {
		final List<Suggestion> list = new ArrayList<>();
		try (Jedis conn = _conn()) {
			final List<String> result = sendCommand(conn, AutoCompleter.Command.SUGGET, args.toArray(new String[args.size()])).getMultiBulkReply();
			for (int i = 1; i < result.size() + 1; i++) {
				if (i % 3 == 0) {
					Suggestion.Builder builder = Suggestion.builder();
					builder.str(result.get(i - 3));
					builder.score(Double.parseDouble(result.get(i - 2)));
					builder.payload(result.get(i - 1));
					list.add(builder.build());
				}
			}
		}
		return list;
	}

	@FunctionalInterface
	private interface KVHandler {
		void apply(String key, Object value);
	}

	/**
	 * IndexOptions encapsulates flags for index creation and shuold be given to the client on index creation
	 */
	public static class IndexOptions {
		/**
		 * Set this to tell the index not to save term offset vectors. This reduces memory consumption but does not
		 * allow performing exact matches, and reduces overall relevance of multi-term queries
		 */
		public static final int USE_TERM_OFFSETS = 0x01;

		/**
		 * If set (default), we keep flags per index record telling us what fields the term appeared on,
		 * and allowing us to filter results by field
		 */
		public static final int KEEP_FIELD_FLAGS = 0x02;

		/**
		 * With each document:term record, store how often the term appears within the document. This can be used
		 * for sorting documents by their relevance to the given term.
		 */
		public static final int KEEP_TERM_FREQUENCIES = 0x08;

		public static final int DEFAULT_FLAGS = USE_TERM_OFFSETS | KEEP_FIELD_FLAGS | KEEP_TERM_FREQUENCIES;

		private int flags = 0x0;

		private List<String> stopwords = null;

		/**
		 * Default constructor
		 *
		 * @param flags flag mask
		 */
		public IndexOptions(int flags) {
			this.flags = flags;
			stopwords = null;
		}

		/**
		 * The default indexing options - use term offsets and keep fields flags
		 */
		public static io.redisearch.client.Client.IndexOptions Default() {
			return new io.redisearch.client.Client.IndexOptions(DEFAULT_FLAGS);
		}

		/**
		 * Set a custom stopword list
		 *
		 * @param stopwords the list of stopwords
		 * @return the options object itself, for builder-style construction
		 */
		public IndexOptions SetStopwords(String... stopwords) {
			this.stopwords = Arrays.asList(stopwords);
			return this;
		}

		/**
		 * Set the index to contain no stopwords, overriding the default list
		 *
		 * @return the options object itself, for builder-style constructions
		 */
		public IndexOptions SetNoStopwords() {
			stopwords = new ArrayList<>(0);
			return this;
		}

		public void serializeRedisArgs(List<String> args) {

			if ((flags & USE_TERM_OFFSETS) == 0) {
				args.add("NOOFFSETS");
			}
			if ((flags & KEEP_FIELD_FLAGS) == 0) {
				args.add("NOFIELDS");
			}
			if ((flags & KEEP_TERM_FREQUENCIES) == 0) {
				args.add("NOFREQS");
			}

			if (stopwords != null) {

				args.add("STOPWORDS");
				args.add(String.format("%d", stopwords.size()));
				if (!stopwords.isEmpty()) {
					args.addAll(stopwords);
				}

			}
		}
	}

}

RedisearchResult:

public class RedisearchResult {
	public final long totalResults;
	public final List<RedisearchDocument> docs;


	public RedisearchResult(List<Object> resp, boolean hasContent, boolean hasScores, boolean hasPayloads) {

		// Calculate the step distance to walk over the results.
		// The order of results is id, score (if withScore), payLoad (if hasPayloads), fields
		int step = 1;
		int scoreOffset = 0;
		int contentOffset = 1;
		int payloadOffset = 0;
		if (hasScores) {
			step += 1;
			scoreOffset = 1;
			contentOffset+=1;
		}
		if (hasContent) {
			step += 1;
			if (hasPayloads) {
				payloadOffset =scoreOffset+1;
				step += 1;
				contentOffset+=1;
			}
		}

		// the first element is always the number of results
		totalResults = (Long) resp.get(0);
		docs = new ArrayList<>(resp.size() - 1);

		for (int i = 1; i < resp.size(); i += step) {

			Double score = hasScores ? Double.valueOf(new String((byte[]) resp.get(i + scoreOffset))) : 1.0;
			byte[] payload = hasPayloads ? (byte[]) resp.get(i + payloadOffset) : null;
			List<byte[]> fields = hasContent ? (List<byte[]>) resp.get(i + contentOffset) : null;
			String id = new String((byte[]) resp.get(i));

			docs.add(RedisearchDocument.load(id, score, payload, fields));
		}


	}
}

 

 类似资料: