atjiu / pybbs

更实用的Java开发的社区(论坛),Better use of Java development community (forum)
GNU Affero General Public License v3.0
1.85k stars 707 forks source link

ApiController 实现方式上的讨论 #25

Closed TakWolf closed 7 years ago

TakWolf commented 7 years ago

https://github.com/tomoya92/pybbs/blob/master/src/main/java/cn/tomoya/module/api/controller/UserApiController.java

你线上跑的是这个2.4的spring版本吗? 怎么感觉功能对不上啊!!

atjiu commented 7 years ago

线上跑的还是2.3的代码,2.4的第三方登录还没写,我想等2.4里的第三方登录集成好了,再把2.3数据库里的用户数据都整理一下,然后再用2.4的代码

TakWolf commented 7 years ago

我修改了问题,稍后展开

TakWolf commented 7 years ago

2.4版本中 ApiController 部分感觉现有实现方式不是十分优雅。不清楚这么做是不是为了兼容 2.3 jfinal 的版本。jfinal由于他自己的设计原因,对api类的支持并不标准,换句话说,不支持通用的restful习惯。

既然2.4已经全面切换到spring boot了,本来就是破坏性升级, 就没必要兼容了。Api部分可以设计的更符合restful习惯

下面详细说明

atjiu commented 7 years ago

api会重新写的,不会兼容2.3,这个放心好了,我也非常喜欢restful风格的路由

TakWolf commented 7 years ago

现有的实现方式: https://github.com/tomoya92/pybbs/blob/master/src/main/java/cn/tomoya/module/api/controller/NotificationApiController.java#L29

 @RequestMapping(value = "/notRead", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public String notRead() {
        User user = getUser();
        if(user == null) {
            return JsonUtil.error("用户未登录");
        } else {
            return JsonUtil.success(notificationService.countByTargetUserAndIsRead(user, false));
        }
    }

方法上添加了@ResponseBody保证返回实体,而非模板,但是返回类型是string 而地下实现上是通过JsonUtil生成的json字符串。 因为没有走框架的response机制,需要显示标注 produces = MediaType.APPLICATION_JSON 指定 content-type 没有使用http状态码(全是200) 鉴权使用了 security,但是未登录状态确实手动判断的

TakWolf commented 7 years ago

@tomoya92 我又重新看了一下,api这块你是不是还没完成?

这个问题先留着等你完成之后在讨论吧

atjiu commented 7 years ago

api这块确实是还没写的,只是通知数我用的是接口

接口这块你有什么建义吗?可以先说说,我可以参照着开发

TakWolf commented 7 years ago

ApiController 这里,Action直接返回实体,不要手动生成json举例,例如上面的通知接口这么写:

 @GetMapping(value = "/notRead")
 @ResponseBody
 public Result<Long> notRead() throw  ApiException {
        User user = getUser();
        return Result.success(notificationService.countByTargetUserAndIsRead(user, false));
 }

逻辑在处理上,都是正确情况下的逻辑,减少了分支判断。 所有的错误通过异常抛出。 ApiException是一个自定义异常,他建议继承自 org.stringframework.web.client.HttpClientErrorException 这里你也可以自己处理,只不过HttpClientErrorException本身就带了跟http状态吗的逻辑的。 getUser()这里如果鉴权不通过,则抛出 HttpClientErrorException(401)异常

然后所有异常,通过ExceptionHandler统一处理,发现异常来自于客户端,则返回json并带有状态吗,其他controller的,走默认的 /error,表示是web端的错误

这样的好处:

  1. json序列化被透明了,逻辑层不在需要关注了。因此,不在需要手动指定 produces =json。 同时序列化也不限于json了,你也可以同时支持 xml,然后让客户端通过添加 accept=json 头的方式手动选择。虽然一般没啥用。json自定义通过提供 序列化bean 替换spring默认实现来控制。 2.强类型,Result的泛型不在是Object了,Action的返回值也更明确,语义更好。 3.业务思路更通顺,因为就是正确的需求。不对的头抛出了异常。 4.异常可以统一抽象处理,可以实现http错误状态吗

为什么要http状态吗? 符合restful。另外就是,客户端的框架通常是依赖于状态吗的,例如 Retrofit 返回状态如果是200,我才知道是正确的,框架可以自动序列化返回结果,其他的跳转到错误回调。这样处理起来客户端非常舒服。跟服务端明确的放回实体是一个道理。

为什么错误要走异常? 因为这个其实是spring推荐的方式,比如下面:

 @RequestMapping(value = "/some", method = RequestMethod.GET)
    public String mail(@RequestParam(name = "a", defaultValue = "3") String a) {
.......
}

如果defaultValue你不写的话,调用的时候不传a,spring会直接抛出HttpClientErrorException(400) 这个特性是非常方便的,他减少了很多逻辑处理,而且就是通过异常机制实现的。 因此,业务部分也通过异常处理的话,就可以统一去定制返回值。

atjiu commented 7 years ago

太感谢了,我会参考着写的,两次感谢

keepmoving1573 commented 7 years ago
/**
     * 保存回复
     *
     * @param topicId
     * @param content
     * @return
     */
    @RequestMapping(value = "/save", method = RequestMethod.POST)
    public String save(Integer topicId, String content, HttpServletResponse response)

方法上边的@RequestMapping注解已经比较老了,spring最新的注解可以简化他。

/**
     * 保存回复
     *
     * @param topicId
     * @param content
     * @return
     */
    @PostMapping("/save")
    public String save(Integer topicId, String content, HttpServletResponse response)
atjiu commented 7 years ago

哦哦,学习了,有空了我都把它改了😊