Redkale

##

Redkale 入门 Convert 组件 Service 组件 Source 组件 Net 组件 Watch 组件 Redkale 插件 技术文章

### REST插件介绍

org.redkalex.rest 是基于HTTP服务的REST插件，提供了很简单的REST服务接口方便开发。REST根据加载的Service组件自动生成对应的HttpServlet。其接口比Spring Boot之类的REST服务框架要简化得多，不需要进行注解配置也可以按REST服务加载。

### 快速上手

为了让REST生效，必须配置 application.xml 中 HTTP 的 <server> 节点的interceptor属性值为 org.redkalex.rest.RestNodeInterceptor 。
同时必须在<server> 节点 里增加 <rest> 子节点。配置介绍如下：

    <application port="5001">

        <resources>
            <!-- ... -->
        </resources>
        <!--
               interceptor: 值必须要是 org.redkalex.rest.RestNodeInterceptor，且只能配置在protocol="HTTP"的server节点上，REST服务才会生效。
        -->
        <server protocol="HTTP" port="6060" interceptor="org.redkalex.rest.RestNodeInterceptor">
            <!--
               REST的核心配置项
               base:     REST服务的BaseServlet，必须是 org.redkalex.rest.RestHttpServlet 的子类，该属性值没有默认值，必须指定。
               autoload：默认值"true"  默认值. 加载当前server所能使用的Servce对象;
               mustsign：默认值"true" 是否只加载标记为RestController的Service类，默认加载所有可用Service
               includes：当autoload="true"， 拉取类名与includes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
               excludes：当autoload="true"， 排除类名与excludes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
            -->
            <rest base="org.redkalex.test.rest.SimpleRestServlet" mustsign="true" autoload="true" includes="" excludes="">
                <!--
                   value:  Service类名，列出的表示必须被加载的Service对象。
                -->
                <service value="com.xxx.XXXXService"/>
            </rest>
            <!-- 其他配置... -->
        </server>
    </application>

通常配置都需要编写一个 org.redkalex.rest.RestHttpServlet 子类，主要用于获取当前用户信息和鉴权，且必须指定具体的User对象类。开发者的实现类可以参考 redkale-demo 中的BaseServlet类，以下是一个简单的范例:

    public class SimpleRestServlet extends RestHttpServlet<UserInfo> {

        protected static final RetResult RET_UNLOGIN = RetCodes.retResult(RetCodes.RET_USER_UNLOGIN);

        protected static final RetResult RET_AUTHILLEGAL = RetCodes.retResult(RetCodes.RET_USER_AUTH_ILLEGAL);

        @Resource
        private UserService userService;

        //获取当前用户信息
        @Override
        protected UserInfo currentUser(HttpRequest req) throws IOException {
            String sessionid = req.getSessionid(false);
            if (sessionid == null || sessionid.isEmpty()) return null;
            return userService.current(sessionid);
        }

        //普通鉴权
        @Override
        public boolean authenticate(int module, int actionid, HttpRequest request, HttpResponse response) throws IOException {
            UserInfo info = currentUser(request);
            if (info == null) {
                response.finishJson(RET_UNLOGIN);
                return false;
            } else if (!info.checkAuth(module, actionid)) {
                response.finishJson(RET_AUTHILLEGAL);
                return false;
            }
            return true;
        }

    }

编写完 org.redkalex.rest.RestHttpServlet 子类后就需要对Service进行设置，设置需要三大注解：@RestController、@RestMapping、@RestParam。
@RestController ：

    /**
     * 只能依附在Service类上，value默认为Service的类名去掉Service字样的字符串 (如HelloService，的默认路径为 hello)。
     */
    @Target({TYPE})
    public @interface RestController {

        boolean ignore() default false; //是否屏蔽该类的转换

        String value() default ""; //模块名, 只能是模板名,不能含特殊字符

        boolean repair() default true; //同@WebServlet的repair属性

        int module() default 0; //模块ID值，鉴权时用到

    }

