Token导航 LogoToken导航TokenDH.com
前端设计敏感数据github未标认证来源可访问许可证需确认审计异常

axim-rest-framework公理休息框架

Agent Skill

用于辅助 API 设计、接口文档、请求响应结构和服务集成说明。它适合让 Agent 梳理 endpoint、生成 OpenAPI 草稿、检查字段命名、整理错误码或辅助前后端联调。使用时需要确认真实业务语义、鉴权方式、分页和错误处理规则;涉及生成接口文档时,应避免凭空补字段,最好从现有代码、schema 或接口样例中提取事实。

总安装

190

周安装

8

GitHub Stars

公开资料未说明

下载量

67
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:axim-rest-framework(公理休息框架)
来源仓库:https://github.com/axim-one/rest-framework
仓库路径:skills/axim-rest-framework
安装命令:
npx skills add https://github.com/axim-one/rest-framework --skill axim-rest-framework
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/axim-one/rest-framework --skill axim-rest-framework

简介

基于 Spring Boot + MyBatis 的轻量级 REST 框架,减少样板代码同时保留 SQL 控制。

  • 支持注解映射和仓库代理模式,需 Java 17+ 和 MySQL 5.7+/8.0+。
  • 生产环境必须设置 session secret key 和激活 prod profile。
  • 安装命令:npx skills add https://github.com/axim-one/rest-framework --skill axim-rest-framework
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

Axim REST Framework

Spring Boot + MyBatis lightweight REST framework. Annotation-based entity mapping and repository proxy pattern that minimizes boilerplate while keeping MyBatis SQL control.

Version: 1.1.0 Requirements: Java 17+, Spring Boot 3.3+, MySQL 5.7+/8.0+, MyBatis 3.0+ Repository: https://github.com/Axim-one/rest-framework

Critical Rules

  • SECURITY: axim.rest.session.secret-key MUST be set in production. Without it, tokens have NO signature — anyone can forge a session token.
  • SECURITY: Set spring.profiles.active=prod in production. Non-prod profiles log full request bodies including passwords.
  • @XColumn is only needed for: primary keys, custom column names, or insert/update control. Regular fields auto-map via camelCase → snake_case — do NOT add @XColumn to every field.
  • @XDefaultValue(value="X") alone does NOT work — isDBDefaultUsed defaults to true, so the value is ignored. Must set isDBDefaultUsed=false for literal values.
  • @XRestServiceScan is required on the application class when using @XRestService declarative REST clients.
  • XWebClient beans can be registered via axim.web-client.services.{name}={url} in properties, then injected with @Qualifier.
  • Session token format is NOT JWT — it uses custom Base64(payload).HmacSHA256(signature). Do not use JWT libraries.
  • JSON date format is yyyy-MM-dd HH:mm:ss, not ISO 8601.
  • XSessionResolver auto-detects SessionData subclass parameters — no annotation required on the controller parameter.
  • @XPaginationDefault defaults: page=1, size=10, direction=DESC. Sort without direction defaults to ASC.
  • MANDATORY: Every member variable (Entity, DTO, Request, Response, VO) and every enum item MUST have a detailed Javadoc comment including purpose, example values, format rules, constraints, and allowed values.

Installation

Gradle

repositories {
    mavenCentral()
    maven { url 'https://jitpack.io' }
}

dependencies {
    implementation 'com.github.Axim-one.rest-framework:core:1.2.1'
    implementation 'com.github.Axim-one.rest-framework:rest-api:1.2.1'
    implementation 'com.github.Axim-one.rest-framework:mybatis:1.2.1'
}

Maven

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>com.github.Axim-one.rest-framework</groupId>
        <artifactId>core</artifactId>
        <version>1.2.1</version>
    </dependency>
    <dependency>
        <groupId>com.github.Axim-one.rest-framework</groupId>
        <artifactId>rest-api</artifactId>
        <version>1.2.1</version>
    </dependency>
    <dependency>
        <groupId>com.github.Axim-one.rest-framework</groupId>
        <artifactId>mybatis</artifactId>
        <version>1.2.1</version>
    </dependency>
</dependencies>

Application Setup

CRITICAL: All annotations below are required. Add @XRestServiceScan if using @XRestService REST clients.

@ComponentScan({"one.axim.framework.rest", "one.axim.framework.mybatis", "com.myapp"})
@SpringBootApplication
@XRepositoryScan("com.myapp.repository")
@MapperScan({"one.axim.framework.mybatis.mapper", "com.myapp.mapper"})
@XRestServiceScan("com.myapp.client")  // Only if using @XRestService REST clients
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
AnnotationRequiredPurpose
@ComponentScanYesMust include one.axim.framework.rest, one.axim.framework.mybatis, and app packages
@XRepositoryScanYesScans for @XRepository interfaces
@MapperScanYesMust include one.axim.framework.mybatis.mapper + app mapper packages
@XRestServiceScanIf using REST clientScans for @XRestService interfaces, creates JDK proxy beans

