手机端 API 接口、接口调用、前后分离

有不少朋友咨询我,想用JeeSite做前后分开的应用,或者手机端API怎么调用?又或者只想用JeeSite作为服务端API,仅提供服务接口怎么做?那这篇文章一定适合你,下面我来介绍下一些JeeSite已内置的接口及如果自己来开发API接口提供服务。

内置接口

系统登录

JeeSite的系统默认登录,设置了DES加密,如果不想加密,可将secretKey设置为空即可,或更改密钥,配置如下application.yml(v4.0.x:jeesite.yml):

  1. shiro:
  2. loginSubmit:
  3. # 登录提交信息安全Key,加密用户名、密码、验证码,后再提交(key设置为3个,用逗号分隔)
  4. secretKey: thinkgem,jeesite,com
  5. # 设置为空,关闭登录DES加密。
  6. # secretKey: ~
  7. # 如果是JS请求可能会有跨域访问问题,可将如下参数设置为,允许的域名,全部域名设置*号,否则设置为空
  8. accessControlAllowOrigin: '*'

如果开启了加密,你就需要先将DES加密工具引入:

JS:<script src="${ctxStatic}/common/des.js?${_version}"></script>

Java: com.jeesite.common.codec.DesUtils

引入完成之后就可以通过如下方法进行调用加密了:

JS:

  1. <script>
  2. var secretKey = '${@Global.getConfig("shiro.loginSubmit.secretKey")}';
  3. var username = DesUtils.encode('system', secretKey);
  4. var password = DesUtils.encode('admin', secretKey);
  5. console.log('&username=' + username + '&password=' + password);
  6. </script>

Java:

  1. String secretKey = Global.getConfig("shiro.loginSubmit.secretKey");
  2. String username = DesUtils.encode("system", secretKey);
  3. String password = DesUtils.encode("admin", secretKey);
  4. System.out.println("&username=" + username + "&password=" + password);

以上两种语言,输出结果相同如下:

  1. &username=F3EDC7D2C193E0B8DCF554C726719ED2&password=235880C505ACCDA5C581A4F4CDB81DA0

下面我们就可以拿着这个用户名密码进行测试登录了。

通过JS的Ajax或者通过Java的HttpClient进行POST或GET请求如下地址,即可进行登录验证:

  1. http://127.0.0.1:8980/js/a/login?__login=true&__ajax=json&username=F3EDC7D2C193E0B8DCF554C726719ED2&password=235880C505ACCDA5C581A4F4CDB81DA0