@RestMapping ：

    /**
     * 只能依附在Service实现类的public方法上
     * value默认为"/" + Service的类名去掉Service及后面字样的小写字符串 (如HelloService，的默认路径为/hello)。
     */
    @Target({METHOD})
    public @interface RestMapping {

        boolean ignore() default false; //是否屏蔽该方法的转换

        //请求的方法名, 不能含特殊字符
        //默认为方法名的小写(若方法名以createXXX、updateXXX、deleteXXX、queryXXX、findXXX且XXXService为Service的类名将只截取XXX之前)
        String name() default "";

        boolean authignore() default true; //是否跳过鉴权，默认跳过

        int actionid() default 0; //操作ID值，鉴权时用到

        String contentType() default "";  //设置Response的ContentType 默认值为 text/plain; charset=utf-8

        String jsvar() default ""; //以application/javascript输出对象是指明js的对象名，该值存在时则忽略contentType()的值
    }

@RestParam ：

    /**
     * 只能依附在Service类的方法的参数上
     */
    @Target({PARAMETER})
    public @interface RestParam {

        String value(); //参数名

        /**
         * 参数是否从header取， 默认使用 request.getJsonParameter， 设置为true则使用 request.getJsonHeader 取值
         *
         * @return 是否从header取
         */
        boolean header() default false;
    }

REST的设置方式有两大种: 一种是采用默认REST注解，一种是显式的设置。
第一种方式需要开发者对Service中的方法命名需要遵循一定规则, 如下范例，模块为Hello，Service类命名为HelloService，增删改查的方法采用createHello、deleteHello、updateHello、queryHello或其他xxxHello命名。REST插件加载任何没有标记@RestController、@RestMapping、@RestParam 的Service将按照一定规则生成默认值：
1、@RestController.value() 默认值为Service类名的小写化并去掉service及后面字样, 视为模块名
2、@RestMapping.name() 默认值为Service的方法名小写化并去掉模块名字样
3、@RestParam.value() 如果方法名以find、delete开头且方法的参数只有一个且参数类型是基本数据类型或String，则默认值为"#"；若使用Java 8中带上 -parameters 编译项的新特性，默认值为参数名, 若没使用新特性则采用bean、bean2、bean3...的命名规则。

    /**
     * 类说明:
     * Flipper : Source组件中的翻页对象， 只要Service方法中的参数类与该类相同，则不需要设定 @RestParam
     * UserInfo ：当前用户类， 只要Service方法中的参数类与该类相同，则不需要设定 @RestParam
     * HelloEntity: Hello模块的实体类
     * HelloBean: Hellow模块实现FilterBean的过滤Bean类
     *
     */
    @RestController(value = "hello", module = 0, repair = true, ignore = false)
    public class HelloService implements Service {

        @Resource
        private DataSource source;

        //增加记录
        @RestMapping(name = "create", authignore = true)
        public RetResult<HelloEntity> createHello(UserInfo info, @RestParam("bean") HelloEntity entity) { //通过 /hello/create?bean={...} 增加对象
            entity.setCreator(info == null ? 0 : info.getUserid()); //设置当前用户ID
            entity.setCreatetime(System.currentTimeMillis());
            source.insert(entity);
            return new RetResult<>(entity);
        }

        //删除记录
        @RestMapping(name = "delete", authignore = true)
        public void deleteHello(@RestParam("#") int id) { //通过 /hello/delete/1234 删除对象
            source.delete(HelloEntity.class, id);
        }

        //修改记录
        @RestMapping(name = "update", authignore = true)
        public void updateHello(@RestParam("bean") HelloEntity entity) { //通过 /hello/update?bean={...} 修改对象
            entity.setUpdatetime(System.currentTimeMillis());
            source.update(entity);
        }

        //查询列表
        @RestMapping(name = "query", authignore = true)
        public Sheet<HelloEntity> queryHello(@RestParam("bean") HelloBean bean, Flipper flipper) { //通过 /hello/query/offset:0/limit:20?bean={...} 查询列表
            return source.querySheet(HelloEntity.class, flipper, bean);
        }

        //查询单个
        @RestMapping(name = "find", authignore = true)
        public HelloEntity findHello(@RestParam("#") int id) {  //通过 /hello/find/1234 查询对象
            return source.find(HelloEntity.class, id);
        }
    }