application.properties — Complete Reference

# ── DataSource ──
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.config-location=classpath:mybatis-config.xml

# ── Framework: HTTP Client ──
axim.rest.client.pool-size=200                    # Max connection pool (default: 200)
axim.rest.client.connection-request-timeout=30    # seconds (default: 30)
axim.rest.client.response-timeout=30              # seconds (default: 30)
axim.rest.debug=false                             # REST client logging (default: false)

# ── Framework: Gateway ──
axim.rest.gateway.host=http://api-gateway:8080    # Enables gateway mode for @XRestService

# ── Framework: XWebClient Beans ──
axim.web-client.services.userClient=http://user-service:8080    # Named XWebClient bean
axim.web-client.services.orderClient=http://order-service:8080  # Named XWebClient bean

# ── Framework: Session / Token ──
axim.rest.session.secret-key=your-hmac-secret-key # HMAC-SHA256 signing (omit = unsigned)
axim.rest.session.token-expire-days=90            # Token lifetime (default: 90)

# ── Framework: i18n ──
axim.rest.message.default-language=ko-KR          # Default locale (default: ko-KR)
axim.rest.message.language-header=Accept-Language  # Language header (default: Accept-Language)
spring.messages.basename=messages                  # App message files (default: messages)
spring.messages.encoding=UTF-8

mybatis-config.xml

All three elements (objectFactory, plugins, mappers) are required.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="useGeneratedKeys" value="true"/>
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <setting name="defaultStatementTimeout" value="10"/>
        <setting name="callSettersOnNulls" value="true"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!-- REQUIRED: Entity instantiation -->
    <objectFactory type="one.axim.framework.mybatis.plugin.XObjectFactory"/>
    <!-- REQUIRED: Pagination + result mapping -->
    <plugins>
        <plugin interceptor="one.axim.framework.mybatis.plugin.XResultInterceptor"/>
    </plugins>
    <!-- REQUIRED: Framework internal CRUD mapper -->
    <mappers>
        <mapper class="one.axim.framework.mybatis.mapper.CommonMapper"/>
    </mappers>
</configuration>

Entity Definition

Use @XEntity to map a class to a database table. Fields auto-map using camelCase → snake_case conversion.

@Data
@XEntity("users")
public class User {

    @XColumn(isPrimaryKey = true, isAutoIncrement = true)
    private Long id;

    private String email;
    private String name;

    @XDefaultValue(value = "NOW()", isDBValue = true)
    private LocalDateTime createdAt;

    @XDefaultValue(updateValue = "NOW()", isDBValue = true)
    private LocalDateTime updatedAt;

    @XColumn(insert = false, update = false)
    private String readOnlyField;

    @XIgnoreColumn
    private String transientField;
}

Entity with Schema

@XEntity(value = "orders", schema = "shop")
public class Order { ... }

Entity Inheritance

Parent class fields are automatically included:

public class BaseEntity {
    @XColumn(isPrimaryKey = true, isAutoIncrement = true)
    private Long id;

    @XDefaultValue(value = "NOW()", isDBValue = true)
    private LocalDateTime createdAt;

    @XDefaultValue(updateValue = "NOW()", isDBValue = true)
    private LocalDateTime updatedAt;
}

@Data
@XEntity("partners")
public class Partner extends BaseEntity {
    private String name;
    private String status;
}

@XDefaultValue Patterns

// Pattern 1: Use DB DEFAULT (column omitted from INSERT)
@XDefaultValue(isDBDefaultUsed = true)
private String region;

// Pattern 2: Literal string value on INSERT
@XDefaultValue(value = "ACTIVE", isDBDefaultUsed = false)
private String status;

// Pattern 3: DB expression on INSERT
@XDefaultValue(value = "NOW()", isDBValue = true, isDBDefaultUsed = false)
private LocalDateTime createdAt;

// Pattern 4: Auto-set value on UPDATE
@XDefaultValue(updateValue = "NOW()", isDBValue = true)
private LocalDateTime updatedAt;

Annotations Reference

AnnotationTargetDescription
@XEntity(value, schema)ClassMaps class to database table
@XColumn(value, isPrimaryKey, isAutoIncrement, insert, update)FieldColumn mapping with options
@XDefaultValue(value, updateValue, isDBDefaultUsed, isDBValue)FieldDefault values for INSERT/UPDATE
@XIgnoreColumnFieldExcludes field from DB mapping
@XRepositoryInterfaceMarks repository for proxy generation
@XRepositoryScan(basePackages)ClassScans for @XRepository interfaces

Repository

Extend IXRepository<K, T> and annotate with @XRepository:

@XRepository
public interface UserRepository extends IXRepository<Long, User> {
    User findByEmail(String email);
    List<User> findByStatus(String status);
    boolean existsByEmail(String email);
    long countByStatus(String status);
    int deleteByStatusAndName(String status, String name);
}

