server-commons is a Java utility library for Spring Boot server applications. It packages reusable helpers for Redis, distributed locking and ID generation, synchronous and asynchronous HTTP calls, Elasticsearch, date/time operations, bit manipulation, AES encryption, and client IP extraction.
The library is published as a regular JAR and is intended to be consumed by other services; it does not contain a production application entry point.
| Item | Value |
|---|---|
| Group ID | outfox.infra |
| Artifact ID | server-commons |
| Current version | 1.2.4 |
| Java | Java 8 or later |
| Build tool | Gradle 6.6 (wrapper included) |
| Spring Boot baseline | 2.3.5.RELEASE |
| Area | Main classes | Capabilities |
|---|---|---|
| Redis | RedisConfig, RedisUtil<T> |
JSON serialization, string values, counters, hashes, sets, expiration, deletion, and basic locks |
| Locking | LockUtil, RedisLockUtil |
Redis-backed lock facade |
| Distributed IDs | DistributedIdGenerateUtils |
Synchronized 53-bit IDs containing a timestamp, worker ID, and per-millisecond sequence |
| HTTP | HttpClientUtil, AsyncHttpClientUtil |
Pooled Apache HTTP clients for GET, POST, JSON, form, upload, and DELETE requests |
| Elasticsearch | ElasticsearchUtil<T> |
Insert, get/delete by ID, delete index, and resolve an index name from @Document |
| Date and time | DateTimeUtils |
Day/week/month boundaries, offsets, timestamp formatting, and parsing |
| Math and bits | MathUtils |
Masks, random booleans, bit inspection/update, and bit-filtered integer ranges |
| Encryption | AESUtil |
Legacy AES string encryption/decryption with hexadecimal output |
| Networking | IPUtils |
Client IP extraction from common reverse-proxy headers |
Add the Maven repository that hosts the artifact, then add:
dependencies {
implementation group: 'outfox.infra', name: 'server-commons', version: '1.2.4'
}<dependency>
<groupId>outfox.infra</groupId>
<artifactId>server-commons</artifactId>
<version>1.2.4</version>
</dependency>For local development, install the artifact into your local Maven repository first:
bash ./gradlew installThis project does not register Spring Boot auto-configuration metadata. If you use its Spring-managed Redis, lock, ID, or Elasticsearch classes, include the package in component scanning or import only the configuration/components you need.
@SpringBootApplication
@ComponentScan(basePackages = {
"com.example.application",
"outfox.infra.server.commons"
})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}RedisUtil requires a non-blank key prefix. Standard Spring Boot Redis connection properties are also required:
spring:
redis:
host: localhost
port: 6379
timeout: 2s
infra:
redis:
key:
prefix: MY-SERVICEEvery key passed to RedisUtil is stored as <prefix>::<key>. RedisConfig configures a RedisTemplate<String, T> with string keys and Jackson JSON values.
Configure Elasticsearch using the Spring Boot 2.3 REST client properties when ElasticsearchUtil is needed:
spring:
elasticsearch:
rest:
uris: http://localhost:9200@Service
public class CacheService {
private final RedisUtil<String> redis;
public CacheService(RedisUtil<String> redis) {
this.redis = redis;
}
public void storeSession(String sessionId, String value) {
redis.setNonNull("session:" + sessionId, value, 1800);
}
public String getSession(String sessionId) {
return redis.get("session:" + sessionId);
}
}Other Redis operations include exists, incr, setIfAbsent, hset/hget/hdel, sadd/smember/smembers, expire, pexpire, and single or bulk del.
@Autowired
private LockUtil lockUtil;
public void runOnce() {
if (!lockUtil.acquireLock("daily-job", 30)) {
return;
}
try {
// Protected work
} finally {
lockUtil.releaseLock("daily-job");
}
}The lock timeout is in seconds.
After Spring has initialized DistributedIdGenerateUtils and assigned a worker ID through Redis:
long id = DistributedIdGenerateUtils.generateId();The ID layout is 41 timestamp bits, 5 worker bits, and 7 sequence bits. A single process can generate up to 128 IDs per millisecond before waiting for the next millisecond.
Timeout values are milliseconds. The url argument is a host name because the implementation passes it to URIBuilder.setHost; use query parameters for the supported request target.
HttpClientUtil client = new HttpClientUtil(
1000, // wait for a pooled connection
2000, // connect timeout
5000, // socket timeout
100 // maximum pooled connections
);
Map<String, String> query = new HashMap<>();
query.put("id", "42");
JSONObject response = client.get("https", "api.example.com", query);
int status = response.getIntValue("code");
String body = response.getString("body");All synchronous methods return a Fastjson JSONObject containing the HTTP status as code and the unparsed response entity as body. postJSON adds Content-Type: application/json; the class also supports URL-encoded forms, raw string bodies, multipart file upload, and DELETE.
AsyncHttpClientUtil client = new AsyncHttpClientUtil(1000, 2000, 5000, 100);
Future<HttpResponse> future = client.get(
"https",
"api.example.com",
Collections.singletonMap("id", "42"),
Collections.emptyMap(),
new AsyncHandlerAdapter() {
@Override
public Object completed(HttpResponse response) {
JSONObject result = BaseHttpClientUtil.getJSONResponse(response);
// Process result
return result;
}
}
);Implement IHandler or extend AsyncHandlerAdapter to handle completion, failure, and cancellation. The returned Future<HttpResponse> can be used to wait for or cancel the request.
@Document(indexName = "customers")
public class Customer {
@Id
private String id;
// fields, getters, and setters
}
@Autowired
private ElasticsearchUtil<Customer> elasticsearch;
String id = elasticsearch.insert("customers", customer);
Customer stored = elasticsearch.queryById(Customer.class, id);
elasticsearch.deleteById(Customer.class, id);Operations log Elasticsearch exceptions and return null or false on failure. getIndexName(Customer.class) reads the index name from @Document and throws if the annotation is missing.
String cipherText = AESUtil.encryptStr("payload");
String plainText = AESUtil.decryptStr(cipherText);
long startOfDay = DateTimeUtils.getDayStartTime(System.currentTimeMillis());
String formatted = DateTimeUtils.convertToDateTimeStr(System.currentTimeMillis());
boolean thirdBitSet = MathUtils.checkBit(4, 2);
int withFirstBitSet = MathUtils.setAssignBitIsOne(2, 0);
String clientIp = IPUtils.getIpAddr(request);IPUtils checks X-Original-Forwarded-For, X-Forwarded-For, and several legacy proxy headers before falling back to HttpServletRequest.getRemoteAddr(). Only trust forwarded headers when they are sanitized by a trusted proxy.
The wrapper file is included but is not executable in the current repository checkout, so invoke it through the shell on Unix-like systems:
bash ./gradlew jarThe JAR is written to build/libs/server-commons-<version>.jar.
The test sources cover AES, date/time, and math helpers. Redis and Elasticsearch integration test classes are marked @Ignore; enable them only after replacing the test environment endpoints in src/test/resources/application-test.yml with accessible, non-production services.
The current Gradle configuration does not register Lombok as a test annotation processor, although ElasticsearchTestData uses Lombok-generated builders and accessors. Consequently, bash ./gradlew test fails during compileTestJava. Add the following dependency before running the complete test suite:
dependencies {
testAnnotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.16'
}Then run:
bash ./gradlew clean testFor local development, use a snapshot version and install locally:
bash ./gradlew jar
bash ./gradlew installTo publish to a Maven repository, fill in the repository URL and credentials in build.gradle, choose the intended release or snapshot version, and run:
bash ./gradlew publishUse snapshot artifacts only for local/development work and fixed versions for test and production environments.
AESUtilcontains a fixed key and uses the provider defaultAEStransformation (normally ECB with PKCS#5 padding). It provides compatibility, not modern secure encryption. Do not use it for new sensitive data; prefer an authenticated mode such as AES-GCM with externally managed keys.- Redis lock values do not contain an owner token, and release is a plain delete. A client can therefore delete a lock acquired by another client after expiration/reacquisition. Use a token-checked Lua release or a mature locking library when correctness is critical.
- Distributed worker IDs are allocated from one Redis counter and wrap after 32 values. They are not leased or tied to process lifetime, so deployments must evaluate collision risk during restarts and at larger scale.
- Day-boundary and schedule methods use a fixed UTC+8 offset, while some other date methods use the JVM default time zone. Confirm that behavior before using the helpers outside UTC+8 deployments.
- The HTTP utility API builds a URI from a scheme and host only; it has no separate path parameter. It also does not expose an explicit lifecycle/close method for its pooled clients.
RedisConfigenables Jackson default typing for non-final values. Review serialized types and trust boundaries before reading Redis data written by untrusted parties.
src/main/java/outfox/infra/server/commons/
├── elasticsearch/ Elasticsearch helper
├── http/ Shared, synchronous, and asynchronous HTTP clients
├── lock/ Lock interface and Redis implementation
├── redis/ Redis template configuration and operations
├── AESUtil.java
├── DateTimeUtils.java
├── DistributedIdGenerateUtils.java
├── IPUtils.java
└── MathUtils.java