根据默认命名规则可以看出，以上范例生成的RestServlet与去掉所有@RestController、@RestMapping、@RestParam后的Service生成的是完全相同的。 REST插件根据Service会动态生成HttpServlet，以上范例生成的HttpServlet如下：

    @WebServlet(value = {"/hello/*"}, repair = true)
    public class _DynHelloRestServlet extends SimpleRestServlet {

        @Resource
        private HelloService _service;

        @AuthIgnore
        @WebAction(url = "/hello/create")
        public void create(HttpRequest req, HttpResponse resp) throws IOException {
            HelloEntity bean = req.getJsonParameter(HelloEntity.class, "bean");
            UserInfo user = currentUser(req);
            RetResult<HelloEntity> result = _service.createHello(user, bean);
            resp.finishJson(result);
        }

        @AuthIgnore
        @WebAction(url = "/hello/delete/")
        public void delete(HttpRequest req, HttpResponse resp) throws IOException {
            int id = Integer.parseInt(req.getRequstURILastPath());
            _service.deleteHello(id);
            resp.finishJson(RetResult.success());
        }

        @AuthIgnore
        @WebAction(url = "/hello/update")
        public void update(HttpRequest req, HttpResponse resp) throws IOException {
            HelloEntity bean = req.getJsonParameter(HelloEntity.class, "bean");
            _service.updateHello(bean);
            resp.finishJson(RetResult.success());
        }

        @AuthIgnore
        @WebAction(url = "/hello/query")
        public void query(HttpRequest req, HttpResponse resp) throws IOException {
            HelloBean bean = req.getJsonParameter(HelloBean.class, "bean");
            Flipper flipper = req.getFlipper();
            Sheet<HelloEntity> result = _service.queryHello(bean, flipper);
            resp.finishJson(result);
        }

        @AuthIgnore
        @WebAction(url = "/hello/find/")
        public void find(HttpRequest req, HttpResponse resp) throws IOException {
            int id = Integer.parseInt(req.getRequstURILastPath());
            HelloEntity bean = _service.findHello(id);
            resp.finishJson(bean);
        }
    }

REST的第二种设置方式是显式的设置, 看如下范例：

    /**
     * 类说明:
     * Flipper : Source组件中的翻页对象
     * UserInfo ：当前用户类
     * HelloEntity: Hello模块的实体类
     * HelloBean: Hellow模块实现FilterBean的过滤Bean类
     *
     */
    public class HelloService implements Service {

        @Resource
        private DataSource source;

        //增加记录
        public RetResult<HelloEntity> createHello(UserInfo info, HelloEntity entity) {
            entity.setCreator(info == null ? 0 : info.getUserid()); //设置当前用户ID
            entity.setCreatetime(System.currentTimeMillis());
            source.insert(entity);
            return new RetResult<>(entity);
        }

        //删除记录
        public void deleteHello(int id) { //通过 /hello/delete/1234 删除对象
            source.delete(HelloEntity.class, id);
        }

        //修改记录
        public void updateHello(HelloEntity entity) { //通过 /hello/update?bean={...} 修改对象
            entity.setUpdatetime(System.currentTimeMillis());
            source.update(entity);
        }

        //修改记录
        @RestMapping(name = "partupdate")
        public void updateHello(HelloEntity entity, @RestParam("cols") String[] columns) { //通过 /hello/update?bean={...} 修改对象
            entity.setUpdatetime(System.currentTimeMillis());
            source.updateColumns(entity, columns);
        }

        //查询Sheet列表
        public Sheet<HelloEntity> queryHello(HelloBean bean, Flipper flipper) { //通过 /hello/query/offset:0/limit:20?bean={...} 查询Sheet列表
            return source.querySheet(HelloEntity.class, flipper, bean);
        }

        //查询List列表
        @RestMapping(name = "list")
        public List<HelloEntity> queryHello(HelloBean bean) { //通过 /hello/list?bean={...} 查询List列表
            return source.queryList(HelloEntity.class, bean);
        }

        //查询单个
        @RestMapping(name = "find")
        @RestMapping(name = "jsfind", jsvar = "varhello")
        public HelloEntity findHello(@RestParam("#") int id) {  //通过 /hello/find/1234 查询对象
            return source.find(HelloEntity.class, id);
        }
    }