Repository API

MethodReturnDescription
save(entity)KPK null → INSERT, PK present → Upsert (Composite: all PKs set → upsert)
insert(entity)KPlain INSERT with auto-generated ID (Composite: returns key class)
saveAll(List)KBatch INSERT IGNORE
update(entity)intFull UPDATE (all columns including nulls)
modify(entity)intSelective UPDATE (non-null fields only)
findOne(key)TFind by primary key
findAll()List<T>Find all rows
findAll(pagination)XPage<T>Paginated find all
findWhere(Map)List<T>Find by conditions
findWhere(pagination, Map)XPage<T>Paginated find by conditions
findOneWhere(Map)TFind one by conditions
exists(key)booleanCheck existence by PK
count() / count(Map)longTotal / conditional count
deleteById(key)intDelete by primary key
deleteWhere(Map)intDelete by conditions

CRUD Examples

// save() - Upsert
User user = new User();
user.setName("Alice");
userRepository.save(user);        // INSERT, auto-increment ID set on entity
user.setId(1L);
userRepository.save(user);        // INSERT ... ON DUPLICATE KEY UPDATE

// insert() - Plain INSERT
userRepository.insert(user);

// update() vs modify()
userRepository.update(user);      // SET name='Alice', email=NULL, status=NULL
userRepository.modify(user);      // SET name='Alice' (null fields skipped)

// saveAll() - Batch
userRepository.saveAll(List.of(user1, user2, user3));

// Find
User found = userRepository.findOne(1L);
List<User> active = userRepository.findWhere(Map.of("status", "ACTIVE"));
boolean exists = userRepository.exists(1L);
long count = userRepository.count(Map.of("status", "ACTIVE"));

// Delete
userRepository.deleteById(1L);
userRepository.deleteWhere(Map.of("status", "INACTIVE"));

Composite Primary Key

Entities with multiple primary keys use a key class for IXRepository<K, T>.

// Key class — field names must match entity PK field names
@Data
public class OrderItemKey {
    private Long orderId;
    private Long itemId;
}

// Entity — multiple @XColumn(isPrimaryKey = true)
@Data
@XEntity("order_items")
public class OrderItem {
    @XColumn(isPrimaryKey = true)
    private Long orderId;
    @XColumn(isPrimaryKey = true)
    private Long itemId;
    private int quantity;
    private BigDecimal price;
}

// Repository
@XRepository
public interface OrderItemRepository extends IXRepository<OrderItemKey, OrderItem> {}
// Usage
OrderItemKey key = new OrderItemKey();
key.setOrderId(1L);
key.setItemId(100L);

repository.findOne(key);       // WHERE order_id = ? AND item_id = ?
repository.delete(key);        // WHERE order_id = ? AND item_id = ?
repository.save(orderItem);    // All PKs set → upsert, any null → insert
repository.insert(orderItem);  // Returns OrderItemKey with both PK values

Query Derivation

Declare methods and SQL is auto-generated from the method name.

Supported Prefixes: findBy, findAllBy, countBy, existsBy, deleteBy Condition Combinator: And

@XRepository
public interface OrderRepository extends IXRepository<Long, Order> {
    Order findByOrderNo(String orderNo);                           // WHERE order_no = ?
    List<Order> findByUserIdAndStatus(Long userId, String status); // WHERE user_id = ? AND status = ?
    long countByStatus(String status);                             // SELECT COUNT(*) WHERE status = ?
    boolean existsByOrderNo(String orderNo);                       // EXISTS check
    int deleteByUserIdAndStatus(Long userId, String status);       // DELETE WHERE ...
}

Pagination

IMPORTANT: Always use XPagination and XPage for pagination. NEVER create custom pagination classes (e.g., PageRequest, PageResponse, PaginationDTO). The framework handles COUNT, ORDER BY, and LIMIT automatically.

XPagination pagination = new XPagination();
pagination.setPage(1);       // 1-based
pagination.setSize(20);
pagination.addOrder(new XOrder("createdAt", XDirection.DESC));

XPage<User> result = userRepository.findAll(pagination);
result.getTotalCount();   // total rows
result.getPage();         // current page
result.getPageRows();     // rows in this page
result.getHasNext();      // more pages?

// Controller with auto-binding
@GetMapping
public XPage<User> searchUsers(@XPaginationDefault XPagination pagination) {
    return userRepository.findAll(pagination);
}
// Accepts: ?page=1&size=10&sort=email,asc

Argument Resolvers

Two resolvers are auto-registered via XWebMvcConfiguration:

XPaginationResolver — @XPaginationDefault

Resolves XPagination from query parameters. Annotation defaults:

AttributeDefaultDescription
page1Page number (1-based)
size10Rows per page
offset0Row offset (alternative to page)
column"" (none)Default sort column (camelCase)
directionDESCDefault sort direction