你也可以添加登录附加参数如下:

  1. 1、可以指定登录设备类型(在线用户列表区分、登录验证码按设备区分,可根据设备指定session超时时间,默认PC):
  2. &param_deviceType=mobileApp
  3. 2、可以指定登录的系统(区分不同的菜单,默认default
  4. &param_sysCode=mobileApp
  5. 3、可以指定登录页面和主框架页的视图(默认:employee
  6. &param_userType=member

若登录信息不正确,则返回如下失败JSON数据:

  1. {
  2. "username": "F3EDC7D2C193E110B8DCF554C726719ED2",
  3. "rememberMe": false,
  4. "rememberUserCode": false,
  5. "params": "",
  6. "shiroLoginFailure": "org.apache.shiro.authc.UnknownAccountException",
  7. "message": "账号或密码错误, 请重试.",
  8. "isValidCodeLogin": false,
  9. "result": "false",
  10. "sessionid":"2a6669501bf24afebcf4ff63eb048a56"
  11. }

如果失败,第二次登录,建议附加一个__sid参数,用来指明是同一个会话,如:

  1. http://127.0.0.1:8980/js/a/login?__login=true&__ajax=json&username=F3EDC7D2C193E0B8DCF554C726719ED2&password=235880C505ACCDA5C581A4F4CDB81DA0&__sid=2a6669501bf24afebcf4ff63eb048a56

注意:若参数配置的密码失败次数超过了预警值,则返回的结果信息中的isValidCodeLogin会变为true,这时你需要调用http://127.0.0.1:8980/js/validCode?__sid=2a6669501bf24afebcf4ff63eb048a56地址来获取验证码图片,另外请注意,移动端一般调用是无cookie的,建议加请求参数中要包含__sid参数,否则获取到的验证码值将无法与你登录请求会话匹配。

若登录信息正确,则返回如下登录成功JSON数据:

  1. {
  2. "user": {
  3. "id": "system",
  4. "status": "0",
  5. "remarks": "",
  6. "userCode": "system",
  7. "loginCode": "system",
  8. "userName": "超级管理员",
  9. "userType": "none",
  10. "mgrType": "0",
  11. "lastLoginIp": "127.0.0.1",
  12. "lastLoginDate": "2018-03-14 22:34:44",
  13. "userWeight": 0,
  14. "oldLastLoginIp": "127.0.0.1",
  15. "corpName_": "JeeSite",
  16. "corpCode_": "0",
  17. "oldLoginDate": "2018-03-14 22:34:44",
  18. "avatarUrl": "/ctxPath/static/images/user1.jpg"
  19. },
  20. "result": "true",
  21. "message": "登录成功!",
  22. "sessionid": "5fe9c7c45ded4425b03eff8f78179637"
  23. }

在登录成功的信息里,也有个 sessionid 属性,该属性值将作为你以后访问系统的凭证,相当于token令牌,举例如下:

  1. 1、获取用户权限信息:
  2. http://127.0.0.1:8980/js/a/authInfo?__sid=5fe9c7c45ded4425b03eff8f78179637
  3. 2、获取用户菜单信息:
  4. http://127.0.0.1:8980/js/a/menuTree?__sid=5fe9c7c45ded4425b03eff8f78179637
  5. 3、重新获取登录信息:
  6. http://127.0.0.1:8980/js/a/index.json?__sid=5fe9c7c45ded4425b03eff8f78179637
  7. 4、获取当前用户信息:
  8. http://127.0.0.1:8980/js/a/sys/user/info.json?__sid=5fe9c7c45ded4425b03eff8f78179637

会话失效,刷新 Token

token 就是 sid,当会话失效后,系统会返回 "result":"login" 则代表 sid 已失效,需要重新登录。

你需要写一个通用的 post 或 get 方法,逻辑如下:

  • 正常调用业务地址,若返回 "result":"login" 则代表 __sid 会话已失效,需要重新登录,否则正常处理;
  • 若会话已失效,则重新调用 login 登录方法重新获取新的 _sid
  • 根据新的 __sid 再调用业务地址,返回正确数据。 伪代码如下:
  1. var __sid = ''; // 当前 sid
  2. function post(url, data, callback){
  3. $.post('http://host/'+url, {__sid: __sid}, function(data){
  4. // 如果 sid 失效,则刷新 sid 并重试
  5. if (Object.prototype.toString.call(res) === '[object Object]'
  6. && data.result == 'login'){
  7. refreshSid(function(data){
  8. if (data.result == 'true'){
  9. $.post('http://host/'+url, {__sid: __sid}, function(data){
  10. callback(data);
  11. });
  12. }else{
  13. // 身份认证失败
  14. callback(data);
  15. }
  16. });
  17. }else{
  18. callback(data);
  19. }
  20. });
  21. }
  22. function refreshSid(callback){
  23. $.post('http://host/a/login', {username: '', password: '', __sid: __sid}, function(data){
  24. if (data.result == 'true'){
  25. __sid = data.sessionid;
  26. }
  27. callback(data);
  28. });
  29. }

另外一种方法:手机端登录后生成一个 UUID,将这个 UUID 存入用户表的 mobileImei 字段;当手机端会话失效后,发送这个 UUID,后台根据 UUID 从用户表中获取到登录名,然后通过 “无条件登录” 方法登录系统。

快速登录接口:

1、设置 application.yml(v4.0.x:jeesite.yml)的 shiro.sso.secretKey 快速登录安全Key:

  1. shiro:
  2. # 简单 SSO 登录相关配置
  3. sso:
  4. # 如果启用/sso/{username}/{token}单点登录,请修改此安全key并与单点登录系统key一致。
  5. secretKey: thinkgem
  6. # 是否加密单点登录安全Key
  7. encryptKey: true

2、举例调用地址如下,调用完成后自动登录系统:

  1. http://localhost/project/sso/{username}/{token}?url=/sys/user/list?p1=v1%26p2=v2&relogin=true

如果url中携带参数,请使用转义字符,如“&”号,使用“%26”转义。

该接口源码如下(提供参考):

  1. /**
  2. * 单点登录(如已经登录,则直接跳转)
  3. * @param username 登录用户名(loginCode)
  4. * @param token 单点登录令牌,令牌组成:sso密钥+用户名+日期,进行md5加密,举例:
  5. * // 注意如果 shiro.sso.encryptKey 为 true,则 secretKey 会自动加密。
  6. * String secretKey = Global.getConfig("shiro.sso.secretKey");
  7. * String token = Md5Utils.md5(secretKey + username + DateUtils.getDate("yyyyMMdd"));
  8. * @param params 登录附加参数(JSON格式),或 param_ 前缀的请求参数。
  9. * @param url 登录成功后跳转的url地址。
  10. * @param relogin 是否强制重新登录,需要强制重新登录传递true
  11. */
  12. @RequestMapping(value = "sso/{username}/{token}")
  13. public String sso(@PathVariable String username, @PathVariable String token,
  14. @RequestParam(defaultValue="${adminPath}") String url, String relogin,
  15. HttpServletRequest request, Model model){
  16. User user = UserUtils.getUser();
  17. // 如果已经登录,并且是同一个人,并且不强制重新登录,则直接跳转到目标页
  18. if(StringUtils.isNotBlank(user.getUserCode())
  19. && StringUtils.equals(user.getLoginCode(), username)
  20. && !ObjectUtils.toBoolean(relogin)){
  21. return REDIRECT + EncodeUtils.decodeUrl2(url);
  22. }
  23. // 通过令牌登录系统
  24. if (token != null){
  25. try {
  26. FormToken upToken = new FormToken();
  27. upToken.setUsername(username); // 登录用户名
  28. upToken.setSsoToken(token); // 单点登录令牌
  29. upToken.setParams(ServletUtils.getExtParams(request)); // 登录附加参数
  30. UserUtils.getSubject().login(upToken);
  31. return REDIRECT + EncodeUtils.decodeUrl2(url);
  32. } catch (AuthenticationException e) {
  33. if (!e.getMessage().startsWith("msg:")){
  34. throw new AuthenticationException("msg:登录失败,请联系管理员。", e);
  35. }
  36. throw e;
  37. }
  38. }
  39. return "error/403";
  40. }

无条件登录举例:

  1. String username = "admin";
  2. FormToken upToken = new FormToken();
  3. upToken.setUsername(username);
  4. upToken.setSsoToken(UserUtils.getSsoToken(username));
  5. UserUtils.getSubject().login(upToken);
  6. String sid = UserUtils.getSession().getId().toString();
  7. System.out.println(sid);

系统退出

  1. http://127.0.0.1:8980/js/a/logout?__ajax=json&__sid=5fe9c7c45ded4425b03eff8f78179637

注意:无 cookie 环境下,必须要指定要退出的 sessionid

返回JSON数据:

  1. {"result":"true","message":"退出成功!"}

接口发现

剩下的接口就不一一说明了,交给大家一个接口发现的方法。

所有连接加 .json 或 .xml 或增加 ajax=json 参数,或增加 ajax=xml 参数,则自动返回 json 或 xml 数据,而不返回视图,举例:

用户列表的访问地址是 /a/sys/empUser/list,如果直接访问,则返回页面的视图界面,如果加后缀 .json,则返回视图所需要的json数据,如:/a/sys/empUser/list.json,这样返回的数据,就可以在你的前端分离应用中使用了。

所有列表加载的数据均使用 listData 为后缀获取数据,如用户列表的数据地址为 /a/sys/empUser/listData,则直接返回JSON数据。

listData只是一个命名规则,如果你发现了不遵循规范的地址,怎么办?你可以通过Chrome浏览器的开发者界面(F12),打开Network,Filter中选择XHR,好了,准备就绪,这是你点击列表里的查询按钮,即可监控到访问的数据的地址是什么

开发一个API接口

在你的 Controller 映射方法上增加 @ResponseBody 即可返回 JSON 数据,而不返回视图

或者替换 @Controller 为 @RestController,则应用所有映射方法均返回JSON数据。

如果你想两用,就如 接口发现 章节所述,增加 .json 后缀即可。

另外,对于移动端或高并发的应用对于流量是非常珍贵的,通用方法可能会返回一些很多无用的数据,这时,你可以使用 @JsonView 注解进行配置,返回需要的数据即可。