转换为RestHttpServlet如下：

    @WebServlet(value = {"/hello/*"}, repair = true)
    public class _DynHelloRestServlet extends SimpleRestServlet {

        @Resource
        private HelloService _service;

        @AuthIgnore
        @WebAction(url = "/hello/create")
        public void create(HttpRequest req, HttpResponse resp) throws IOException {
            HelloEntity bean = req.getJsonParameter(HelloEntity.class, "bean");
            UserInfo user = currentUser(req);
            RetResult<HelloEntity> result = _service.createHello(user, bean);
            resp.finishJson(result);
        }

        @AuthIgnore
        @WebAction(url = "/hello/delete/")
        public void delete(HttpRequest req, HttpResponse resp) throws IOException {
            int id = Integer.parseInt(req.getRequstURILastPath());
            _service.deleteHello(id);
            resp.finishJson(RetResult.success());
        }

        @AuthIgnore
        @WebAction(url = "/hello/update")
        public void update(HttpRequest req, HttpResponse resp) throws IOException {
            HelloEntity bean = req.getJsonParameter(HelloEntity.class, "bean");
            _service.updateHello(bean);
            resp.finishJson(RetResult.success());
        }

        @AuthIgnore
        @WebAction(url = "/hello/partupdate")
        public void partupdate(HttpRequest req, HttpResponse resp) throws IOException {
            HelloEntity bean = req.getJsonParameter(HelloEntity.class, "bean");
            String[] cols = req.getJsonParameter(String[].class, "cols");
            _service.updateHello(bean, cols);
            resp.finishJson(RetResult.success());
        }

        @AuthIgnore
        @WebAction(url = "/hello/query")
        public void query(HttpRequest req, HttpResponse resp) throws IOException {
            HelloBean bean = req.getJsonParameter(HelloBean.class, "bean");
            Flipper flipper = req.getFlipper();
            Sheet<HelloEntity> result = _service.queryHello(bean, flipper);
            resp.finishJson(result);
        }

        @AuthIgnore
        @WebAction(url = "/hello/list")
        public void list(HttpRequest req, HttpResponse resp) throws IOException {
            HelloBean bean = req.getJsonParameter(HelloBean.class, "bean");
            List<HelloEntity> result = _service.queryHello(bean);
            resp.finishJson(result);
        }

        @AuthIgnore
        @WebAction(url = "/hello/find/")
        public void find(HttpRequest req, HttpResponse resp) throws IOException {
            int id = Integer.parseInt(req.getRequstURILastPath());
            HelloEntity bean = _service.findHello(id);
            resp.finishJson(bean);
        }

        @AuthIgnore
        @WebAction(url = "/hello/jsfind/")
        public void jsfind(HttpRequest req, HttpResponse resp) throws IOException {
            int id = Integer.parseInt(req.getRequstURILastPath());
            HelloEntity bean = _service.findHello(id);
            resp.finishJsResult("varhello", bean);
        }
    }

REST插件让开发者省去了编写HttpServlet过程，让开发更加敏捷。