Sort parsing:

?sort=createdAt,DESC          → XOrder("createdAt", DESC)
?sort=name                    → XOrder("name", ASC)   ← omitted direction defaults to ASC
?sort=createdAt,DESC&sort=name,ASC  → multi-sort

Priority: ?page= present → page-based; ?offset= only → offset-based. Query params override annotation defaults. "undefined" and "null" strings are treated as absent.

@GetMapping
public XPage<User> listUsers(
        @XPaginationDefault(size = 20, column = "createdAt", direction = XDirection.DESC)
        XPagination pagination) {
    return userRepository.findAll(pagination);
}

XSessionResolver — SessionData Subclass

Resolves any SessionData subclass from Access-Token HTTP header. No annotation required — auto-detected by parameter type.

// UserSession extends SessionData → auto-resolved from Access-Token header
@GetMapping("/me")
public UserProfile getMyProfile(UserSession session) {
    return userService.getProfile(session.getUserId());
}
  • Requires XAccessTokenParseHandler bean (auto-configured or custom @Component)
  • If XAccessTokenParseHandler not registered → returns null (no error)
  • If token missing → 401 (NOT_FOUND_ACCESS_TOKEN)
  • If token invalid → 401 (INVALID_ACCESS_TOKEN)
  • If token expired → 401 (EXPIRE_ACCESS_TOKEN)

Query Strategy: Repository vs Custom Mapper

The framework provides two query approaches. Choosing the right one is critical:

Use @XRepository (auto-generated SQL) when:

  • Exact-match WHERE conditions: findByStatus("ACTIVE")
  • Single-table CRUD operations
  • Simple AND conditions: findByUserIdAndStatus(id, status)

Use @Mapper (custom SQL) when:

  • LIKE / partial match: WHERE name LIKE '%keyword%'
  • BETWEEN / range: WHERE created_at BETWEEN? AND?
  • JOIN: Any query involving multiple tables
  • Subqueries: WHERE id IN (SELECT...)
  • Aggregation: GROUP BY, HAVING, SUM(), COUNT() per group
  • OR conditions: WHERE status =? OR role =?
  • Complex sorting: Sorting by computed/joined columns
  • UNION: Combining result sets

CRITICAL: Query derivation only supports exact-match = with And combinator. It does NOT support LIKE, BETWEEN, OR, IN, >, <, JOIN, or any other SQL operator. When these are needed, immediately create a @Mapper interface — do not attempt to work around Repository limitations.

Custom Mapper with Pagination (XPagination)

Custom @Mapper methods integrate with XPagination seamlessly. The framework's XResultInterceptor automatically intercepts the query to handle COUNT, ORDER BY, and LIMIT — you only write the base SELECT.

Rules for custom mapper pagination:

  1. Include XPagination as a parameter
  2. Return XPage<T> as the return type (entity type is inferred from the generic parameter)
  3. Write only the base SELECT — do NOT add ORDER BY or LIMIT in your SQL
@Mapper
public interface UserMapper {

    // LIKE search with pagination
    @Select("SELECT * FROM users WHERE name LIKE CONCAT('%', #{keyword}, '%')")
    XPage<User> searchByName(XPagination pagination, @Param("keyword") String keyword);

    // BETWEEN with pagination
    @Select("SELECT * FROM users WHERE created_at BETWEEN #{from} AND #{to}")
    XPage<User> findByDateRange(XPagination pagination,
                                @Param("from") LocalDateTime from,
                                @Param("to") LocalDateTime to);

    // JOIN with pagination
    @Select("SELECT u.*, d.name AS department_name FROM users u " +
            "INNER JOIN departments d ON u.department_id = d.id " +
            "WHERE d.status = #{status}")
    XPage<UserWithDepartment> findUsersWithDepartment(XPagination pagination,
                                                      @Param("status") String status);

    // Multiple conditions (OR, IN)
    @Select("<script>" +
            "SELECT * FROM users WHERE status IN " +
            "<foreach item='s' collection='statuses' open='(' separator=',' close=')'>" +
            "#{s}" +
            "</foreach>" +
            "</script>")
    XPage<User> findByStatuses(XPagination pagination,
                               @Param("statuses") List<String> statuses);

    // Without pagination — just return List<T> (no XPagination needed)
    @Select("SELECT * FROM users WHERE email LIKE CONCAT('%', #{keyword}, '%')")
    List<User> searchByEmail(@Param("keyword") String keyword);

    // Aggregation (non-paginated, returns custom projection)
    @Select("SELECT department_id, COUNT(*) as user_count FROM users GROUP BY department_id")
    List<Map<String, Object>> countByDepartment();
}

Controller Pattern: Repository + Mapper Together

A typical controller uses Repository for simple operations and Mapper for complex queries:

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/users")
public class UserController {

    private final UserRepository userRepository;  // simple CRUD
    private final UserMapper userMapper;           // complex queries

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userRepository.findOne(id);
    }

    @GetMapping
    public XPage<User> searchUsers(@XPaginationDefault XPagination pagination,
                                   @RequestParam(required = false) String keyword) {
        if (keyword != null) {
            return userMapper.searchByName(pagination, keyword);
        }
        return userRepository.findAll(pagination);
    }

    @PostMapping
    public User createUser(@RequestBody User user) {
        userRepository.save(user);
        return user;
    }
}

Error Code System

ErrorCode Record

public record ErrorCode(String code, String messageKey) {}

Built-in Exceptions

CodeExceptionHTTPDescription
1UnAuthorizedException401Authentication required
2UnAuthorizedException401Invalid credentials
3UnAuthorizedException401Token expired
11InvalidRequestParameterException400Invalid parameter
12InvalidRequestParameterException400Request body not found
13InvalidRequestParameterException400Method not supported
100NotFoundException404Not found
900UnavailableServerException504Server unavailable
999UnknownServerException500Unknown error

Custom Exception

public class UserException extends XRestException {
    public static final ErrorCode DUPLICATE_EMAIL = new ErrorCode("2001", "user.error.duplicate-email");
    public static final ErrorCode INACTIVE_ACCOUNT = new ErrorCode("2002", "user.error.inactive-account");

    public UserException(ErrorCode error) {
        super(HttpStatus.BAD_REQUEST, error);
    }

    public UserException(ErrorCode error, String description) {
        super(HttpStatus.BAD_REQUEST, error, description);
    }
}

// Usage
throw new UserException(UserException.DUPLICATE_EMAIL, "alice@example.com already exists");

i18n Messages

# messages.properties
user.error.duplicate-email=Email already exists.

# messages_ko.properties
user.error.duplicate-email=이미 존재하는 이메일입니다.

Error Response Format

{
    "code": "2001",
    "message": "Email already exists.",
    "description": "alice@example.com already exists",
    "data": null
}

Declarative REST Client

Requires @XRestServiceScan on application class.

Direct Mode vs Gateway Mode

// Direct Mode — host specified → URL: {host}{path}
@XRestService(value = "user-service", host = "${USER_SERVICE_HOST:http://localhost:8081}")
public interface UserServiceClient { ... }

// Gateway Mode — host omitted → URL: {gatewayHost}/{serviceName}/{version}{path}
@XRestService(value = "user-service", version = "v1")
public interface UserServiceClient { ... }

Parameter Annotations

@XRestService(value = "order-service", host = "${ORDER_SERVICE_HOST}")
public interface OrderServiceClient {

    @XRestAPI(value = "/orders/{id}", method = XHttpMethod.GET)
    Order getOrder(@PathVariable("id") Long id);

    @XRestAPI(value = "/orders", method = XHttpMethod.POST)
    Order createOrder(@RequestBody OrderCreateRequest request);

    @XRestAPI(value = "/orders", method = XHttpMethod.GET)
    List<Order> search(@RequestParam("status") String status,
                       @RequestParam("keyword") String keyword);

    @XRestAPI(value = "/orders", method = XHttpMethod.GET)
    List<Order> getOrders(@RequestHeader("X-Tenant-Id") String tenantId);

    // XPagination auto-converted → ?page=1&size=20&sort=createdAt,DESC
    @XRestAPI(value = "/orders", method = XHttpMethod.GET)
    XPage<Order> listOrders(XPagination pagination);

    @XRestAPI(value = "/orders/{id}", method = XHttpMethod.PUT)
    Order updateOrder(@PathVariable("id") Long id,
                      @RequestBody OrderUpdateRequest request,
                      @RequestHeader("Access-Token") String token);
}

Error Handling

try {
    Order order = orderClient.getOrder(id);
} catch (XRestException e) {
    e.getStatus();       // Original HTTP status (400, 404, 500, etc.)
    e.getCode();         // Error code from ApiError
    e.getMessage();      // Error message
    e.getDescription();  // Additional description
}

JSON Date Format

The framework ObjectMapper uses yyyy-MM-dd HH:mm:ss (NOT ISO 8601):

// ✓ "2024-01-15 14:30:00"
// ✗ "2024-01-15T14:30:00Z"

XWebClient (RestClient-based Alternative)

For programmatic HTTP calls (not declarative proxy). Two registration options:

Option 1: Declarative Bean Registration via Properties

# application.properties — each entry creates a named XWebClient bean
axim.web-client.services.userClient=http://user-service:8080
axim.web-client.services.orderClient=http://order-service:8080
@Service
@RequiredArgsConstructor
public class ExternalApiService {

    @Qualifier("userClient")
    private final XWebClient userClient;

    public User getUser(Long id) {
        return userClient.get("/users/{id}", User.class, id);
    }
}

Option 2: Programmatic via XWebClientFactory

@Service
@RequiredArgsConstructor
public class ExternalApiService {

    private final XWebClientFactory webClientFactory;

    public User getUser(Long id) {
        XWebClient client = webClientFactory.create("http://external-api.com");
        return client.get("/users/{id}", User.class, id);
    }
}

API Reference

// Simple API
client.get("/users/{id}", User.class, id);
client.post("/users", body, User.class);
client.put("/users/{id}", body, User.class, id);
client.delete("/users/{id}", Void.class, id);

// Generic types
client.get("/users", new ParameterizedTypeReference<List<User>>() {});

// Builder API
client.spec()
        .get("/users?keyword=" + keyword)
        .header("X-API-Key", "my-key")
        .body(requestBody)
        .retrieve(new ParameterizedTypeReference<List<User>>() {});
Feature@XRestServiceXWebClient
StyleInterface + annotationsDirect method calls
Bean creation@XRestServiceScanProperties or XWebClientFactory
Best forInternal microservice callsExternal API, dynamic URLs
PaginationAuto XPagination → query paramsManual query string
## Session / Token Authentication

The framework provides a built-in token system. **This is NOT JWT** — uses custom `Base64(payload).HmacSHA256(signature)` format.

### Custom Session Data

@Data public class UserSession extends SessionData { /** 사용자 고유 ID */ private Long userId; /** 사용자 이름 */ private String userName; /** 사용자 권한 목록 (예시: ["ADMIN", "USER"]) */ private List<String> roles; }


`SessionData` base fields (auto-managed): `sessionId`, `createDate` (format: `yyyyMMddHHmmss`)

### Generating Tokens (Login)

@RestController @RequiredArgsConstructor @RequestMapping("/api/v1/auth") public class AuthController {

private final XAccessTokenParseHandler tokenHandler;

@PostMapping("/login") public Map<String, String> login(@RequestBody LoginRequest request) { // Authenticate user... UserSession session = new UserSession(); session.setSessionId(UUID.randomUUID().toString()); session.setUserId(authenticatedUser.getId()); session.setUserName(authenticatedUser.getName()); session.setRoles(authenticatedUser.getRoles());

String token = tokenHandler.generateAccessToken(session); return Map.of("accessToken", token); } }


### Using Session in Controllers

Session auto-resolved from `Access-Token` HTTP header:

@GetMapping("/me") public UserProfile getMyProfile(UserSession session) { // If token missing → 401 (NOT_FOUND_ACCESS_TOKEN) // If token invalid → 401 (INVALID_ACCESS_TOKEN) // If token expired → 401 (EXPIRE_ACCESS_TOKEN) return userService.getProfile(session.getUserId()); }


### Session Configuration

MUST be set in production — without it, tokens can be forged

axim.rest.session.secret-key=your-secret-key axim.rest.session.token-expire-days=90 # Expiration in days (default: 90)


**SECURITY WARNING:** If `secret-key` is omitted, tokens have NO signature verification — anyone can forge a valid session token by crafting Base64-encoded JSON. ALWAYS set a strong secret key in production.

## i18n Message Source

Hierarchical message resolution: Application messages override framework defaults.

messages.properties (your app) → overrides → framework-messages.properties (built-in)


Built-in framework messages:

server.http.error.invalid-parameter=Invalid request parameter. server.http.error.required-auth=Authentication required. server.http.error.invalid-auth=Invalid authentication credentials. server.http.error.expire-auth=Authentication expired. server.http.error.notfound-api=API not found. server.http.error.server-error=Internal server error.


If key not found in any source, the key string itself is returned (no exception).

## Security Warnings

### 1. Session Secret Key — Token Forgery Risk

Without `axim.rest.session.secret-key`, token payload is Base64-decoded without integrity check. **Anyone can forge a valid session token.**

✗ DANGEROUS — attacker creates Base64({"userId":1}) → valid token

axim.rest.session.secret-key=

✓ REQUIRED for production

axim.rest.session.secret-key=a-strong-random-secret-key-at-least-32-chars


Rules: Always set in production. Minimum 32 chars. Never commit to source control — use environment variables.

### 2. Request Body Logging in Non-prod Profile

`XRequestFilter` logs full request bodies when profile is NOT `prod`. **Passwords and sensitive fields are logged as-is** (no field-level masking). HTTP headers like `Authorization` are masked, but request body fields are NOT.

✗ Non-prod → logs plaintext passwords from login endpoints

spring.profiles.active=dev

✓ Prod → disables body logging and stack traces in errors

spring.profiles.active=prod


### 3. Demo Credentials

Demo module contains hardcoded DB credentials for local development only. NEVER copy into production config.

## Complete Service Layer Example

@Service @RequiredArgsConstructor public class UserService { private final UserRepository userRepository;

public User create(User user) { userRepository.save(user); return user; }

public User partialUpdate(Long id, UserUpdateRequest req) { User user = new User(); user.setId(id); user.setName(req.getName()); userRepository.modify(user); // selective UPDATE return userRepository.findOne(id); }

public XPage<User> list(int page, int size) { XPagination pagination = new XPagination(); pagination.setPage(page); pagination.setSize(size); pagination.addOrder(new XOrder("createdAt", XDirection.DESC)); return userRepository.findAll(pagination); } }


## Coding Conventions

### MANDATORY: Document All Member Variables and Enum Items

Every member variable in Entity, DTO, Request, Response, VO classes and every enum item MUST have a detailed Javadoc comment. Include: purpose, example values, format/pattern rules, constraints, and allowed values.

#### Entity Example

@Data @XEntity("orders") public class Order {

/** 주문 고유 식별자 (Auto Increment) */ @XColumn(isPrimaryKey = true, isAutoIncrement = true) private Long id;

/** * 주문 번호 * - 형식: "ORD-{yyyyMMdd}-{6자리 시퀀스}" * - 예시: "ORD-20240115-000001" * - UNIQUE 제약조건 적용 */ private String orderNo;

/** * 주문 상태 * - "PENDING": 결제 대기 * - "PAID": 결제 완료 * - "SHIPPED": 배송 중 * - "DELIVERED": 배송 완료 * - "CANCELLED": 주문 취소 * @see OrderStatus */ private String status;

/** * 주문 총 금액 (단위: 원, KRW) * - 소수점 2자리까지 허용 * - 음수 불가 * - 예시: 15000.00 */ private BigDecimal totalAmount;

/** * 주문자 ID (users 테이블 FK) * - NULL 불가 */ private Long userId;

/** 주문 생성 일시 (INSERT 시 자동 설정) */ @XDefaultValue(value = "NOW()", isDBValue = true) private LocalDateTime createdAt;

/** 주문 수정 일시 (UPDATE 시 자동 갱신) */ @XDefaultValue(updateValue = "NOW()", isDBValue = true) private LocalDateTime updatedAt; }


#### DTO / Request Example

@Data public class OrderCreateRequest {

/** * 주문할 상품 ID 목록 * - 최소 1개 이상 필수 * - 예시: [1, 2, 3] */ @NotEmpty private List<Long> productIds;

/** * 배송지 주소 * - 전체 도로명 주소 (우편번호 제외) * - 예시: "서울특별시 강남구 테헤란로 123 4층" * - 최대 200자 */ @NotBlank @Size(max = 200) private String shippingAddress;

/** * 배송 메모 (선택사항) * - 예시: "부재 시 경비실에 맡겨주세요" * - 최대 500자, NULL 허용 */ @Size(max = 500) private String deliveryNote;

/** * 결제 수단 코드 * - "CARD": 신용/체크카드 * - "BANK": 무통장입금 * - "KAKAO": 카카오페이 * - "NAVER": 네이버페이 */ @NotBlank private String paymentMethod; }


#### Enum Example

public enum OrderStatus {

/** 결제 대기 — 주문이 생성되었으나 결제가 완료되지 않은 상태 */ PENDING,

/** 결제 완료 — 결제가 확인되어 상품 준비 중인 상태 */ PAID,

/** 배송 중 — 택배사에 인계되어 배송이 진행 중인 상태 */ SHIPPED,

/** 배송 완료 — 수령인이 상품을 수령한 상태 */ DELIVERED,

/** 주문 취소 — 고객 요청 또는 시스템에 의해 취소된 상태. 환불 처리 필요 */ CANCELLED }


#### Comment Rules

| Rule | Description |
| --- | --- |
| All member variables | Must have `/** */` Javadoc comment describing purpose |
| Example values | Include concrete examples with `예시:` or `e.g.` prefix |
| Format/Pattern | Document format rules (e.g., `"ORD-{yyyyMMdd}-{seq}"`) |
| Allowed values | List all valid values for string-coded fields |
| Constraints | Note NOT NULL, UNIQUE, max length, range limits |
| Enum items | Each item must have a comment explaining the state/meaning |
| FK references | Note the referenced table (e.g., "users 테이블 FK") |
| Units | Specify units for numeric fields (e.g., 원, KRW, %, 초) |

### @XColumn Usage Rules

`@XColumn` is NOT required on every field. The framework auto-maps fields using camelCase → snake_case. Only use `@XColumn` when you need to set specific options.

@Data @XEntity("users") public class User {

@XColumn(isPrimaryKey = true, isAutoIncrement = true) private Long id; // ✓ @XColumn needed — primary key

private String email; // ✓ auto-mapped to "email" — NO @XColumn needed private String userName; // ✓ auto-mapped to "user_name" — NO @XColumn needed private Integer loginCount; // ✓ auto-mapped to "login_count" — NO @XColumn needed

@XColumn("usr_email_addr") private String emailAddr; // ✓ @XColumn needed — custom column name

@XColumn(insert = false, update = false) private String readOnly; // ✓ @XColumn needed — read-only field

@XColumn(update = false) private String createdBy; // ✓ @XColumn needed — immutable after creation }


| Situation | @XColumn | Example |
| --- | --- | --- |
| Primary Key | **Required** | `@XColumn(isPrimaryKey = true, isAutoIncrement = true)` |
| Composite PK field | **Required** | `@XColumn(isPrimaryKey = true)` |
| Regular field (camelCase→snake_case) | **Omit** | `private String userName;` → `user_name` |
| Custom column name | **Required** | `@XColumn("usr_nm")` |
| Read-only / insert-only / update-only | **Required** | `@XColumn(insert = false, update = false)` |
| Exclude from DB entirely | Use `@XIgnoreColumn` | `@XIgnoreColumn private String temp;` |

### @XDefaultValue Pitfall: isDBDefaultUsed Defaults to true

CRITICAL: `isDBDefaultUsed` defaults to `true`. This means `@XDefaultValue(value = "ACTIVE")` will **omit the column from INSERT** and use DB DEFAULT — the `value` is silently ignored!

// ✗ WRONG — "ACTIVE" is IGNORED because isDBDefaultUsed defaults to true @XDefaultValue(value = "ACTIVE") private String status;

// ✓ CORRECT — must set isDBDefaultUsed = false for literal values @XDefaultValue(value = "ACTIVE", isDBDefaultUsed = false) private String status;

// ✓ CORRECT — DB expression @XDefaultValue(value = "NOW()", isDBValue = true) private LocalDateTime createdAt;

// ✓ CORRECT — intentionally use DB DEFAULT @XDefaultValue(isDBDefaultUsed = true) private String region;

// ✓ CORRECT — auto-set on UPDATE only @XDefaultValue(updateValue = "NOW()", isDBValue = true) private LocalDateTime updatedAt;


## Common Pitfalls

### 1. Column Names vs Field Names

Query derivation and `findWhere()` use **Java field names** (camelCase), NOT column names (snake_case).

// ✗ WRONG User findByUser_name(String name); userRepository.findWhere(Map.of("user_name", "Alice"));

// ✓ CORRECT User findByUserName(String name); userRepository.findWhere(Map.of("userName", "Alice"));


### 2. findBy Return Type Determines Behavior

User findByEmail(String email); // → LIMIT 1 (single result) List<User> findByEmail(String email); // → no LIMIT (all matches)


### 3. update() Overwrites with NULL

User user = new User(); user.setId(1L); user.setName("Alice"); // email and status are null

userRepository.update(user); // ✗ Sets email=NULL, status=NULL in DB! userRepository.modify(user); // ✓ Only sets name='Alice', others preserved


### 4. ORDER BY / LIMIT in Custom Mapper SQL

XResultInterceptor handles pagination SQL automatically. Never add ORDER BY or LIMIT.

// ✗ WRONG @Select("SELECT * FROM users WHERE status = #{status} ORDER BY created_at DESC LIMIT 20") XPage<User> findByStatus(XPagination pagination, @Param("status") String status);

// ✓ CORRECT — only the base SELECT @Select("SELECT * FROM users WHERE status = #{status}") XPage<User> findByStatus(XPagination pagination, @Param("status") String status);


### 5. Never Create Custom Pagination Classes

// ✗ WRONG public class PageRequest { int page; int size; } public class PageResponse<T> { List<T> items; long total; }

// ✓ CORRECT — always use framework classes XPagination pagination = new XPagination(); XPage<User> result = userRepository.findAll(pagination);


### 6. Empty Map in findWhere()

userRepository.findWhere(Map.of()); // ✗ Throws exception userRepository.findAll(); // ✓ Use findAll() instead userRepository.findWhere(Map.of("status", "ACTIVE")); // ✓ Non-empty map


### 7. Query Derivation Method Name Parsing

`And` only splits when preceded by lowercase and followed by uppercase.

User findByBrandName(String brandName); // → single field "brandName" List<User> findByStatusAndName(String status, String name); // → two fields "status", "name"

// ✗ Parameter count must match parsed field count List<User> findByStatusAndName(String status); // WRONG — 2 fields but 1 param


## Architecture

Application Code Framework Internals ──────────────── ─────────────────── @XRepository XRepositoryBeanScanner UserRepository ↓ extends IXRepository<K, T> XRepositoryProxyFactoryBean ↓ ↓ (JDK Dynamic Proxy) XRepositoryProxy (InvocationHandler) ↓ ↓ CommonMapper (@Mapper) ↓ CrudSqlProvider (SQL Generation + Cache) ↓ XResultInterceptor (Pagination, Result Mapping)

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

32.38%
按下载量换算22

Claude

31.17%
按下载量换算21

Cursor

17.24%
按下载量换算12

Gemini CLI

9.84%
按下载量换算7

安全审计

Gen Agent Trust Hub

未通过

Socket

通过

Snyk

可疑

权限和风险

敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills