├── src └── main │ ├── webapp │ ├── index.jsp │ ├── WEB-INF │ │ └── web.xml │ └── doc │ │ ├── file.html │ │ └── index.html │ ├── resources │ ├── jdbc.properties │ ├── configure.xml │ ├── version.xml │ └── log4j.properties │ └── java │ └── com │ └── mlongbo │ └── jfinal │ ├── router │ ├── ActionRouter.java │ └── APIRouter.java │ ├── common │ ├── utils │ │ ├── TokenUtil.java │ │ ├── SMSUtils.java │ │ ├── DateUtils.java │ │ ├── FileType.java │ │ ├── RandomUtils.java │ │ ├── StringUtils.java │ │ └── FileUtils.java │ ├── bean │ │ ├── FileResponse.java │ │ ├── Constant.java │ │ ├── DatumResponse.java │ │ ├── Code.java │ │ ├── DataResponse.java │ │ ├── BaseResponse.java │ │ └── LoginResponse.java │ ├── Require.java │ ├── token │ │ └── TokenManager.java │ ├── httpclient │ │ ├── HttpResponse.java │ │ └── HttpRequester.java │ └── XmlProperty.java │ ├── model │ ├── RegisterCode.java │ ├── FeedBack.java │ └── User.java │ ├── handler │ ├── ContextHandler.java │ └── APINotFoundHandler.java │ ├── version │ ├── ClientType.java │ ├── Version.java │ ├── VersionManager.java │ └── VersionProperty.java │ ├── interceptor │ ├── ErrorInterceptor.java │ └── TokenInterceptor.java │ ├── action │ └── IndexAction.java │ ├── config │ ├── AppConstant.java │ ├── Context.java │ └── AppProperty.java │ ├── plugin │ └── HikariCPPlugin.java │ ├── api │ ├── FileAPIController.java │ ├── CommonAPIController.java │ ├── BaseAPIController.java │ └── AccountAPIController.java │ └── AppConfig.java ├── .gitignore ├── doc ├── file.md ├── index.md └── user.md ├── LICENSE ├── tables.sql ├── pom.xml └── README.md /src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hello World~

4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/jdbc.properties: -------------------------------------------------------------------------------- 1 | driverClass=com.mysql.jdbc.Driver 2 | jdbcUrl=jdbc:mysql://localhost:3306/jfinal-api-scaffold 3 | user=root 4 | password= 5 | maxPoolSize=10 6 | minPoolSize=1 7 | initialPoolSize=1 8 | maxIdleTime=60 9 | acquireIncrement=1 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | .idea/ 15 | *.iml -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/router/ActionRouter.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.router; 2 | 3 | import com.jfinal.config.Routes; 4 | import com.mlongbo.jfinal.action.IndexAction; 5 | 6 | /** 7 | * @author malongbo 8 | * @date 2015/2/13 9 | * @package com.snailbaba.router 10 | */ 11 | public class ActionRouter extends Routes{ 12 | @Override 13 | public void config() { 14 | add("/", IndexAction.class, "/"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/utils/TokenUtil.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.utils; 2 | 3 | /** 4 | * @author malongbo 5 | * @date 15-1-18 6 | * @package com.pet.project.common.token 7 | */ 8 | public class TokenUtil { 9 | /** 10 | * 生成token号码 11 | * @return token号码 12 | */ 13 | public static String generateToken() { 14 | return RandomUtils.randomCustomUUID().concat(RandomUtils.randomString(6)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/model/RegisterCode.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.model; 2 | 3 | import com.jfinal.plugin.activerecord.Model; 4 | 5 | /** 6 | * 短信注册验证码* 7 | * @author malongbo 8 | */ 9 | public class RegisterCode extends Model { 10 | 11 | public static final String MOBILE = "mobile"; 12 | 13 | public static final String CODE = "code"; 14 | 15 | public static RegisterCode dao = new RegisterCode(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/router/APIRouter.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.router; 2 | 3 | import com.jfinal.config.Routes; 4 | import com.mlongbo.jfinal.api.*; 5 | 6 | /** 7 | * @author malongbo 8 | */ 9 | public class APIRouter extends Routes { 10 | @Override 11 | public void config() { 12 | //公共api 13 | add("/api", CommonAPIController.class); 14 | //用户相关 15 | add("/api/account", AccountAPIController.class); 16 | //文件相关 17 | add("/api/fs",FileAPIController.class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/bean/FileResponse.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.bean; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author malongbo 7 | * @date 2015/1/28 8 | * @package com.pet.project.bean 9 | */ 10 | public class FileResponse extends DatumResponse { 11 | /* 12 | * 保存上传失败的文件名 13 | */ 14 | private List failed; 15 | 16 | public List getFailed() { 17 | return failed; 18 | } 19 | 20 | public FileResponse setFailed(List failed) { 21 | this.failed = failed; 22 | return this; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/handler/ContextHandler.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.handler; 2 | 3 | import com.jfinal.handler.Handler; 4 | import com.mlongbo.jfinal.config.Context; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | /** 10 | * @author malongbo 11 | */ 12 | public class ContextHandler extends Handler { 13 | @Override 14 | public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) { 15 | Context.me().setRequest(request); 16 | this.nextHandler.handle(target, request, response, isHandled); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/configure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | http://mlongbo.com/upload 6 | 7 | 1 8 | 9 | upload 10 | 11 | /images 12 | 13 | /videos 14 | 15 | /others 16 | 17 | /images/defaultUserAvatar.jpg 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/bean/Constant.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.bean; 2 | 3 | import com.mlongbo.jfinal.config.AppProperty; 4 | 5 | /** 6 | * @author malongbo 7 | * @date 2015/1/17 8 | * @package com.pet.project.bean 9 | */ 10 | public class Constant { 11 | private static Constant me = new Constant(); 12 | 13 | private String resourceServer; 14 | 15 | /** 16 | * 获取单例对象 17 | * @return 18 | */ 19 | public static Constant me() { 20 | return me; 21 | } 22 | 23 | public String getResourceServer() { 24 | return AppProperty.me().resourcePrefix(); 25 | } 26 | 27 | public void setResourceServer(String resourceServer) { 28 | this.resourceServer = resourceServer; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/model/FeedBack.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.model; 2 | 3 | import com.jfinal.plugin.activerecord.Model; 4 | 5 | /** 6 | * 意见反馈实体 7 | * @author mlongbo 8 | */ 9 | public class FeedBack extends Model { 10 | 11 | private static final long serialVersionUID = -1267302372426876814L; 12 | 13 | public static final FeedBack dao = new FeedBack(); 14 | 15 | /** 16 | * 唯一id 17 | */ 18 | public static String ID = "id"; 19 | 20 | /** 21 | * 反馈用户的id* 22 | */ 23 | public static String USER_ID = "userId"; 24 | 25 | /** 26 | * 反馈时间* 27 | */ 28 | public static String CREATION_DATE = "creationDate"; 29 | 30 | /** 31 | * 反馈内容* 32 | */ 33 | public static String SUGGESTION = "suggestion"; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/version/ClientType.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.version; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 终端类型枚举对象* 7 | * @author malongbo 8 | */ 9 | public enum ClientType implements Serializable { 10 | ANDROID("android"),IPHONE("iphone"); 11 | 12 | private String type; 13 | 14 | ClientType(String type) { 15 | this.type = type; 16 | } 17 | 18 | public String getType() { 19 | return type; 20 | } 21 | 22 | public static ClientType getClientType(String type) { 23 | if (ANDROID.type.equalsIgnoreCase(type) ) { 24 | return ANDROID; 25 | } 26 | 27 | if (IPHONE.type.equalsIgnoreCase(type) ) { 28 | return IPHONE; 29 | } 30 | 31 | return null; 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/bean/DatumResponse.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.bean; 2 | 3 | /** 4 | * @author malongbo 5 | * @date 2015/1/17 6 | * @package com.pet.project.bean 7 | */ 8 | public class DatumResponse extends BaseResponse { 9 | private Object datum; 10 | 11 | public DatumResponse() { 12 | super(); 13 | } 14 | 15 | public DatumResponse (Object datum) { 16 | this.datum = datum; 17 | } 18 | 19 | public DatumResponse(Integer code) { 20 | super(code); 21 | } 22 | 23 | public DatumResponse(Integer code, String message) { 24 | super(code, message); 25 | } 26 | 27 | public DatumResponse setDatum(Object datum) { 28 | this.datum = datum; 29 | return this; 30 | } 31 | 32 | public Object getDatum() { 33 | return datum; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | jfinal 9 | com.jfinal.core.JFinalFilter 10 | 11 | configClass 12 | com.mlongbo.jfinal.AppConfig 13 | 14 | 15 | 16 | jfinal 17 | /* 18 | 19 | 20 | 21 | index.jsp 22 | 23 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/interceptor/ErrorInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.interceptor; 2 | 3 | import com.jfinal.aop.Interceptor; 4 | import com.jfinal.core.ActionInvocation; 5 | import com.mlongbo.jfinal.common.bean.BaseResponse; 6 | import com.mlongbo.jfinal.common.bean.Code; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * 捕获所有api action异常 12 | * @author malongbo 13 | */ 14 | public class ErrorInterceptor implements Interceptor { 15 | private static final Logger logger = LoggerFactory.getLogger(ErrorInterceptor.class); 16 | @Override 17 | public void intercept(ActionInvocation ai) { 18 | try { 19 | ai.invoke(); 20 | } catch (Exception e) { 21 | e.printStackTrace(); 22 | logger.error(e.getMessage(), e); 23 | ai.getController().renderJson(new BaseResponse(Code.ERROR, "server error")); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/bean/Code.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.bean; 2 | 3 | /** 4 | * @author malongbo 5 | */ 6 | public class Code { 7 | 8 | /** 9 | * 成功 10 | */ 11 | public static final int SUCCESS = 1; 12 | 13 | /** 14 | * 失败 15 | */ 16 | public static final int FAIL = 0; 17 | 18 | /** 19 | * 参数错误: 一般是缺少或参数值不符合要求 20 | */ 21 | public static final int ARGUMENT_ERROR = 2; 22 | 23 | /** 24 | * 服务器错误 25 | */ 26 | public static final int ERROR = 500; 27 | 28 | /** 29 | * 接口不存在 30 | */ 31 | public static final int NOT_FOUND = 404; 32 | 33 | /** 34 | * token无效 35 | */ 36 | public static final int TOKEN_INVALID = 422; 37 | 38 | /** 39 | * 帐号已存在* 40 | */ 41 | public static final int ACCOUNT_EXISTS = 3; 42 | 43 | /** 44 | * 验证码错误 45 | */ 46 | public static final int CODE_ERROR = 4; 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/bean/DataResponse.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.bean; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author malongbo 7 | * @date 2015/1/17 8 | * @package com.pet.project.bean 9 | */ 10 | public class DataResponse extends BaseResponse { 11 | private List data; 12 | 13 | public DataResponse() { 14 | super(); 15 | } 16 | 17 | public DataResponse(String message) { 18 | super(message); 19 | } 20 | 21 | public DataResponse (List data) { 22 | this.data = data; 23 | } 24 | 25 | public DataResponse(Integer code) { 26 | super(code); 27 | } 28 | 29 | public DataResponse(Integer code, String message) { 30 | super(code, message); 31 | } 32 | 33 | public DataResponse setData(List data) { 34 | this.data = data; 35 | return this; 36 | } 37 | 38 | public List getData() { 39 | return data; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /doc/file.md: -------------------------------------------------------------------------------- 1 | ## 文件上传 2 | 3 | ### 接口说明 4 | 5 | 文件上传是一个通用的总接口,凡是涉及文件上传操作的功能,请先调用文件上传接口,返回正确文件访问路径后,再提交纯文本表单。即提交文本表单时,用户头像、图片相关的表单字段,为上传接口返回的URL值。 6 | 7 | ### url 8 | /api/fs/upload 9 | 10 | ### method 11 | post 12 | 13 | ### 参数 14 | 15 | * token:必要参数,为空或错误将不能上传。token可以从登录后的用户信息里获得(不登录是无法获取上传权限的) 16 | 17 | 该接口支持单文件及多文件上传, 每个文件对应一个请求参数, 如: file1对应a.jpg, file2对应b.jpg 18 | 19 | ### 响应结果说明 20 | 21 | **节点说明**: 22 | 23 | * code:表示响应结果状态,1表示成功,0表示有一或多个文件上传失败 24 | * message:响应结果的文字说明 25 | * failed: 此字段标记上传失败的请求参数名称(名称对应上传时所传递的文件),如: ['file1','file2'] 26 | * datum: 此字段返回了上传成功的文件所对应的文件地址, key为上传时传递的请求参数, value为文件地址 27 | 28 | #### 上传成功 29 | 30 | { 31 | code: 1, 32 | datum: { 33 | fileUpload1: "/imgs/2015/02/01/20131117223307_JMMX5.thumb.700_0.jpeg", 34 | fileUpload2: "/imgs/2015/02/01/shortcut.png" 35 | } 36 | } 37 | 38 | #### 包含上传失败的文件 39 | 40 | { 41 | code: 0, 42 | failed: ['fileUpload1'] 43 | } 44 | 45 | #### 请求中未包含文件 46 | 47 | { 48 | message: "uploadFileName can not be null", 49 | code: 2 50 | } -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/Require.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common; 2 | 3 | import com.mlongbo.jfinal.common.utils.StringUtils; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * 存放校验条件和响应信息 10 | * @author malongbo 11 | */ 12 | public class Require { 13 | private List conditions = new ArrayList(); //不为空的条件集合 14 | private List messages = new ArrayList(); //响应信息集合 15 | 16 | public Require put(Object param, String message) { 17 | conditions.add(param); 18 | messages.add(message); 19 | return this; 20 | } 21 | 22 | /** 23 | * 创建实例* 24 | * @return Require对象 25 | */ 26 | public static final Require me() { 27 | return new Require(); 28 | } 29 | 30 | public Object get(int index) { 31 | return conditions.get(index); 32 | } 33 | public String getMessage(int index) { 34 | return messages.get(index); 35 | } 36 | 37 | public int getLength() { 38 | return conditions.size(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/version.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 0.1.2 8 | http://snailbaba.mlongbo.com/snailbaba_0_1_1.apk 9 | 修复bug 10 | 11 | 12 | 0.1.0 13 | http://snailbaba.mlongbo.com/snailbaba_0_1_0.apk 14 | 第一个版本 15 | 16 | 17 | 18 | 19 | 0.1.2 20 | http://snailbaba.mlongbo.com/snailbaba_0_1_1.apk 21 | 修复bug 22 | 23 | 24 | 0.1.0 25 | http://snailbaba.mlongbo.com/upload/snailbaba_0_1_0.apk 26 | 第一个版本 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/action/IndexAction.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.action; 2 | 3 | import com.jfinal.core.Controller; 4 | import com.mlongbo.jfinal.common.utils.StringUtils; 5 | import com.mlongbo.jfinal.model.RegisterCode; 6 | 7 | /** 8 | * @author malongbo 9 | * @date 2015/2/13 10 | * @package com.snailbaba.action 11 | */ 12 | public class IndexAction extends Controller { 13 | public void index () { 14 | render("index.jsp"); 15 | } 16 | 17 | public void doc() { 18 | render("doc/index.html"); 19 | 20 | } 21 | 22 | /** 23 | * 查询手机验证码 * 24 | * 测试使用* 25 | */ 26 | public void findCode () { 27 | String mobile = getPara("mobile"); 28 | if (StringUtils.isNotEmpty(mobile)) { 29 | String codeStr = "没查到该手机对应的验证码"; 30 | RegisterCode code = RegisterCode.dao.findById(mobile); 31 | if (code != null && StringUtils.isNotEmpty(code.getStr(RegisterCode.CODE))) { 32 | codeStr = code.getStr(RegisterCode.CODE); 33 | } 34 | renderHtml(codeStr); 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/bean/BaseResponse.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.bean; 2 | 3 | /** 4 | * @author malongbo 5 | * @date 2015/1/17 6 | * @package com.pet.project.bean 7 | */ 8 | public class BaseResponse { 9 | 10 | private Integer code = Code.SUCCESS; 11 | 12 | private String message; 13 | 14 | public BaseResponse() { 15 | } 16 | 17 | public BaseResponse(String message) { 18 | this.message = message; 19 | } 20 | 21 | public BaseResponse(Integer code) { 22 | this.code = code; 23 | } 24 | 25 | public BaseResponse(Integer code, String message) { 26 | this.code = code; 27 | this.message = message; 28 | } 29 | 30 | public BaseResponse setCode(Integer code) { 31 | this.code = code; 32 | return this; 33 | } 34 | 35 | public BaseResponse setMessage(String message) { 36 | this.message = message; 37 | return this; 38 | } 39 | 40 | public Integer getCode() { 41 | return code; 42 | } 43 | 44 | public String getMessage() { 45 | return message; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Longbo Ma (mlongbo.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/config/AppConstant.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.config; 2 | 3 | /** 4 | * 常量类* 5 | * @author malongbo 6 | */ 7 | public final class AppConstant { 8 | private AppConstant(){} 9 | 10 | /** 11 | * 文件下载的地址前缀* 12 | */ 13 | public static final String RES_PREFIX = "resource.prefix"; 14 | 15 | /** 16 | * 标记是否将上传的文件存储在应用目录,如tomcat. 1表示是,0表示否* 17 | */ 18 | public static final String RES_APP_PATH = "resource.appPath"; 19 | 20 | /** 21 | * 上传文件存储目录, 如果appPath值为0,需填写目录的绝对路径,否则填写应用目录的相对路径* 22 | */ 23 | public static final String RES_UPLOAD_ROOT_PATH = "resource.uploadRootPath"; 24 | 25 | /** 26 | * 图片文件存储的相对目录* 27 | */ 28 | public static final String RES_IMAGE_PATH = "resource.imagePath"; 29 | 30 | /** 31 | * 视频文件存储的相对目录* 32 | */ 33 | public static final String RES_VIDEO_PATH = "resource.videoPath"; 34 | 35 | /** 36 | * 其它文件存储的相对目录* 37 | */ 38 | public static final String RES_OTHER_PATH = "resource.otherPath"; 39 | 40 | /** 41 | * 默认的头像路径, 与prefix值拼接后可访问* 42 | */ 43 | public static final String RES_DEFAULT_USER_AVATAR = "resource.defaultUserAvatar"; 44 | } 45 | -------------------------------------------------------------------------------- /tables.sql: -------------------------------------------------------------------------------- 1 | 2 | # 用户表 3 | CREATE TABLE `t_user` ( 4 | `userId` char(32) NOT NULL DEFAULT '', 5 | `loginName` varchar(20) NOT NULL COMMENT '用户登录名', 6 | `nickName` varchar(20) NOT NULL COMMENT '昵称', 7 | `password` varchar(32) NOT NULL COMMENT 'md5加密后的密码', 8 | `sex` tinyint(2) NOT NULL COMMENT '性别,1表示男,0表示女', 9 | `email` varchar(100) DEFAULT NULL COMMENT '邮箱', 10 | `status` tinyint(2) DEFAULT '1' COMMENT '帐号状态. 1表示开启 ,0表示禁用', 11 | `creationDate` bigint(20) DEFAULT NULL COMMENT '帐号创建日期时间戳', 12 | `avatar` varchar(500) DEFAULT NULL COMMENT '头像地址', 13 | PRIMARY KEY (`userId`) 14 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 15 | 16 | # 注册验证码表 17 | CREATE TABLE `t_register_code` ( 18 | `mobile` char(11) NOT NULL COMMENT '接收短信的手机号码', 19 | `code` char(4) DEFAULT NULL, 20 | PRIMARY KEY (`mobile`) 21 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 22 | 23 | # 意见反馈表 24 | CREATE TABLE `t_feedback` ( 25 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键,自增', 26 | `userId` char(32) DEFAULT NULL COMMENT '用户ID', 27 | `creationDate` bigint(20) NOT NULL COMMENT '反馈的时间戳', 28 | `suggestion` varchar(300) NOT NULL COMMENT '反馈内容', 29 | PRIMARY KEY (`id`) 30 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4; 31 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/bean/LoginResponse.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.bean; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * @author malongbo 7 | * @date 2015/1/17 8 | * @package com.pet.project.bean 9 | */ 10 | public class LoginResponse extends BaseResponse { 11 | private Map info; 12 | private String token; 13 | private Constant constant = Constant.me(); 14 | 15 | public LoginResponse() { 16 | super(); 17 | } 18 | 19 | public LoginResponse(Integer code) { 20 | super(code); 21 | } 22 | 23 | public LoginResponse(Integer code, String message) { 24 | super(code, message); 25 | } 26 | 27 | public Map getInfo() { 28 | return info; 29 | } 30 | 31 | 32 | public String getToken() { 33 | return token; 34 | } 35 | 36 | public Constant getConstant() { 37 | return constant; 38 | } 39 | 40 | public void setInfo(Map info) { 41 | this.info = info; 42 | } 43 | 44 | 45 | public void setToken(String token) { 46 | this.token = token; 47 | } 48 | 49 | public void setConstant(Constant constant) { 50 | this.constant = constant; 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/model/User.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.model; 2 | 3 | import com.jfinal.plugin.activerecord.Model; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @author malongbo 9 | * @date 2015/2/13 10 | */ 11 | public class User extends Model { 12 | public static String USER_ID = "userId"; 13 | public static String LOGIN_NAME = "loginName"; 14 | public static String NICK_NAME = "nickName"; 15 | public static String PASSWORD = "password"; 16 | public static String SEX = "sex"; 17 | public static String EMAIL = "email"; 18 | public static String STATUS = "status"; 19 | public static String CREATION_DATE = "creationDate"; 20 | public static String AVATAR = "avatar"; 21 | 22 | 23 | private static final long serialVersionUID = 1L; 24 | public static final User user = new User(); 25 | 26 | /** 27 | * 获取用户id* 28 | * @return 用户id 29 | */ 30 | public String userId() { 31 | return getStr(USER_ID); 32 | 33 | } 34 | 35 | /** 36 | * 检查值是否有效* 37 | * @param sex 性别值 38 | * @return 有效性 39 | */ 40 | public static final boolean checkSex(int sex) { 41 | 42 | return sex == 1 || sex == 0; 43 | } 44 | 45 | @Override 46 | public Map getAttrs() { 47 | return super.getAttrs(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/interceptor/TokenInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.interceptor; 2 | 3 | import com.jfinal.aop.Interceptor; 4 | import com.jfinal.core.ActionInvocation; 5 | import com.jfinal.core.Controller; 6 | import com.mlongbo.jfinal.common.bean.BaseResponse; 7 | import com.mlongbo.jfinal.common.bean.Code; 8 | import com.mlongbo.jfinal.common.token.TokenManager; 9 | import com.mlongbo.jfinal.common.utils.StringUtils; 10 | import com.mlongbo.jfinal.model.User; 11 | 12 | /** 13 | * Token拦截器 14 | * @author malongbo 15 | * @date 15-1-18 16 | * @package com.pet.project.interceptor 17 | */ 18 | public class TokenInterceptor implements Interceptor { 19 | @Override 20 | public void intercept(ActionInvocation ai) { 21 | Controller controller = ai.getController(); 22 | String token = controller.getPara("token"); 23 | if (StringUtils.isEmpty(token)) { 24 | controller.renderJson(new BaseResponse(Code.ARGUMENT_ERROR, "token can not be null")); 25 | return; 26 | } 27 | 28 | User user = TokenManager.getMe().validate(token); 29 | if (user == null) { 30 | controller.renderJson(new BaseResponse(Code.TOKEN_INVALID, "token is invalid")); 31 | return; 32 | } 33 | 34 | controller.setAttr("user", user); 35 | ai.invoke(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/config/Context.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.config; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | 5 | /** 6 | * @author malongbo 7 | */ 8 | public class Context { 9 | private static final Context instance = new Context(); 10 | private ThreadLocal request = new ThreadLocal(); 11 | private AppProperty config; 12 | private boolean initialized = false; 13 | 14 | public static final Context me() { 15 | return instance; 16 | } 17 | 18 | public synchronized void setRequest(HttpServletRequest servletRequest) { 19 | if (request != null) { 20 | request.set(servletRequest); 21 | } 22 | } 23 | 24 | public AppProperty getConfig() { 25 | return config; 26 | } 27 | 28 | public HttpServletRequest getRequest() { 29 | if (request != null) { 30 | return request.get(); 31 | } 32 | return null; 33 | } 34 | 35 | public synchronized void init() { 36 | if (initialized) { 37 | return; 38 | } 39 | 40 | config = AppProperty.me().init(); 41 | 42 | initialized = true; 43 | 44 | } 45 | 46 | public synchronized void destroy () { 47 | config.destroy(); 48 | config = null; 49 | request = null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/token/TokenManager.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.token; 2 | 3 | 4 | import com.mlongbo.jfinal.common.utils.TokenUtil; 5 | import com.mlongbo.jfinal.model.User; 6 | 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | /** 11 | * @author malongbo 12 | * @date 15-1-18 13 | * @package com.pet.project.common.token 14 | */ 15 | public class TokenManager { 16 | private static TokenManager me = new TokenManager(); 17 | 18 | private Map tokens; 19 | private Map userToken; 20 | 21 | public TokenManager() { 22 | tokens = new ConcurrentHashMap(); 23 | userToken = new ConcurrentHashMap(); 24 | } 25 | 26 | /** 27 | * 获取单例对象 28 | * @return 29 | */ 30 | public static TokenManager getMe() { 31 | return me; 32 | } 33 | 34 | /** 35 | * 验证token 36 | * @param token 37 | * @return 38 | */ 39 | public User validate(String token) { 40 | return tokens.get(token); 41 | } 42 | 43 | /** 44 | * 生成token值 45 | * @param user 46 | * @return 47 | */ 48 | public String generateToken(User user) { 49 | String token = TokenUtil.generateToken(); 50 | userToken.put(user.getStr(User.USER_ID), token); 51 | tokens.put(token, user); 52 | return token; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/version/Version.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.version; 2 | 3 | import com.mlongbo.jfinal.common.utils.StringUtils; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * @author malongbo 9 | */ 10 | public class Version implements Serializable { 11 | private ClientType type; //终端类型 12 | private String version; //版本号 13 | private String url; //新版本下载地址 14 | private String message; //更新内容 15 | 16 | /** 17 | * 检查值是否符合要求* 18 | * @param type 终端类型值 19 | */ 20 | public static final boolean checkType(String type) { 21 | if (StringUtils.isEmpty(type)) { 22 | return false; 23 | } 24 | 25 | type = type.trim().toLowerCase(); 26 | return "android".equals(type) || "iphone".equals(type); 27 | } 28 | 29 | public ClientType getType() { 30 | return type; 31 | } 32 | 33 | public void setType(ClientType type) { 34 | this.type = type; 35 | } 36 | 37 | public String getVersion() { 38 | return version; 39 | } 40 | 41 | public void setVersion(String version) { 42 | this.version = version; 43 | } 44 | 45 | public String getUrl() { 46 | return url; 47 | } 48 | 49 | public void setUrl(String url) { 50 | this.url = url; 51 | } 52 | 53 | public String getMessage() { 54 | return message; 55 | } 56 | 57 | public void setMessage(String message) { 58 | this.message = message; 59 | } 60 | } -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/handler/APINotFoundHandler.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.handler; 2 | 3 | import com.jfinal.core.JFinal; 4 | import com.jfinal.handler.Handler; 5 | import com.jfinal.render.RenderFactory; 6 | import com.mlongbo.jfinal.common.bean.Code; 7 | import com.mlongbo.jfinal.common.bean.BaseResponse; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.UnsupportedEncodingException; 12 | 13 | /** 14 | * 处理404接口* 15 | * @author malongbo 16 | * @date 15-1-18 17 | * @package com.pet.project 18 | */ 19 | public class APINotFoundHandler extends Handler { 20 | @Override 21 | public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) { 22 | if (!target.startsWith("/api")) { 23 | this.nextHandler.handle(target, request, response, isHandled); 24 | return; 25 | } 26 | 27 | if (JFinal.me().getAction(target, new String[1]) == null) { 28 | isHandled[0] = true; 29 | try { 30 | request.setCharacterEncoding("utf-8"); 31 | } catch (UnsupportedEncodingException e) { 32 | e.printStackTrace(); 33 | } 34 | RenderFactory.me().getJsonRender(new BaseResponse(Code.NOT_FOUND, "resource is not found")).setContext(request, response).render(); 35 | } else { 36 | this.nextHandler.handle(target, request, response, isHandled); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/version/VersionManager.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.version; 2 | 3 | import com.mlongbo.jfinal.common.utils.StringUtils; 4 | 5 | import java.io.IOException; 6 | 7 | /** 8 | * 版本管理器* 9 | * @author malongbo 10 | */ 11 | public class VersionManager { 12 | private static VersionManager me = new VersionManager(); 13 | private VersionProperty property; //文件配置 14 | private static String propertyName = "/version.xml"; //默认的配置文件 15 | 16 | public VersionManager() { 17 | this(propertyName); 18 | } 19 | 20 | public VersionManager(String propertyName) { 21 | try { 22 | property = new VersionProperty(VersionManager.class.getResource(propertyName).getPath()); 23 | } catch (IOException e) { 24 | throw new RuntimeException(propertyName + " can not found", e); 25 | } 26 | } 27 | 28 | public static VersionManager me() { 29 | return me; 30 | } 31 | 32 | /** 33 | * 检查版本* 34 | * @param version 版本号 35 | * @param client 终端类型 36 | * @return 当前最新版本 37 | */ 38 | public Version check(String version, String client) { 39 | if (property == null || StringUtils.isEmpty(version) || StringUtils.isEmpty(client)) { 40 | return null; 41 | } 42 | 43 | Version nowVersion = property.getNowVersion(ClientType.getClientType(client)); 44 | 45 | if (nowVersion == null || version.equalsIgnoreCase(nowVersion.getVersion())) { 46 | return null; 47 | } 48 | 49 | return nowVersion; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | ## HTTP API文档 总览 2 | 3 | ### [文件上传](file.md) 4 | 5 | ### [用户相关](user.md) 6 | 7 | 8 | *** 9 | 10 | ## 意见反馈 11 | 12 | #### URL 13 | /api/feedback 14 | 15 | #### METHOD 16 | POST(必须是POST) 17 | 18 | #### 参数 19 | 20 | * **token**:令牌(可选项) 21 | * **suggestion**: 内容(必需项) 22 | 23 | #### 响应结果说明 24 | 25 | **节点说明**: 26 | 27 | * code:表示响应结果状态,1表示成功,0表示失败 28 | * message:响应结果的文字说明 29 | 30 | ##### 简要示例 31 | 32 | { 33 | "code": 1, 34 | "message": "意见反馈成功" 35 | } 36 | 37 | 38 | *** 39 | 40 | ## 版本更新检查 41 | 42 | #### URL 43 | /api/version/check 44 | 45 | #### METHOD 46 | GET 47 | 48 | #### 参数 49 | 50 | * **version**:当前客户端版本号(必需项) 51 | * **client**: 客户端类型, 可选值只能是android和iphone(必需项) 52 | 53 | #### 响应结果说明 54 | 55 | **节点说明**: 56 | 57 | * code:表示响应结果状态,1表示有更新,0表示无更新 58 | * message:响应结果状态的文字说明 59 | 60 | **datum节点说明**: 61 | 62 | * message: 更新说明 63 | * url: 新版本下载地址 64 | * version: 新版本号 65 | 66 | ##### 简要示例 67 | 68 | { 69 | "code": 1, 70 | "datum": { 71 | "message": "修复bug", 72 | "url": "http://mlongbo.com/android_0_1_1.apk", 73 | "version": "0.1.2" 74 | } 75 | } 76 | 77 | *** 78 | 79 | ## 附录(很重要) 80 | 81 | ### 文件地址说明 82 | 83 | 接口中所有的文件地址都是相对路径, 需要拼接地址前缀才能正常使用. 84 | 85 | 在登录成功后,登录接口返回的`resourceServer`字段为地址前缀. 86 | 87 | 如: resourceServer为`http://mlongbo.com`, 用户头像地址为`/img/avatar/rose.jpg`, 88 | 那么完整的地址就是`http://mlongbo.com/img/avatar/rose.jpg` 89 | 90 | ### 时间戳说明 91 | 92 | 文档中提到的时间戳全部精确到毫秒 93 | 94 | ### code对照表 95 | 96 | * 1 ok - 成功状态 97 | * 0 faild 98 | * 2 argument error - 表示请求参数值有误, 或未携带需要的参数值 99 | * 3 帐号已存在 100 | * 4 验证码错误 101 | * 500 error - 服务器错误 102 | * 404 not found - 请求的资源或接口不存在 103 | * 422 token error - 未传递token参数,或token值非法 -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/utils/SMSUtils.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.utils; 2 | 3 | import java.util.HashMap; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | /** 8 | * 短信相关的工具类* 9 | * @author malongbo 10 | */ 11 | public class SMSUtils { 12 | 13 | 14 | /** 15 | * 检测手机号有效性* 16 | * @param mobile 手机号码 17 | * @return 是否有效 18 | */ 19 | public static final boolean isMobileNo(String mobile){ 20 | Pattern p = Pattern.compile("^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$"); 21 | Matcher m = p.matcher(mobile); 22 | return m.matches(); 23 | } 24 | 25 | /** 26 | * 生成短信验证码* 27 | * @param length 长度 28 | * @return 指定长度的随机短信验证码 29 | */ 30 | public static final String randomSMSCode(int length) { 31 | boolean numberFlag = true; 32 | String retStr = ""; 33 | String strTable = numberFlag ? "1234567890" : "1234567890abcdefghijkmnpqrstuvwxyz"; 34 | int len = strTable.length(); 35 | boolean bDone = true; 36 | do { 37 | retStr = ""; 38 | int count = 0; 39 | for (int i = 0; i < length; i++) { 40 | double dblR = Math.random() * len; 41 | int intR = (int) Math.floor(dblR); 42 | char c = strTable.charAt(intR); 43 | if (('0' <= c) && (c <= '9')) { 44 | count++; 45 | } 46 | retStr += strTable.charAt(intR); 47 | } 48 | if (count >= 2) { 49 | bDone = false; 50 | } 51 | } while (bDone); 52 | return retStr; 53 | } 54 | 55 | /** 56 | * 发送短信验证码* 57 | * @param mobile 手机号码 58 | * @param code 验证码 59 | * @return 是否发送成功 60 | */ 61 | public static final boolean sendCode(String mobile, String code) { 62 | 63 | //todo 这里实现短信发送功能 64 | 65 | return true; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/plugin/HikariCPPlugin.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.plugin; 2 | 3 | import com.jfinal.plugin.IPlugin; 4 | import com.jfinal.plugin.activerecord.IDataSourceProvider; 5 | import com.zaxxer.hikari.HikariConfig; 6 | import com.zaxxer.hikari.HikariDataSource; 7 | 8 | import javax.sql.DataSource; 9 | 10 | /** 11 | * * 12 | * @author malongbo 13 | */ 14 | public class HikariCPPlugin implements IPlugin,IDataSourceProvider { 15 | 16 | private String jdbcUrl; 17 | private String user; 18 | private String password; 19 | private String driverClass = "com.mysql.jdbc.Driver"; 20 | private int maxPoolSize = 10; 21 | 22 | private HikariDataSource dataSource; 23 | 24 | public HikariCPPlugin(String jdbcUrl, String user, String password) { 25 | this.jdbcUrl = jdbcUrl; 26 | this.user = user; 27 | this.password = password; 28 | } 29 | 30 | public HikariCPPlugin(String jdbcUrl, String user, String password, String driverClass, int maxPoolSize) { 31 | this.jdbcUrl = jdbcUrl; 32 | this.user = user; 33 | this.password = password; 34 | this.driverClass = driverClass; 35 | this.maxPoolSize = maxPoolSize; 36 | } 37 | 38 | @Override 39 | public boolean start() { 40 | HikariConfig config = new HikariConfig(); 41 | config.setMaximumPoolSize(maxPoolSize); 42 | // config.setDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlDataSource"); 43 | config.setDriverClassName(driverClass); 44 | config.setJdbcUrl(jdbcUrl); 45 | config.setUsername(user); 46 | config.setPassword(password); 47 | 48 | //防止中文乱码 49 | config.addDataSourceProperty("useUnicode", "true"); 50 | config.addDataSourceProperty("characterEncoding", "utf8"); 51 | 52 | config.setConnectionTestQuery("SELECT 1"); 53 | 54 | this.dataSource = new HikariDataSource(config); 55 | 56 | return true; 57 | } 58 | 59 | @Override 60 | public boolean stop() { 61 | if (dataSource != null) 62 | dataSource.close(); 63 | return true; 64 | } 65 | 66 | @Override 67 | public DataSource getDataSource() { 68 | return this.dataSource; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/api/FileAPIController.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.api; 2 | 3 | import com.jfinal.aop.Before; 4 | import com.jfinal.upload.UploadFile; 5 | import com.mlongbo.jfinal.common.bean.Code; 6 | import com.mlongbo.jfinal.common.bean.FileResponse; 7 | import com.mlongbo.jfinal.common.utils.FileUtils; 8 | import com.mlongbo.jfinal.common.utils.StringUtils; 9 | import com.mlongbo.jfinal.interceptor.TokenInterceptor; 10 | 11 | import java.io.File; 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | /** 18 | * 文件上传总的控制器,所有文件上传类表单均拆分成文件上传和文本提交 19 | * @author mlongbo 20 | */ 21 | 22 | @Before(TokenInterceptor.class) 23 | public class FileAPIController extends BaseAPIController { 24 | 25 | /** 26 | * 处理单文件或多文件上传,上传成功后,返回url集合 27 | */ 28 | public void upload(){ 29 | if (!methodType("post")) { 30 | render404(); 31 | return; 32 | } 33 | FileResponse response = new FileResponse(); 34 | try { 35 | List fileList = getFiles();//已接收到的文件 36 | if(fileList != null && !fileList.isEmpty()){ 37 | Map urls = new HashMap();//用于保存上传成功的文件地址 38 | List failedFiles = new ArrayList(); //用于保存未成功上传的文件名 39 | 40 | for(UploadFile uploadFile : fileList){ 41 | File file=uploadFile.getFile(); 42 | String urlPath = FileUtils.saveUploadFile(file); 43 | if (StringUtils.isEmpty(urlPath)) { 44 | failedFiles.add(uploadFile.getParameterName());//标记为上传失败 45 | } else { 46 | //返回相对路径,用于响应 47 | urls.put(uploadFile.getParameterName(), urlPath + file.getName()); 48 | } 49 | } 50 | response.setDatum(urls); 51 | if (failedFiles.size() > 0) { 52 | response.setCode(Code.FAIL);//表示此次上传有未上传成功的文件 53 | response.setFailed(failedFiles); 54 | } 55 | }else{ 56 | response.setCode(Code.ARGUMENT_ERROR).setMessage("uploadFileName can not be null"); 57 | } 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | response.setCode(Code.ERROR); 61 | } 62 | renderJson(response); 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/httpclient/HttpResponse.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.httpclient; 2 | 3 | 4 | import java.util.Vector; 5 | 6 | /** 7 | * Http响应对象 8 | * @author malongbo 9 | */ 10 | public final class HttpResponse { 11 | 12 | String urlString; 13 | 14 | int defaultPort; 15 | 16 | String file; 17 | 18 | String host; 19 | 20 | String path; 21 | 22 | int port; 23 | 24 | String protocol; 25 | 26 | String query; 27 | 28 | String ref; 29 | 30 | String userInfo; 31 | 32 | String contentEncoding; 33 | 34 | /** 35 | * 响应内容 36 | */ 37 | String content; 38 | 39 | String contentType; 40 | 41 | int code; 42 | 43 | String message; 44 | 45 | String method; 46 | 47 | int connectTimeout; 48 | 49 | int readTimeout; 50 | 51 | Vector contentCollection; 52 | 53 | public String getContent() { 54 | return content; 55 | } 56 | 57 | public String getContentType() { 58 | return contentType; 59 | } 60 | 61 | public int getCode() { 62 | return code; 63 | } 64 | 65 | public String getMessage() { 66 | return message; 67 | } 68 | 69 | public Vector getContentCollection() { 70 | return contentCollection; 71 | } 72 | 73 | public String getContentEncoding() { 74 | return contentEncoding; 75 | } 76 | 77 | public String getMethod() { 78 | return method; 79 | } 80 | 81 | public int getConnectTimeout() { 82 | return connectTimeout; 83 | } 84 | 85 | public int getReadTimeout() { 86 | return readTimeout; 87 | } 88 | 89 | public String getUrlString() { 90 | return urlString; 91 | } 92 | 93 | public int getDefaultPort() { 94 | return defaultPort; 95 | } 96 | 97 | public String getFile() { 98 | return file; 99 | } 100 | 101 | public String getHost() { 102 | return host; 103 | } 104 | 105 | public String getPath() { 106 | return path; 107 | } 108 | 109 | public int getPort() { 110 | return port; 111 | } 112 | 113 | public String getProtocol() { 114 | return protocol; 115 | } 116 | 117 | public String getQuery() { 118 | return query; 119 | } 120 | 121 | public String getRef() { 122 | return ref; 123 | } 124 | 125 | public String getUserInfo() { 126 | return userInfo; 127 | } 128 | 129 | } -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Set root logger level to DEBUG and its only appender to CONSOLE. 2 | log4j.rootLogger=INFO,CONSOLE 3 | #log4j.rootLogger=ERROR 4 | # CONSOLE 5 | log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender 6 | log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout 7 | log4j.appender.CONSOLE.layout.ConversionPattern=%d %p [%c] - %m %n 8 | #log4j.appender.ERROR=org.apache.log4j.ConsoleAppender 9 | #log4j.appender.ERROR.layout=org.apache.log4j.PatternLayout 10 | #log4j.appender.ERROR.layout.ConversionPattern=%d %p [%c] - <%m>%n 11 | log4j.logger.org.smslib=ERROR 12 | log4j.logger.org.mortbay.log=ERROR 13 | log4j.logger.org.apache.jasper.servlet.JspServlet=ERROR 14 | log4j.logger.org.apache.jasper.compiler.JspRuntimeContext=ERROR 15 | log4j.logger.org.apache.jasper.compiler.Compiler=ERROR 16 | 17 | log4j.logger.org.apache.mina.filter.codec.ProtocolCodecFilter=ERROR 18 | 19 | log4j.logger.org.apache.mina.core.filterchain.IoFilterEvent=ERROR 20 | log4j.logger.QueueExecutor=INFO 21 | log4j.logger.MEPushHandler=INFO 22 | log4j.logger.SenderTask=INFO 23 | 24 | # FILE 25 | log4j.appender.logfile=org.apache.log4j.RollingFileAppender 26 | log4j.appender.logfile.File=D:/home/project/work/sms2/src/main/webapp/build/deploy/logs/myweb.log 27 | log4j.appender.logfile.MaxFileSize=512KB 28 | log4j.appender.logfile.MaxBackupIndex=5 29 | log4j.appender.logfile.layout=org.apache.log4j.PatternLayout 30 | log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n 31 | # LIMIT CATEGORIES 32 | 33 | #log4j.logger.org.jbpm.graph=DEBUG 34 | 35 | # Hibernate debugging levels and their output 36 | #log4j.logger.com.opensymphony.xwork2=ERROR 37 | 38 | #Log all SQL DML statements as they are executed 39 | #log4j.logger.org.hibernate.SQL=DEBUG 40 | #Log all JDBC parameters 41 | #log4j.logger.org.hibernate.type=DEBUG 42 | #Log all SQL DDL statements as they are executed 43 | #log4j.logger.org.hibernate.tool.hbm2ddl=DEBUG 44 | #Log the state of all entities (max 20 entities) associated with the session at flush time 45 | #log4j.logger.org.hibernate.pretty=DEBUG 46 | #Log all second-level cache activity 47 | #log4j.logger.org.hibernate.cache=DEBUG 48 | #Log transaction related activity 49 | #log4j.logger.org.hibernate.transaction=DEBUG 50 | #Log all JDBC resource acquisition 51 | #log4j.logger.org.hibernate.jdbc=TRACE 52 | #Log HQL and SQL ASTs and other information about query parsing 53 | #log4j.logger.org.hibernate.hql.ast=DEBUG 54 | #Log all JAAS authorization requests 55 | #log4j.logger.org.hibernate.secure=DEBUG 56 | #Log everything (a lot of information, but very useful for troubleshooting) 57 | #log4j.logger.org.hibernate=DEBUG 58 | #log4j.logger.org.hibernate.tools=DEBUG 59 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/AppConfig.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal; 2 | 3 | import com.jfinal.config.*; 4 | import com.jfinal.plugin.activerecord.ActiveRecordPlugin; 5 | import com.jfinal.render.ViewType; 6 | import com.mlongbo.jfinal.config.Context; 7 | import com.mlongbo.jfinal.handler.ContextHandler; 8 | import com.mlongbo.jfinal.interceptor.ErrorInterceptor; 9 | import com.mlongbo.jfinal.model.*; 10 | import com.mlongbo.jfinal.plugin.HikariCPPlugin; 11 | import com.mlongbo.jfinal.router.APIRouter; 12 | import com.mlongbo.jfinal.handler.APINotFoundHandler; 13 | import com.mlongbo.jfinal.router.ActionRouter; 14 | 15 | /** 16 | * JFinal总配置文件,挂接所有接口与插件 17 | * @author mlongbo 18 | */ 19 | public class AppConfig extends JFinalConfig { 20 | 21 | /** 22 | * 常量配置 23 | */ 24 | @Override 25 | public void configConstant(Constants me) { 26 | me.setDevMode(true);//开启开发模式 27 | me.setEncoding("UTF-8"); 28 | me.setViewType(ViewType.JSP); 29 | } 30 | 31 | /** 32 | * 所有接口配置 33 | */ 34 | @Override 35 | public void configRoute(Routes me) { 36 | me.add(new APIRouter());//接口路由 37 | me.add(new ActionRouter()); //页面路由 38 | } 39 | 40 | /** 41 | * 插件配置 42 | */ 43 | @Override 44 | public void configPlugin(Plugins me) { 45 | // C3p0Plugin cp = new C3p0Plugin(loadPropertyFile("jdbc.properties")); 46 | // me.add(cp); 47 | 48 | //初始化连接池插件 49 | loadPropertyFile("jdbc.properties"); 50 | HikariCPPlugin hcp = new HikariCPPlugin(getProperty("jdbcUrl"), 51 | getProperty("user"), 52 | getProperty("password"), 53 | getProperty("driverClass"), 54 | getPropertyToInt("maxPoolSize")); 55 | 56 | me.add(hcp); 57 | 58 | ActiveRecordPlugin arp = new ActiveRecordPlugin(hcp); 59 | me.add(arp); 60 | 61 | arp.addMapping("t_user", User.USER_ID, User.class);//用户表 62 | arp.addMapping("t_register_code", RegisterCode.MOBILE, RegisterCode.class); //注册验证码对象 63 | arp.addMapping("t_feedback", FeedBack.class); //意见反馈表 64 | } 65 | 66 | /** 67 | * 拦截器配置 68 | */ 69 | @Override 70 | public void configInterceptor(Interceptors me) { 71 | me.add(new ErrorInterceptor()); 72 | 73 | } 74 | 75 | /** 76 | * handle 配置* 77 | */ 78 | @Override 79 | public void configHandler(Handlers me) { 80 | me.add(new ContextHandler()); 81 | me.add(new APINotFoundHandler()); 82 | } 83 | 84 | @Override 85 | public void afterJFinalStart() { 86 | Context.me().init(); 87 | } 88 | 89 | @Override 90 | public void beforeJFinalStop() { 91 | Context.me().destroy(); 92 | } 93 | } -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/utils/DateUtils.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.utils; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Calendar; 6 | import java.util.Date; 7 | 8 | /** 9 | * 时间工具 10 | * @author malongbo 11 | */ 12 | public final class DateUtils { 13 | /** 14 | * 获得当前时间 15 | * 格式为:yyyy-MM-dd HH:mm:ss 16 | */ 17 | public static String getNowTime() { 18 | Date nowday = new Date(); 19 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 精确到秒 20 | String time = sdf.format(nowday); 21 | return time; 22 | } 23 | 24 | /** 25 | * 获取当前系统时间戳 26 | * @return 27 | */ 28 | public static Long getNowTimeStamp() { 29 | return System.currentTimeMillis(); 30 | } 31 | 32 | public static Long getNowDateTime() { 33 | return new Date().getTime()/1000; 34 | // return new Date().getTime()/1000; 35 | } 36 | 37 | /** 38 | * 自定义日期格式 39 | * @param format 40 | * @return 41 | */ 42 | public static String getNowTime(String format) { 43 | Date nowday = new Date(); 44 | SimpleDateFormat sdf = new SimpleDateFormat(format);// 精确到秒 45 | String time = sdf.format(nowday); 46 | return time; 47 | } 48 | 49 | /** 50 | * 将时间字符转成Unix时间戳 51 | * @param timeStr 52 | * @return 53 | * @throws java.text.ParseException 54 | */ 55 | public static Long getTime(String timeStr) throws ParseException { 56 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 精确到秒 57 | Date date = sdf.parse(timeStr); 58 | return date.getTime()/1000; 59 | } 60 | 61 | /** 62 | * 将Unix时间戳转成时间字符 63 | * @param timestamp 64 | * @return 65 | */ 66 | public static String getTime(long timestamp) { 67 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 精确到秒 68 | Date date = new Date(timestamp*1000); 69 | return sdf.format(date); 70 | } 71 | 72 | /** 73 | * 获取半年后的时间 74 | * 时间字符格式为:yyyy-MM-dd HH:mm:ss 75 | * @return 时间字符串 76 | */ 77 | public static String getHalfYearLaterTime() { 78 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 精确到秒 79 | 80 | Calendar calendar = Calendar.getInstance(); 81 | int currMonth = calendar.get(Calendar.MONTH) + 1; 82 | 83 | if (currMonth >= 1 && currMonth <= 6) { 84 | calendar.add(Calendar.MONTH, 6); 85 | } else { 86 | calendar.add(Calendar.YEAR, 1); 87 | calendar.set(Calendar.MONTH, currMonth - 6 - 1); 88 | } 89 | 90 | 91 | return sdf.format(calendar.getTime()); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/api/CommonAPIController.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.api; 2 | 3 | import com.jfinal.aop.Before; 4 | import com.jfinal.core.ActionKey; 5 | import com.mlongbo.jfinal.common.bean.BaseResponse; 6 | import com.mlongbo.jfinal.common.bean.Code; 7 | import com.mlongbo.jfinal.common.bean.DatumResponse; 8 | import com.mlongbo.jfinal.common.Require; 9 | import com.mlongbo.jfinal.common.utils.DateUtils; 10 | import com.mlongbo.jfinal.interceptor.TokenInterceptor; 11 | import com.mlongbo.jfinal.model.FeedBack; 12 | import com.mlongbo.jfinal.model.User; 13 | import com.mlongbo.jfinal.version.Version; 14 | import com.mlongbo.jfinal.version.VersionManager; 15 | 16 | /** 17 | * 公共模块的api* 18 | * 19 | * 意见反馈: POST /api/feedback 20 | * 版本更新检查: GET /api/version/check 21 | * 22 | * @author malongbo 23 | */ 24 | public class CommonAPIController extends BaseAPIController{ 25 | 26 | /** 27 | * 处理用户意见反馈 28 | */ 29 | @Before(TokenInterceptor.class) 30 | public void feedback(){ 31 | if (!"post".equalsIgnoreCase(getRequest().getMethod())) { 32 | renderJson(new BaseResponse(Code.NOT_FOUND)); 33 | return; 34 | } 35 | 36 | //内容 37 | String suggestion=getPara("suggestion"); 38 | if(!notNull(Require.me() 39 | .put(suggestion, "suggestion can not be null"))){ 40 | return; 41 | } 42 | 43 | FeedBack feedBack = new FeedBack().set(FeedBack.SUGGESTION, suggestion) 44 | .set(FeedBack.CREATION_DATE, DateUtils.getNowTimeStamp()); 45 | 46 | User user = getUser(); 47 | if (user != null) { 48 | feedBack.set(FeedBack.USER_ID, user.userId()); 49 | } 50 | 51 | //保存反馈 52 | boolean flag = feedBack.save(); 53 | 54 | renderJson(new BaseResponse(flag ? Code.SUCCESS : Code.FAIL, flag ? "意见反馈成功" : "意见反馈失败")); 55 | } 56 | 57 | /** 58 | * 版本更新检查* 59 | */ 60 | @ActionKey("/api/version/check") 61 | public void checkVersion() { 62 | String version = getPara("version");//版本号 63 | String client = getPara("client"); //终端类型, 可选值有android, iphone 64 | if (!notNull(Require.me() 65 | .put(version, "version can not be null") 66 | .put(client, "client can not be null"))) { 67 | return; 68 | } 69 | 70 | //检查值是否有效 71 | if (!Version.checkType(client)) { 72 | renderArgumentError("client is invalid"); 73 | return; 74 | } 75 | 76 | Version result = VersionManager.me().check(version, client); 77 | DatumResponse response = new DatumResponse(result); 78 | if (result == null) { 79 | response.setCode(Code.FAIL);//表示无更新 80 | } 81 | 82 | renderJson(response); 83 | 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/utils/FileType.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.utils; 2 | 3 | /** 4 | * 文件类型枚取* 5 | * @author malongbo 6 | */ 7 | public enum FileType { 8 | 9 | /** 10 | * JEPG. 11 | */ 12 | JPEG("FFD8FF"), 13 | 14 | /** 15 | * PNG. 16 | */ 17 | PNG("89504E47"), 18 | 19 | /** 20 | * GIF. 21 | */ 22 | GIF("47494638"), 23 | 24 | /** 25 | * TIFF. 26 | */ 27 | TIFF("49492A00"), 28 | 29 | /** 30 | * Windows Bitmap. 31 | */ 32 | BMP("424D"), 33 | 34 | /** 35 | * CAD. 36 | */ 37 | DWG("41433130"), 38 | 39 | /** 40 | * Adobe Photoshop. 41 | */ 42 | PSD("38425053"), 43 | 44 | /** 45 | * Rich Text Format. 46 | */ 47 | RTF("7B5C727466"), 48 | 49 | /** 50 | * XML. 51 | */ 52 | XML("3C3F786D6C"), 53 | 54 | /** 55 | * HTML. 56 | */ 57 | HTML("68746D6C3E"), 58 | 59 | /** 60 | * Email [thorough only]. 61 | */ 62 | EML("44656C69766572792D646174653A"), 63 | 64 | /** 65 | * Outlook Express. 66 | */ 67 | DBX("CFAD12FEC5FD746F"), 68 | 69 | /** 70 | * Outlook (pst). 71 | */ 72 | PST("2142444E"), 73 | 74 | /** 75 | * MS Word/Excel. 76 | */ 77 | XLS_DOC("D0CF11E0"), 78 | 79 | /** 80 | * MS Access. 81 | */ 82 | MDB("5374616E64617264204A"), 83 | 84 | /** 85 | * WordPerfect. 86 | */ 87 | WPD("FF575043"), 88 | 89 | /** 90 | * Postscript. 91 | */ 92 | EPS("252150532D41646F6265"), 93 | 94 | /** 95 | * Adobe Acrobat. 96 | */ 97 | PDF("255044462D312E"), 98 | 99 | /** 100 | * Quicken. 101 | */ 102 | QDF("AC9EBD8F"), 103 | 104 | /** 105 | * Windows Password. 106 | */ 107 | PWL("E3828596"), 108 | 109 | /** 110 | * ZIP Archive. 111 | */ 112 | ZIP("504B0304"), 113 | 114 | /** 115 | * RAR Archive. 116 | */ 117 | RAR("52617221"), 118 | 119 | /** 120 | * Wave. 121 | */ 122 | WAV("57415645"), 123 | 124 | /** 125 | * AVI. 126 | */ 127 | AVI("41564920"), 128 | 129 | /** 130 | * Real Audio. 131 | */ 132 | RAM("2E7261FD"), 133 | 134 | /** 135 | * Real Media. 136 | */ 137 | RM("2E524D46"), 138 | 139 | /** 140 | * MPEG (mpg). 141 | */ 142 | MPG("000001BA"), 143 | 144 | /** 145 | * Quicktime. 146 | */ 147 | MOV("6D6F6F76"), 148 | 149 | /** 150 | * Windows Media. 151 | */ 152 | ASF("3026B2758E66CF11"), 153 | 154 | /** 155 | * MIDI. 156 | */ 157 | MID("4D546864"); 158 | 159 | private String value = ""; 160 | 161 | /** 162 | * Constructor. 163 | */ 164 | private FileType(String value) { 165 | this.value = value; 166 | } 167 | 168 | public String getValue() { 169 | return value; 170 | } 171 | 172 | public void setValue(String value) { 173 | this.value = value; 174 | } 175 | } -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/api/BaseAPIController.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.api; 2 | 3 | import com.jfinal.core.Controller; 4 | import com.mlongbo.jfinal.common.bean.DataResponse; 5 | import com.mlongbo.jfinal.common.Require; 6 | import com.mlongbo.jfinal.common.utils.StringUtils; 7 | import com.mlongbo.jfinal.model.User; 8 | import com.mlongbo.jfinal.common.bean.BaseResponse; 9 | import com.mlongbo.jfinal.common.bean.Code; 10 | import com.mlongbo.jfinal.common.token.TokenManager; 11 | 12 | import java.lang.reflect.Array; 13 | import java.util.List; 14 | 15 | /** 16 | * 基本的api 17 | * 基于jfinal controller做一些封装 18 | * @author malongbo 19 | */ 20 | public class BaseAPIController extends Controller { 21 | 22 | /** 23 | * 获取当前用户对象 24 | * @return 25 | */ 26 | protected User getUser() { 27 | User user = getAttr("user"); 28 | if (user == null) { 29 | String token = getPara("token"); 30 | return StringUtils.isEmpty(token) ? null : TokenManager.getMe().validate(token); 31 | } 32 | return getAttr("user"); 33 | } 34 | 35 | /** 36 | * 响应接口不存在* 37 | */ 38 | public void render404() { 39 | 40 | renderJson(new BaseResponse(Code.NOT_FOUND)); 41 | 42 | } 43 | 44 | /** 45 | * 响应请求参数有误* 46 | * @param message 错误信息 47 | */ 48 | public void renderArgumentError(String message) { 49 | 50 | renderJson(new BaseResponse(Code.ARGUMENT_ERROR, message)); 51 | 52 | } 53 | 54 | /** 55 | * 响应数组类型* 56 | * @param list 结果集合 57 | */ 58 | public void renderDataResponse(List list) { 59 | DataResponse resp = new DataResponse(); 60 | resp.setData(list); 61 | if (list == null || list.size() == 0) { 62 | resp.setMessage("未查询到数据"); 63 | } else { 64 | resp.setMessage("success"); 65 | } 66 | renderJson(resp); 67 | 68 | } 69 | 70 | /** 71 | * 响应操作成功* 72 | * @param message 响应信息 73 | */ 74 | public void renderSuccess(String message) { 75 | renderJson(new BaseResponse().setMessage(message)); 76 | 77 | } 78 | 79 | /** 80 | * 响应操作失败* 81 | * @param message 响应信息 82 | */ 83 | public void renderFailed(String message) { 84 | renderJson(new BaseResponse(Code.FAIL, message)); 85 | 86 | } 87 | /** 88 | * 判断请求类型是否相同* 89 | * @param name 90 | * @return 91 | */ 92 | protected boolean methodType(String name) { 93 | return getRequest().getMethod().equalsIgnoreCase(name); 94 | } 95 | 96 | /** 97 | * 判断参数值是否为空 98 | * @param rules 99 | * @return 100 | */ 101 | public boolean notNull(Require rules) { 102 | 103 | if (rules == null || rules.getLength() < 1) { 104 | return true; 105 | } 106 | 107 | for (int i = 0, total = rules.getLength(); i < total; i++) { 108 | Object key = rules.get(i); 109 | String message = rules.getMessage(i); 110 | BaseResponse response = new BaseResponse(Code.ARGUMENT_ERROR); 111 | 112 | if (key == null) { 113 | renderJson(response.setMessage(message)); 114 | return false; 115 | } 116 | 117 | if (key instanceof String && StringUtils.isEmpty((String) key)) { 118 | renderJson(response.setMessage(message)); 119 | return false; 120 | } 121 | 122 | if (key instanceof Array) { 123 | Object[] arr = (Object[]) key; 124 | 125 | if (arr.length < 1) { 126 | renderJson(response.setMessage(message)); 127 | return false; 128 | } 129 | } 130 | } 131 | 132 | return true; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/config/AppProperty.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.config; 2 | 3 | import com.mlongbo.jfinal.common.XmlProperty; 4 | 5 | import java.io.IOException; 6 | 7 | /** 8 | * @author malongbo 9 | */ 10 | public class AppProperty { 11 | private XmlProperty property; 12 | private String propertyName = "configure.xml"; 13 | private static AppProperty instance = new AppProperty(); 14 | 15 | public AppProperty() { 16 | } 17 | 18 | public AppProperty(String propertyName) { 19 | this.propertyName = propertyName; 20 | } 21 | 22 | 23 | public static final AppProperty me() { 24 | return instance; 25 | 26 | } 27 | 28 | protected AppProperty init() { 29 | 30 | try { 31 | property = new XmlProperty(AppProperty.class.getResource("/"+propertyName).getFile()); 32 | } catch (IOException e) { 33 | e.printStackTrace(); 34 | } 35 | return this; 36 | } 37 | 38 | protected void destroy() { 39 | property.destroy(); 40 | property = null; 41 | } 42 | 43 | public String getProperty(String key) { 44 | if (property == null) { 45 | return null; 46 | } 47 | return property.getProperty(key); 48 | } 49 | 50 | public String getProperty(String key, String defaultValue) { 51 | String value = getProperty(key); 52 | if (value == null) { 53 | return defaultValue; 54 | } 55 | 56 | return value; 57 | 58 | } 59 | 60 | public Integer getPropertyToInt(String key, Integer defaultValue) { 61 | try { 62 | return Integer.valueOf(getProperty(key)); 63 | } catch (Exception e) { 64 | return defaultValue; 65 | } 66 | } 67 | 68 | public Integer getPropertyToInt(String key) { 69 | return getPropertyToInt(key, null); 70 | } 71 | 72 | public Double getPropertyToDouble(String key, Double defaultValue) { 73 | try { 74 | return Double.valueOf(getProperty(key)); 75 | } catch (Exception e) { 76 | return defaultValue; 77 | } 78 | } 79 | 80 | public Double getPropertyToDouble(String key) { 81 | return getPropertyToDouble(key, null); 82 | } 83 | 84 | public Float getPropertyToFloat(String key, Float defaultValue) { 85 | try { 86 | return Float.valueOf(getProperty(key)); 87 | } catch (Exception e) { 88 | return defaultValue; 89 | } 90 | } 91 | 92 | public Float getPropertyToFloat(String key) { 93 | return getPropertyToFloat(key, null); 94 | } 95 | 96 | public Boolean getPropertyToBoolean(String key, Boolean defaultValue) { 97 | try { 98 | return Boolean.valueOf(getProperty(key)); 99 | } catch (Exception e) { 100 | return defaultValue; 101 | } 102 | } 103 | 104 | public Boolean getPropertyToBoolean(String key) { 105 | return getPropertyToBoolean(key, null); 106 | } 107 | 108 | public String resourcePrefix() { 109 | return getProperty(AppConstant.RES_PREFIX); 110 | } 111 | 112 | public int appPath() { 113 | return getPropertyToInt(AppConstant.RES_APP_PATH, 1); 114 | } 115 | 116 | public String uploadRooPath() { 117 | return getProperty(AppConstant.RES_UPLOAD_ROOT_PATH, "upload"); 118 | } 119 | 120 | public String imagePath() { 121 | return getProperty(AppConstant.RES_IMAGE_PATH, "/images"); 122 | } 123 | 124 | public String videoPath() { 125 | return getProperty(AppConstant.RES_VIDEO_PATH, "/videoPath"); 126 | } 127 | 128 | public String otherPath() { 129 | return getProperty(AppConstant.RES_OTHER_PATH, "/otherPath"); 130 | } 131 | 132 | public String defaultUserAvatar() { 133 | return getProperty(AppConstant.RES_DEFAULT_USER_AVATAR, "/defaultUserAvatar"); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/utils/RandomUtils.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.utils; 2 | 3 | import java.util.Random; 4 | import java.util.UUID; 5 | 6 | /** 7 | * @author malongbo 8 | */ 9 | public final class RandomUtils { 10 | 11 | /** 12 | * Array of numbers and letters of mixed case. Numbers appear in the list 13 | * twice so that there is a more equal chance that a number will be picked. 14 | * We can use the array to get a random number or letter by picking a random 15 | * array index. 16 | */ 17 | private static char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz" + 18 | "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray(); 19 | 20 | 21 | /** 22 | * 根据指定长度随机生成小写字母 23 | * @param length 长度 24 | * @return 指定长度的随机小写字母字符串 25 | */ 26 | public static String randomLowerWords(int length) { 27 | /* 28 | 0~9的ASCII为48~57 29 | A~Z的ASCII为65~90 30 | a~z的ASCII为97~122 31 | */ 32 | StringBuilder sb = new StringBuilder(); 33 | Random randData = new Random(); 34 | 35 | int data = 0; 36 | for(int i = 0; i < length; i++) 37 | { 38 | data=randData.nextInt(26)+97;//保证只会产生97~122之间的整数 39 | sb.append((char)data); 40 | } 41 | return sb.toString(); 42 | } 43 | 44 | /** 45 | * 根据指定长度随机生成大写字母 46 | * @param length 长度 47 | * @return 指定长度的随机大写字母字符串 48 | */ 49 | public static String randomUpperWords(int length) { 50 | /* 51 | 0~9的ASCII为48~57 52 | A~Z的ASCII为65~90 53 | a~z的ASCII为97~122 54 | */ 55 | StringBuilder sb = new StringBuilder(); 56 | Random randData = new Random(); 57 | 58 | int data = 0; 59 | for(int i = 0; i < length; i++) 60 | { 61 | data=randData.nextInt(26)+65;//保证只会产生97~122之间的整数 62 | sb.append((char)data); 63 | } 64 | return sb.toString(); 65 | } 66 | 67 | /** 68 | * 根据指定长度随机生成数字 69 | * @param length 长度 70 | * @return 指定长度的随机数字 71 | */ 72 | public static String randomNumbers(int length) { 73 | /* 74 | 0~9的ASCII为48~57 75 | A~Z的ASCII为65~90 76 | a~z的ASCII为97~122 77 | */ 78 | StringBuilder sb = new StringBuilder(); 79 | Random randData = new Random(); 80 | 81 | int data = 0; 82 | for(int i = 0; i < length; i++) 83 | { 84 | data=randData.nextInt(10);//仅仅会生成0~9 85 | sb.append(data); 86 | } 87 | return sb.toString(); 88 | } 89 | 90 | /** 91 | * 生成32位UUID字符,去除字符'-' 92 | * @return 32位随机UUID字符串 93 | */ 94 | public static String randomCustomUUID() { 95 | UUID uuid = UUID.randomUUID(); 96 | String uuidStr = uuid.toString(); 97 | 98 | return uuidStr.replaceAll("-",""); 99 | } 100 | 101 | /** 102 | * 生成36位UUID字符 103 | * @return 36未随机UUID字符串 104 | */ 105 | public static String randomUUID() { 106 | return UUID.randomUUID().toString(); 107 | } 108 | 109 | /** 110 | * Returns a random String of numbers and letters (lower and upper case) 111 | * of the specified length. The method uses the Random class that is 112 | * built-in to Java which is suitable for low to medium grade security uses. 113 | * This means that the output is only pseudo random, i.e., each number is 114 | * mathematically generated so is not truly random.

115 | *

116 | * The specified length must be at least one. If not, the method will return 117 | * null. 118 | * 119 | * @param length the desired length of the random String to return. 120 | * @return a random String of numbers and letters of the specified length. 121 | */ 122 | public static String randomString(int length) { 123 | if (length < 1) { 124 | return null; 125 | } 126 | // Create a char buffer to put random letters and numbers in. 127 | char[] randBuffer = new char[length]; 128 | for (int i = 0; i < randBuffer.length; i++) { 129 | randBuffer[i] = numbersAndLetters[new Random().nextInt(71)]; 130 | } 131 | return new String(randBuffer); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /doc/user.md: -------------------------------------------------------------------------------- 1 | ## account 2 | 3 | ### 检查账号是否被注册 4 | 5 | #### URL 6 | /api/account/checkUser 7 | 8 | #### METHOD 9 | GET 10 | 11 | #### 参数 12 | * **loginName**:唯一登录名, 即手机号(必填项) 13 | 14 | #### 响应 15 | 16 | ##### 结果说明 17 | 18 | code值含义: 19 | 20 | * 1表示已被注册 21 | * 0表示未被注册 22 | 23 | ##### 简要示例 24 | 25 | { 26 | "code":1, 27 | "message": "registered" 28 | } 29 | 30 | 31 | ### 发送手机验证码 32 | 33 | #### URL 34 | /api/account/sendCode 35 | 36 | #### METHOD 37 | POST(推荐)或GET 38 | 39 | #### 参数 40 | * **loginName**:唯一登录名, 即手机号(必填项) 41 | 42 | #### 响应 43 | 44 | ##### 结果说明 45 | 46 | code值含义: 47 | 48 | * 1表示已发送 49 | * 3表示该手机已被注册 50 | * 0表示发送失败 51 | 52 | ##### 简要示例 53 | 54 | { 55 | "code":1, 56 | "message": "验证码已发送" 57 | } 58 | 59 | 60 | ### 注册 61 | 62 | #### URL 63 | /api/account/register 64 | 65 | #### METHOD 66 | POST 67 | 68 | #### 参数 69 | * **loginName**:唯一登录名, 即手机号码(必填项) 70 | * **password**:密码(必填项) 71 | * **code**: 手机验证码(必需项) 72 | * **nickName**:用户昵称(必填项) 73 | * **avatar**:用户头像url(选填项) 74 | 75 | #### 响应 76 | { 77 | "code":1, 78 | "message": "注册成功" 79 | } 80 | 81 | ##### 结果说明 82 | code为1表示注册成功,如果用户头像URL为空,则会使用默认的用户头像。code不为1表示注册失败 83 | 84 | 85 | ### 登录 86 | 87 | #### URL 88 | /api/account/login 89 | 90 | #### METHOD 91 | POST 92 | 93 | #### 参数 94 | 95 | * **loginName**:用户登录名, 即手机号码(必需项) 96 | * **password**:登录密码(必需项) 97 | 98 | #### 登录成功后响应如下: 99 | 100 | { 101 | message: "login success", 102 | constant: { 103 | resourceServer: "http://mlongbo.com/upload" 104 | }, 105 | token: "9afe4bda832c4b7aa0d255f6bc86486cJ96XKU", 106 | code: 1, 107 | info: { 108 | sex: 1, 109 | status: 1, 110 | avatar: "/imgs/avatar.jpg", 111 | creationDate: 1422782128161, 112 | nickName: "Longbo Ma", 113 | email: "mlongbo@gmail.com", 114 | userId: "f0f504f5710946e5b0a1d6a1acde4210" 115 | } 116 | } 117 | 118 | ##### 结果说明 119 | 120 | 如果code值是0,则可能没有其他节点。token值是全局的,请求请他接口,必须带这个值,否则接口权限将会拒绝访问,通过token值可以获取当前用户ID,即不用再提交用户ID。 121 | 122 | **主节点说明**: 123 | 124 | * code:表示响应结果状态,1表示成功,0表示用户名或密码错误 125 | * message:响应结果的文字说明 126 | * token: 接口口令,与该用户对应 127 | * info:用户信息 128 | * constant: 一些固定变量 129 | 130 | **info节点说明**: 131 | 132 | * userId:用户ID 133 | * nickName:昵称 134 | * email:邮箱 135 | * avatar: 头像地址 136 | * sex: 性别, 0表示女, 1表示男 137 | * status: 账号状态, 1表示启用(正常使用), 0表示禁用 138 | * creationDate: 注册时间 139 | 140 | **constant节点说明**: 141 | 142 | * resourceServer: 文件下载地址前缀,后接文件地址即可使用 143 | 144 | *** 145 | 146 | ### 查询用户资料 147 | 148 | #### URL 149 | /api/account/profile 150 | 151 | #### METHOD 152 | GET(必须是get方法,否则请求出错) 153 | 154 | #### 参数 155 | 156 | * token:令牌符(必需项) 157 | * userId:要查询的用户ID(可选项,如果没有则查询当前用户的信息) 158 | 159 | 160 | #### 响应 161 | ##### 结果说明 162 | 163 | **节点说明**: 164 | 165 | * code:表示响应结果状态,1表示查询成功,0表示未查询到信息 166 | * message:响应结果的文字说明 167 | 168 | **datum节点说明**: 169 | 170 | * userId:用户ID 171 | * nickName:昵称 172 | * email:邮箱 173 | * avatar: 头像地址 174 | * sex: 性别, 0表示女, 1表示男 175 | * status: 账号状态, 1表示启用(正常使用), 0表示禁用 176 | * creationDate: 注册时间 177 | 178 | ##### 简要示例 179 | 180 | { 181 | "code": 1, 182 | "datum": { 183 | sex: 1, 184 | status: 1, 185 | avatar: "/imgs/avatar.jpg", 186 | creationDate: 1422782128161, 187 | nickName: "Longbo Ma", 188 | email: "mlongbo@gmail.com", 189 | userId: "f0f504f5710946e5b0a1d6a1acde4210" 190 | } 191 | } 192 | 193 | ### 修改资料 194 | 195 | #### 说明 196 | 197 | 除token必须以外,修改哪个参数,就传递哪个参数 198 | 199 | #### URL 200 | /api/account/profile 201 | 202 | #### METHOD 203 | PUT 204 | 205 | #### 参数 206 | 207 | * token:令牌符(必需项) 208 | * nickName:姓名(可选项) 209 | * sex:姓名(可选项) 210 | * avatar:姓名(可选项) 211 | * email:邮箱(可选项) 212 | 213 | 214 | #### 响应结果说明 215 | 216 | * code:表示响应结果状态,1表示成功,0表示失败 217 | * message:响应结果的文字说明 218 | 219 | ##### 简要示例 220 | 221 | { 222 | "code": 1, 223 | "message": "修改成功" 224 | } 225 | 226 | *** 227 | 228 | ### 修改密码 229 | 230 | #### URL 231 | /api/account/password 232 | 233 | #### METHOD 234 | PUT 235 | 236 | #### 参数 237 | 238 | * token:令牌符(必需项) 239 | * oldPwd:旧密码(必需项) 240 | * newPwd:新密码(必需项) 241 | 242 | #### 响应结果说明 243 | 244 | * code:表示响应结果状态,1表示成功,0表示失败 245 | * message:响应结果的文字说明 246 | 247 | ##### 简要示例 248 | 249 | { 250 | "code": 1, 251 | "message": "修改成功" 252 | } 253 | 254 | *** 255 | 256 | ### 修改头像 257 | 258 | #### URL 259 | /api/account/avatar 260 | 261 | #### METHOD 262 | PUT 263 | 264 | #### 参数 265 | 266 | * **token**:令牌(必需项) 267 | * **avatar**:头像文件地址,请先使用文件上传接口获取上传后的访问路径(必需项) 268 | 269 | #### 响应结果说明 270 | 271 | * code:表示响应结果状态,1表示成功,0表示失败 272 | * message:响应结果的文字说明 273 | 274 | ##### 简要示例 275 | 276 | { 277 | "code": 1, 278 | "message": "update avatar success" 279 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.mlongbo 8 | ${projectName} 9 | 1.0 10 | war 11 | ${projectName} 12 | https://github.com/lenbo-ma/jfinal-template 13 | 14 | 15 | 1.6 16 | 1.6 17 | UTF-8 18 | 1.6 19 | jfinal-template 20 | 8088 21 | 22 | 23 | 24 | junit 25 | junit 26 | 3.8.1 27 | test 28 | 29 | 30 | 31 | javax.servlet 32 | servlet-api 33 | 2.5 34 | 35 | 36 | org.slf4j 37 | slf4j-api 38 | 1.7.7 39 | 40 | 41 | org.slf4j 42 | slf4j-log4j12 43 | 1.7.2 44 | 45 | 46 | log4j 47 | log4j 48 | 1.2.17 49 | 50 | 51 | 52 | com.jfinal 53 | cos 54 | 26Dec2008 55 | 56 | 57 | 58 | 59 | com.jfinal 60 | jfinal 61 | 1.9 62 | 63 | 64 | mysql 65 | mysql-connector-java 66 | 5.1.34 67 | 68 | 69 | 74 | 75 | com.zaxxer 76 | HikariCP 77 | 1.3.5 78 | 79 | 80 | dom4j 81 | dom4j 82 | 1.6.1 83 | 84 | 85 | 86 | 87 | 88 | ${projectName} 89 | 90 | 91 | org.mortbay.jetty 92 | maven-jetty-plugin 93 | 6.1.10 94 | 95 | 9966 96 | stop-jetty-for-it 97 | / 98 | 99 | 100 | ${jettyPort} 101 | 60000 102 | 103 | 104 | 105 | 106 | 107 | start-jetty 108 | pre-integration-test 109 | 110 | run 111 | 112 | 113 | true 114 | 115 | 116 | 117 | stop-jetty 118 | post-integration-test 119 | 120 | stop 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jfinal-api-scaffold 2 | 3 | ### 项目介绍 4 | 5 | 实际上这个项目更像一个脚手架,是我多次开发HTTP API应用的经验总结。其中包含了常用的模块(如账户相关,版本更新等),以及本人认为比较好的开发方式和规范。 6 | 7 | ### 项目配置 8 | 9 | * **version.xml:** 存放版本更新信息。entry节点代表一个版本,可设置一到多个,可自由切换。android和iphone节点的default属性表示当前的版本号, 对应entry的version节点值; 10 | 11 | * **jdbc.properties:** 这个都懂的,存放数据库连接信息; 12 | * **configure.xml:** root下的子节点可以随便写,但不能包含属性。在服务器运行过程中,一旦此文件内容发生了变化,会实时生效,无需重启。 13 | 14 | 举例,从下面的文件中获取prefix节点的值可使用`AppProperty.me().getProperty("resource.prefix");`读取. 15 | ```xml 16 | 17 | 18 | 19 | 20 | http://mlongbo.com/upload 21 | 22 | 23 | ``` 24 | 25 | AppConstant类存放诸如`resource.prefix`的配置常量, AppProperty类用于读取配置,因此建议使用如: 26 | 27 | ```java 28 | //AppContant类的伪代码 29 | public static final String RES_PREFIX = "resource.prefix"; 30 | 31 | //AppProperty类的伪代码 32 | public String resourcePrefix() { 33 | return getProperty(AppConstant.RES_PREFIX); 34 | } 35 | 36 | //业务模块中调用。获取配置 37 | String str = AppProperty.me().resourcePrefix(); 38 | ``` 39 | 40 | ### 已实现的常用接口列表 41 | 42 | * 检查账号是否被注册: `GET` `/api/account/checkUser` 43 | * 发送注册验证码: `POST` `/api/account/sendCode` 44 | * 注册: `POST` `/api/account/register` 45 | * 登录: `POST` `/api/account/login` 46 | * 查询用户资料: `GET` `/api/account/profile` 47 | * 修改用户资料: `PUT` `/api/account/profile` 48 | * 修改密码: `PUT` `/api/account/password` 49 | * 修改头像: `PUT` `/api/account/avatar` 50 | * 意见反馈: `POST` `/api/feedback` 51 | * 版本更新检查: `GET` `/api/version/check` 52 | * 文件上传: `POST` `/api/fs/upload` 53 | 54 | ### 数据响应规范 55 | 56 | 避免手拼json导致的错误,而使用将Java Bean序列化为JSON的方式。 57 | 58 | json数据的根节点使用code字段标识本次响应的状态,如成功、失败、参数缺少或参数值有误,以及服务器错误等;message节点包含服务器响应回来的一些提示信息,主要是方便客户端开发人员在对接接口时定位错误出现的原因。 59 | 60 | 一般的,响应数据会分为两种,第一种为列表数据,如一组用户信息,另外一种是实体信息,如一条用户信息。data字段值为数组,携带列表数据,datum字段值为json对象,携带实体数据。 61 | 62 | 如: 63 | ```javascript 64 | //实体数据, 此结构对应DatumResponse类 65 | { 66 | "code": 1, 67 | "message": "成功查询到该用户信息", 68 | "datum": { 69 | "name": "jack", 70 | "lover": "rose", 71 | "sex": 1, 72 | "email": "jack@gmail.com" 73 | } 74 | } 75 | //列表数据, 此结构对应DataResponse类 76 | { 77 | "code": 1, 78 | "message": "成功查询到两条用户信息", 79 | "data": [{ 80 | "name": "jack", 81 | "lover": "rose", 82 | "sex": 1, 83 | "email": "jack@gmail.com" 84 | },{ 85 | "name": "rose", 86 | "lover": "jack", 87 | "sex": 0, 88 | "email": "rose@gmail.com" 89 | }] 90 | } 91 | //登录成功, 此结构对应LoginResponse类 92 | { 93 | "code": 1, 94 | "message": "登录成功", 95 | "constant": { 96 | "resourceServer": "http://fs.mlongbo.com" //文件地址前缀 97 | } 98 | "info": { 99 | "name": "jack", 100 | "lover": "rose", 101 | "sex": 1, 102 | "email": "jack@gmail.com" 103 | } 104 | } 105 | //多文件上传, 部分成功,部分失败. 此结构对应FileResponse类 106 | { 107 | "code": 0, //只要有一个文件上传失败,code就会是0 108 | "failed": ["file3"] 109 | "datum": { 110 | "file1": "/upload/images/file1.jpg", 111 | "file2": "/upload/images/file2.jpg" 112 | } 113 | } 114 | //缺少参数 115 | { 116 | "code": 2, 117 | "message": "缺少name参数" 118 | } 119 | //参数值有误 120 | { 121 | "code": 2, 122 | "message": "sex参数值只能为0或1" 123 | } 124 | 125 | //token无效 126 | { 127 | "code": 422, 128 | "message": "token值是无效的,请重新登录获取" 129 | } 130 | ``` 131 | 132 | 附上本人常用的几种code值: 133 | 134 | 135 | * 1 ok - 成功状态。查询成功,操作成功等; 136 | * 0 faild - 失败状态 137 | * 2 argument error - 表示请求参数值有误, 或未携带必需的参数值 138 | * 3 帐号已存在 139 | * 4 注册验证码错误 140 | * 500 error - 服务器错误 141 | * 404 not found - 请求的资源或接口不存在 142 | * 422 token error - 未传递token参数,或token值非法 143 | 144 | ### 请求参数校验 145 | 146 | 最多的还是非空检查, 这里重点说一下。因此,我写了一个工具。使用方法如下: 147 | 148 | ```java 149 | String name = getPara("name"); 150 | String lover = getPara("lover"); 151 | //使用此方式的前提是当前controller类要继承自BaseAPIController类 152 | if (!notNull(Require.me().put(name, "name参数不能为空").put(lover,"lover参数不能为空"))) { 153 | return; 154 | } 155 | 156 | //效果等同于如下代码: 157 | if (StringUtils.isEmpty(name)) { 158 | renderJson(new BaseResponse(2, "name参数不能为空")); 159 | return; 160 | } 161 | if (StringUtils.isEmpty(lover)) { 162 | renderJson(new BaseResponse(2, "lover参数不能为空")); 163 | return; 164 | } 165 | 166 | ``` 167 | ```javascript 168 | //如果没有传递name参数,将会得到如下响应: 169 | { 170 | "code": 2, 171 | "message": "name参数不能为空" 172 | } 173 | ``` 174 | 175 | ### 已实现的公共模块 176 | 177 | 公共模块实现了基本功能,你可以根据自己的业务需求自由调整数据字段。 178 | 179 | #### Token模块 180 | 181 | token, 顾名思义, 表示令牌,用于标识当前用户,同时增加接口的安全性。目前不支持过期策略,也仅支持一个用户一个终端的方式,即用户在一处登录后,再在另一处登录会使之前登录的token失效。 182 | 183 | 要启用token功能只需要配置TokenInterceptor拦截器类即可。 184 | 185 | 在使用时,客户端必须在配置了拦截器的接口请求中携带名为"token"的请求参数。 186 | 187 | 服务端在继承了BaseAPIController类后可以直接调用`getUser();`函数获取当前用户对象. **注意: ** 为了正常地使用getUser函数,必须在登录接口中查出用户对象后,使用类似如下代码建立token与用户对象的映射: 188 | ```java 189 | User nowUser = User.user.findFirst(sql, loginName, StringUtils.encodePassword(password, "md5")); 190 | 191 | //之后要将token值响应给客户端 192 | String token = TokenManager.getMe().generateToken(nowUser)); 193 | ``` 194 | 195 | #### 文件上传模块 196 | 197 | 在以往的接口开发过程中,我们都是使用一个统一的文件上传接口上传文件后,服务器响应上传成功后的文件地址,客户端再使用业务接口将文件地址作为参数值发送到服务器。这样做的好处之一是便于服务端将文件统一管理,比如做缓存或CDN;另一方面是为了减小耦合度,比如此时要换成七牛CDN存放静态文件,客户端只需要改写文件上传部分的代码即可。 198 | 199 | 目前的文件上传接口已实现一或多个文件同时上传,客户端在上传时,必须要为每个文件指定一个请求参数名, 参数名用于在上传结束后,根据服务器的响应数据判断哪些文件是上传失败的,哪些是上传成功的,以及成功后的文件地址是什么。 200 | 201 | 服务器响应实例如下: 202 | ```javascript 203 | //全部上传成功 204 | { 205 | "code": 1, 206 | "message": "success", 207 | "datum": { 208 | "file1": "/upload/images/file1.jpg", 209 | "file2": "/upload/images/file2.jpg" 210 | } 211 | } 212 | //全部上传失败 213 | { 214 | "code": 0, 215 | "message": "failed", 216 | "failed": ["file1", "file2"] 217 | } 218 | //部分成功,部分失败 219 | { 220 | "code": 0, //只要有一个文件上传失败,code就会是0 221 | "failed": ["file3"] 222 | "datum": { 223 | "file1": "/upload/images/file1.jpg", 224 | "file2": "/upload/images/file2.jpg" 225 | } 226 | } 227 | ``` 228 | 229 | #### 版本更新和意见反馈 230 | 231 | 关于版本更新的说明,请查看第一章节中的项目配置,以及API文档; 232 | 233 | 意见反馈模块比较简单,你可以根据你的业务需求改动数据库字段,以及接口参数。 234 | 235 | #### 用户账号模块 236 | 237 | 这个模块目前实现的接口有: 238 | 239 | * 检查账号是否已注册 240 | * 发送手机验证码 241 | * 注册 242 | * 登录 243 | * 查询用户资料 244 | * 修改用户资料 245 | * 修改密码 246 | 247 | 如果使用手机验证码功能,你需要改写SMSUtils类的sendCode函数以实现短信发送功能;如果不使用手机验证码功能,可以在注册的接口代码中将验证码检查的功能去掉。在开发调试的过程中,可以访问code.html查询手机验证码。 248 | 249 | 如果用户表中的字段不能满足你的业务需求,你可以自由增删修改,但同时也需要修改注册和修改用户资料接口。 250 | 251 | #### 工具 252 | 253 | * Jetty插件: 无需使用tomcat,直接使用maven的jetty插件启动项目; 254 | * ant工具: 一般情况下,我们的项目是在服务端使用maven自动构建的,但在开发过程中,代码经常改变需要重新部署,如果重新打包更新又比较麻烦,因此在服务端使用maven命令重新构建后,可直接执行ant命令将已改动的文件copy到tomcat应用目录。所以,若想正常使用该工具,你需要修改build.xml,将tomapp值修改为你的tomcat应用路径。 255 | 256 | ### 资源 257 | 258 | [现有的API接口文档](doc/index.md) 259 | 260 | 261 | ## Copyright & License 262 | 263 | Copyright (c) 2015 Released under the [MIT license](LICENSE). 264 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.utils; 2 | 3 | import java.security.MessageDigest; 4 | 5 | /** 6 | * 字符串操作工具 7 | * @author malongbo 8 | */ 9 | public final class StringUtils { 10 | 11 | /** 12 | * 判断字符串是否为空 13 | * @param str 14 | * @return 15 | */ 16 | public static boolean isEmpty (String str) { 17 | if (str == null) 18 | return true; 19 | 20 | if ("".equals(str.trim())) 21 | return true; 22 | 23 | return false; 24 | } 25 | 26 | /** 27 | * 判断字符串是否不为空 28 | * @param str 29 | * @return 30 | */ 31 | public static boolean isNotEmpty (String str) { 32 | return !isEmpty(str); 33 | } 34 | 35 | /** 36 | * 判断字符串是否为空字符串 37 | * @param str 38 | * @return 39 | */ 40 | public static boolean isBlank (String str) { 41 | return "".equals(str.trim()); 42 | } 43 | 44 | /** 45 | * 判断字符串是否为非空字符串 46 | * @param str 47 | * @return 48 | */ 49 | public static boolean isNotBlank (String str) { 50 | return !isBlank(str); 51 | } 52 | 53 | 54 | /** 55 | * 将字符串转为boolean类型 56 | * @param value 57 | * @param defaultValue 设置默认值,默认使用false 58 | * @return 59 | */ 60 | public static Boolean toBoolean(String value, boolean defaultValue) { 61 | if (isEmpty(value)) 62 | return defaultValue; 63 | 64 | try { 65 | return Boolean.parseBoolean(value); 66 | } catch (Exception e) { 67 | return defaultValue; 68 | } 69 | } 70 | 71 | /** 72 | * 将字符串转为boolean类型 73 | * @param value 74 | * @return 75 | */ 76 | public static Boolean toBoolean(String value) { 77 | return toBoolean(value, false); 78 | } 79 | 80 | /** 81 | * 将字符串转为long类型 82 | * @param value 83 | * @param defaultValue 设置默认值 84 | * @return 85 | */ 86 | public static Long toLong(String value, Long defaultValue) { 87 | if (isEmpty(value)) 88 | return defaultValue; 89 | try { 90 | return Long.parseLong(value); 91 | }catch (Exception e) { 92 | return defaultValue; 93 | } 94 | } 95 | 96 | /** 97 | * 将字符串转为int类型 98 | * @param value 99 | * @param defaultValue 设置默认值 100 | * @return 101 | */ 102 | public static Integer toInt(String value, Integer defaultValue) { 103 | if (isEmpty(value)) 104 | return defaultValue; 105 | try { 106 | return Integer.parseInt(value); 107 | }catch (Exception e) { 108 | return defaultValue; 109 | } 110 | } 111 | 112 | /** 113 | * 将字符串转为double类型 114 | * @param value 115 | * @param defaultValue 116 | * @return 117 | */ 118 | public static Double toDouble(String value, Double defaultValue) { 119 | if (isEmpty(value)) 120 | return defaultValue; 121 | try { 122 | return Double.parseDouble(value); 123 | }catch (Exception e) { 124 | return defaultValue; 125 | } 126 | } 127 | 128 | /** 129 | * 将字符串转为float类型 130 | * @param value 131 | * @param defaultValue 132 | * @return 133 | */ 134 | public static Float toFloat(String value, Float defaultValue) { 135 | if (isEmpty(value)) 136 | return defaultValue; 137 | try { 138 | return Float.parseFloat(value); 139 | }catch (Exception e) { 140 | return defaultValue; 141 | } 142 | } 143 | /** 144 | * 将数组值按英文逗号拼接成字符串 145 | * @param array 146 | * @return 147 | */ 148 | public static String join(Object[] array) { 149 | return join(array, ",",""); 150 | } 151 | 152 | /** 153 | * 将数组值按指定分隔符拼接成字符串 154 | * @param array 155 | * @param delimiter 分割符,默认使用英文逗号 156 | * @return 157 | */ 158 | public static String join(Object[] array, String delimiter) { 159 | 160 | return join(array, delimiter,""); 161 | } 162 | 163 | /** 164 | * 将数组值按指定分隔符拼接成字符串 165 | *

166 | * 示例

167 | * array等于new String[]{"a","b"}

168 | * delimiter等于,

169 | * surround等于'

170 | * 转换结果为:'a','b' 171 | * @param array 172 | * @param delimiter 分割符,默认使用英文逗号 173 | * @param surround 每个值左右符号,默认无 174 | * @return 175 | */ 176 | public static String join(Object[] array, String delimiter, String surround) { 177 | if (array == null) 178 | throw new IllegalArgumentException("Array can not be null"); 179 | 180 | if (array.length == 0) return ""; 181 | 182 | if (surround == null) surround = ""; 183 | 184 | if (delimiter == null) surround = ","; 185 | 186 | StringBuffer buffer = new StringBuffer(); 187 | 188 | for (Object item : array) { 189 | buffer.append(surround).append(item.toString()).append(surround).append(delimiter); 190 | } 191 | 192 | buffer.delete(buffer.length() - delimiter.length(), buffer.length()); 193 | 194 | return buffer.toString(); 195 | } 196 | 197 | /** 198 | * Encode a string using algorithm specified in web.xml and return the 199 | * resulting encrypted password. If exception, the plain credentials 200 | * string is returned 201 | * 202 | * @param password Password or other credentials to use in authenticating 203 | * this username 204 | * @param algorithm Algorithm used to do the digest 205 | * 206 | * @return encypted password based on the algorithm. 207 | */ 208 | public static String encodePassword(String password, String algorithm) { 209 | byte[] unencodedPassword = password.getBytes(); 210 | 211 | MessageDigest md = null; 212 | 213 | try { 214 | // first create an instance, given the provider 215 | md = MessageDigest.getInstance(algorithm); 216 | } catch (Exception e) { 217 | return password; 218 | } 219 | 220 | md.reset(); 221 | 222 | // call the update method one or more times 223 | // (useful when you don't know the size of your data, eg. stream) 224 | md.update(unencodedPassword); 225 | 226 | // now calculate the hash 227 | byte[] encodedPassword = md.digest(); 228 | 229 | StringBuffer buf = new StringBuffer(); 230 | 231 | for (int i = 0; i < encodedPassword.length; i++) { 232 | if ((encodedPassword[i] & 0xff) < 0x10) { 233 | buf.append("0"); 234 | } 235 | 236 | buf.append(Long.toString(encodedPassword[i] & 0xff, 16)); 237 | } 238 | 239 | return buf.toString(); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.utils; 2 | 3 | import com.mlongbo.jfinal.config.AppProperty; 4 | import com.mlongbo.jfinal.config.Context; 5 | 6 | import javax.imageio.ImageIO; 7 | import java.awt.*; 8 | import java.awt.image.BufferedImage; 9 | import java.io.*; 10 | import java.text.SimpleDateFormat; 11 | import java.util.Date; 12 | 13 | /** 14 | * @author malongbo 15 | */ 16 | public final class FileUtils { 17 | 18 | /** 19 | * 获取文件扩展名* 20 | * @param fileName 文件名 21 | * @return 扩展名 22 | */ 23 | public static String getExtension(String fileName) { 24 | int i = fileName.lastIndexOf("."); 25 | if (i < 0) return null; 26 | 27 | return fileName.substring(i+1); 28 | } 29 | 30 | /** 31 | * 获取文件扩展名* 32 | * @param file 文件对象 33 | * @return 扩展名 34 | */ 35 | public static String getExtension(File file) { 36 | if (file == null) return null; 37 | 38 | if (file.isDirectory()) return null; 39 | 40 | String fileName = file.getName(); 41 | return getExtension(fileName); 42 | } 43 | 44 | /** 45 | * 读取文件* 46 | * @param filePath 文件路径 47 | * @return 文件对象 48 | */ 49 | public static File readFile(String filePath) { 50 | File file = new File(filePath); 51 | if (file.isDirectory()) return null; 52 | 53 | if (!file.exists()) return null; 54 | 55 | return file; 56 | } 57 | /** 58 | * 复制文件 59 | * @param oldFilePath 源文件路径 60 | * @param newFilePath 目标文件毒经 61 | * @return 是否成功 62 | */ 63 | public static boolean copyFile(String oldFilePath,String newFilePath) { 64 | try { 65 | int byteRead = 0; 66 | File oldFile = new File(oldFilePath); 67 | if (oldFile.exists()) { // 文件存在时 68 | InputStream inStream = new FileInputStream(oldFilePath); // 读入原文件 69 | FileOutputStream fs = new FileOutputStream(newFilePath); 70 | byte[] buffer = new byte[1444]; 71 | while ((byteRead = inStream.read(buffer)) != -1) { 72 | fs.write(buffer, 0, byteRead); 73 | } 74 | inStream.close(); 75 | fs.close(); 76 | return true; 77 | } 78 | return false; 79 | } catch (Exception e) { 80 | System.out.println("复制单个文件操作出错 "); 81 | e.printStackTrace(); 82 | return false; 83 | } 84 | } 85 | 86 | /** 87 | *删除文件 88 | * @param filePath 文件地址 89 | * @return 是否成功 90 | */ 91 | public static boolean delFile(String filePath) { 92 | return delFile(new File(filePath)); 93 | } 94 | 95 | /** 96 | * 删除文件 97 | * @param file 文件对象 98 | * @return 是否成功 99 | */ 100 | public static boolean delFile(File file) { 101 | if (file.exists()) { 102 | return file.delete(); 103 | } 104 | return false; 105 | } 106 | 107 | /** 108 | * png图片转jpg* 109 | * @param pngImage png图片对象 110 | * @param jpegFile jpg图片对象 111 | * @return 转换是否成功 112 | */ 113 | public static boolean png2jpeg(File pngImage, File jpegFile) { 114 | BufferedImage bufferedImage; 115 | 116 | try { 117 | bufferedImage = ImageIO.read(pngImage); 118 | 119 | BufferedImage newBufferedImage = new BufferedImage(bufferedImage.getWidth(), 120 | bufferedImage.getHeight(), BufferedImage.TYPE_INT_RGB); 121 | 122 | newBufferedImage.createGraphics().drawImage(bufferedImage, 0, 0, Color.WHITE, null); 123 | 124 | ImageIO.write(bufferedImage, "jpg", jpegFile); 125 | 126 | return true; 127 | } catch (IOException e) { 128 | e.printStackTrace(); 129 | return false; 130 | } 131 | } 132 | 133 | /** 134 | * 判断文件是否是图片* 135 | * @param imgFile 文件对象 136 | * @return 137 | */ 138 | public static boolean isImage(File imgFile) { 139 | try { 140 | BufferedImage image = ImageIO.read(imgFile); 141 | return image != null; 142 | } catch (IOException e) { 143 | e.printStackTrace(); 144 | return false; 145 | } 146 | } 147 | 148 | /** 149 | * 判断文件是否是视频* 150 | * @param videoFile 文件对象 151 | * @return 152 | */ 153 | public static boolean isVideo(File videoFile){ 154 | try { 155 | 156 | FileType type = getType(videoFile); 157 | 158 | return type == FileType.AVI || 159 | type == FileType.RAM || 160 | type == FileType.RM || 161 | type == FileType.MOV || 162 | type == FileType.ASF || 163 | type == FileType.MPG; 164 | } catch (Exception e) { 165 | e.printStackTrace(); 166 | } 167 | return false; 168 | } 169 | 170 | /** 171 | * 根据系统当前时间,返回时间层次的文件夹结果,如:upload/2015/01/18/0.jpg 172 | * @return 173 | */ 174 | public static String getTimeFilePath(){ 175 | return new SimpleDateFormat("/yyyy/MM/dd").format(new Date())+"/"; 176 | } 177 | 178 | /** 179 | * 将文件头转换成16进制字符串 180 | * 181 | * @param src 原生byte 182 | * @return 16进制字符串 183 | */ 184 | private static String bytesToHexString(byte[] src){ 185 | 186 | StringBuilder stringBuilder = new StringBuilder(); 187 | if (src == null || src.length <= 0) { 188 | return null; 189 | } 190 | for (int i = 0; i < src.length; i++) { 191 | int v = src[i] & 0xFF; 192 | String hv = Integer.toHexString(v); 193 | if (hv.length() < 2) { 194 | stringBuilder.append(0); 195 | } 196 | stringBuilder.append(hv); 197 | } 198 | return stringBuilder.toString(); 199 | } 200 | 201 | /** 202 | * 得到文件头 203 | * 204 | * @param file 文件 205 | * @return 文件头 206 | * @throws IOException 207 | */ 208 | private static String getFileContent(File file) throws IOException { 209 | 210 | byte[] b = new byte[28]; 211 | 212 | InputStream inputStream = null; 213 | 214 | try { 215 | inputStream = new FileInputStream(file); 216 | inputStream.read(b, 0, 28); 217 | } catch (IOException e) { 218 | e.printStackTrace(); 219 | throw e; 220 | } finally { 221 | if (inputStream != null) { 222 | try { 223 | inputStream.close(); 224 | } catch (IOException e) { 225 | e.printStackTrace(); 226 | throw e; 227 | } 228 | } 229 | } 230 | return bytesToHexString(b); 231 | } 232 | 233 | 234 | /** 235 | * 判断文件类型 236 | * 237 | * @param file 文件 238 | * @return 文件类型 239 | */ 240 | public static FileType getType(File file) throws IOException { 241 | 242 | String fileHead = getFileContent(file); 243 | 244 | if (fileHead == null || fileHead.length() == 0) { 245 | return null; 246 | } 247 | 248 | fileHead = fileHead.toUpperCase(); 249 | 250 | FileType[] fileTypes = FileType.values(); 251 | 252 | for (FileType type : fileTypes) { 253 | if (fileHead.startsWith(type.getValue())) { 254 | return type; 255 | } 256 | } 257 | 258 | return null; 259 | } 260 | 261 | /** 262 | * 保存上传的文件* 263 | * @param file 文件对象 264 | * @return 文件相对路径, 供请求使用 265 | */ 266 | public static final String saveUploadFile(File file) { 267 | String saveFilePath = ""; 268 | //表示存放在tomcat应用目录中 269 | if (AppProperty.me().appPath() == 1) { 270 | saveFilePath = Context.me().getRequest().getSession().getServletContext().getRealPath("/"); 271 | } 272 | 273 | saveFilePath += AppProperty.me().uploadRooPath(); 274 | 275 | String timeFilePath = FileUtils.getTimeFilePath(); 276 | String urlPath = ""; 277 | if(FileUtils.isImage(file)){//保存图片 278 | urlPath = AppProperty.me().imagePath() + timeFilePath; 279 | saveFilePath += urlPath; 280 | }else if(FileUtils.isVideo(file)){//保存视频 281 | urlPath = AppProperty.me().videoPath() + timeFilePath; 282 | saveFilePath += urlPath; 283 | }else{//其他文件(如果是) 284 | urlPath = AppProperty.me().otherPath() + timeFilePath; 285 | saveFilePath += urlPath; 286 | } 287 | File saveFileDir = new File(saveFilePath); 288 | if (!saveFileDir.exists()) { 289 | saveFileDir.mkdirs(); 290 | } 291 | 292 | 293 | 294 | //保存 文件 295 | if (FileUtils.copyFile(file.getAbsolutePath(), saveFilePath + file.getName())) { 296 | //删掉临时文件 297 | file.delete(); 298 | return urlPath; 299 | } else { 300 | return null; 301 | } 302 | } 303 | } 304 | 305 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/api/AccountAPIController.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.api; 2 | 3 | import com.jfinal.aop.Before; 4 | import com.jfinal.aop.ClearInterceptor; 5 | import com.jfinal.log.Logger; 6 | import com.jfinal.plugin.activerecord.Db; 7 | import com.mlongbo.jfinal.common.bean.*; 8 | import com.mlongbo.jfinal.common.utils.SMSUtils; 9 | import com.mlongbo.jfinal.common.Require; 10 | import com.mlongbo.jfinal.common.token.TokenManager; 11 | import com.mlongbo.jfinal.common.utils.DateUtils; 12 | import com.mlongbo.jfinal.common.utils.RandomUtils; 13 | import com.mlongbo.jfinal.common.utils.StringUtils; 14 | import com.mlongbo.jfinal.config.AppProperty; 15 | import com.mlongbo.jfinal.interceptor.TokenInterceptor; 16 | import com.mlongbo.jfinal.model.RegisterCode; 17 | import com.mlongbo.jfinal.model.User; 18 | 19 | import static com.mlongbo.jfinal.model.User.*; 20 | 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | 24 | /** 25 | * 用户账号相关的接口* 26 | * 27 | * 检查账号是否被注册: GET /api/account/checkUser 28 | * 发送注册验证码: POST /api/account/sendCode 29 | * 注册: POST /api/account/register 30 | * 登录: POST /api/account/login 31 | * 查询用户资料: GET /api/account/profile 32 | * 修改用户资料: PUT /api/account/profile 33 | * 修改密码: PUT /api/account/password 34 | * 修改头像: PUT /api/account/avatar 35 | * 36 | * @author malongbo 37 | */ 38 | @Before(TokenInterceptor.class) 39 | public class AccountAPIController extends BaseAPIController { 40 | private static Logger log = Logger.getLogger(AccountAPIController.class); 41 | 42 | /** 43 | * 检查用户账号是否被注册* 44 | */ 45 | @ClearInterceptor 46 | public void checkUser() { 47 | String loginName = getPara("loginName"); 48 | if (StringUtils.isEmpty(loginName)) { 49 | renderArgumentError("loginName can not be null"); 50 | return; 51 | } 52 | //检查手机号码是否被注册 53 | boolean exists = Db.findFirst("SELECT * FROM t_user WHERE loginName=?", loginName) != null; 54 | renderJson(new BaseResponse(exists ? Code.SUCCESS:Code.FAIL, exists ? "registered" : "unregistered")); 55 | } 56 | 57 | /** 58 | * 1. 检查是否被注册* 59 | * 2. 发送短信验证码* 60 | */ 61 | @ClearInterceptor 62 | public void sendCode() { 63 | String loginName = getPara("loginName"); 64 | if (StringUtils.isEmpty(loginName)) { 65 | renderArgumentError("loginName can not be null"); 66 | return; 67 | } 68 | 69 | //检查手机号码有效性 70 | if (!SMSUtils.isMobileNo(loginName)) { 71 | renderArgumentError("mobile number is invalid"); 72 | return; 73 | } 74 | 75 | //检查手机号码是否被注册 76 | if (Db.findFirst("SELECT * FROM t_user WHERE loginName=?", loginName) != null) { 77 | renderJson(new BaseResponse(Code.ACCOUNT_EXISTS,"mobile already registered")); 78 | return; 79 | } 80 | 81 | String smsCode = SMSUtils.randomSMSCode(4); 82 | //发送短信验证码 83 | if (!SMSUtils.sendCode(loginName, smsCode)) { 84 | renderFailed("sms send failed"); 85 | return; 86 | } 87 | 88 | //保存验证码数据 89 | RegisterCode registerCode = new RegisterCode() 90 | .set(RegisterCode.MOBILE, loginName) 91 | .set(RegisterCode.CODE, smsCode); 92 | 93 | //保存数据 94 | if (Db.findFirst("SELECT * FROM t_register_code WHERE mobile=?", loginName) == null) { 95 | registerCode.save(); 96 | } else { 97 | registerCode.update(); 98 | } 99 | 100 | renderJson(new BaseResponse("sms sended")); 101 | 102 | } 103 | 104 | /** 105 | * 用户注册 106 | */ 107 | @ClearInterceptor() 108 | public void register(){ 109 | //必填信息 110 | String loginName = getPara("loginName");//登录帐号 111 | int code = getParaToInt("code", 0);//手机验证码 112 | int sex = getParaToInt("sex", 0);//性别 113 | String password = getPara("password");//密码 114 | String nickName = getPara("nickName");//昵称 115 | //头像信息,为空则使用默认头像地址 116 | String avatar = getPara("avatar", AppProperty.me().defaultUserAvatar()); 117 | 118 | //校验必填项参数 119 | if(!notNull(Require.me() 120 | .put(loginName, "loginName can not be null") 121 | .put(code, "code can not be null")//根据业务需求决定是否使用此字段 122 | .put(password, "password can not be null") 123 | .put(nickName, "nickName can not be null"))){ 124 | return; 125 | } 126 | 127 | //检查账户是否已被注册 128 | if (Db.findFirst("SELECT * FROM t_user WHERE loginName=?", loginName) != null) { 129 | renderJson(new BaseResponse(Code.ACCOUNT_EXISTS, "mobile already registered")); 130 | return; 131 | } 132 | 133 | //检查验证码是否有效, 如果业务不需要,则无需保存此段代码 134 | if (Db.findFirst("SELECT * FROM t_register_code WHERE mobile=? AND code = ?", loginName, code) == null) { 135 | renderJson(new BaseResponse(Code.CODE_ERROR,"code is invalid")); 136 | return; 137 | } 138 | 139 | //保存用户数据 140 | String userId = RandomUtils.randomCustomUUID(); 141 | 142 | new User() 143 | .set("userId", userId) 144 | .set(User.LOGIN_NAME, loginName) 145 | .set(User.PASSWORD, StringUtils.encodePassword(password, "md5")) 146 | .set(User.NICK_NAME, nickName) 147 | .set(User.CREATION_DATE, DateUtils.getNowTimeStamp()) 148 | .set(User.SEX, sex) 149 | .set(User.AVATAR, avatar) 150 | .save(); 151 | 152 | //删除验证码记录 153 | Db.update("DELETE FROM t_register_code WHERE mobile=? AND code = ?", loginName, code); 154 | 155 | //返回数据 156 | renderJson(new BaseResponse("success")); 157 | } 158 | 159 | 160 | /** 161 | * 登录接口 162 | */ 163 | @ClearInterceptor() 164 | public void login() { 165 | String loginName = getPara("loginName"); 166 | String password = getPara("password"); 167 | //校验参数, 确保不能为空 168 | if (!notNull(Require.me() 169 | .put(loginName, "loginName can not be null") 170 | .put(password, "password can not be null") 171 | )) { 172 | return; 173 | } 174 | String sql = "SELECT * FROM t_user WHERE loginName=? AND password=?"; 175 | User nowUser = User.user.findFirst(sql, loginName, StringUtils.encodePassword(password, "md5")); 176 | LoginResponse response = new LoginResponse(); 177 | if (nowUser == null) { 178 | response.setCode(Code.FAIL).setMessage("userName or password is error"); 179 | renderJson(response); 180 | return; 181 | } 182 | Map userInfo = new HashMap(nowUser.getAttrs()); 183 | userInfo.remove(PASSWORD); 184 | response.setInfo(userInfo); 185 | response.setMessage("login success"); 186 | response.setToken(TokenManager.getMe().generateToken(nowUser)); 187 | response.setConstant(Constant.me()); 188 | renderJson(response); 189 | } 190 | 191 | /** 192 | * 资料相关的接口 193 | */ 194 | public void profile() { 195 | String method = getRequest().getMethod(); 196 | if ("get".equalsIgnoreCase(method)) { //查询资料 197 | getProfile(); 198 | } else if ("put".equalsIgnoreCase(method)) { //修改资料 199 | updateProfile(); 200 | } else { 201 | render404(); 202 | } 203 | } 204 | 205 | 206 | /** 207 | * 查询用户资料 208 | */ 209 | private void getProfile() { 210 | String userId = getPara("userId"); 211 | User resultUser = null; 212 | if (StringUtils.isNotEmpty(userId)) { 213 | resultUser = User.user.findById(userId); 214 | } else { 215 | resultUser = getUser(); 216 | } 217 | 218 | DatumResponse response = new DatumResponse(); 219 | 220 | if (resultUser == null) { 221 | response.setCode(Code.FAIL).setMessage("user is not found"); 222 | } else { 223 | HashMap map = new HashMap(resultUser.getAttrs()); 224 | map.remove(PASSWORD); 225 | response.setDatum(map); 226 | } 227 | 228 | renderJson(response); 229 | } 230 | 231 | /** 232 | * 修改用户资料 233 | */ 234 | private void updateProfile() { 235 | boolean flag = false; 236 | BaseResponse response = new BaseResponse(); 237 | User user = getUser(); 238 | String nickName = getPara("nickName"); 239 | if (StringUtils.isNotEmpty(nickName)) { 240 | user.set(NICK_NAME, nickName); 241 | flag = true; 242 | } 243 | 244 | String email = getPara("email"); 245 | if (StringUtils.isNotEmpty(email)) { 246 | user.set(EMAIL, email); 247 | flag = true; 248 | } 249 | 250 | String avatar = getPara("avatar"); 251 | if (StringUtils.isNotEmpty(avatar)) { 252 | user.set(AVATAR, avatar); 253 | flag = true; 254 | } 255 | 256 | //修改性别 257 | Integer sex = getParaToInt("sex", null); 258 | if (null != sex) { 259 | if (!User.checkSex(sex)) { 260 | renderArgumentError("sex is invalid"); 261 | return; 262 | } 263 | user.set(SEX, sex); 264 | flag = true; 265 | } 266 | 267 | if (flag) { 268 | boolean update = user.update(); 269 | renderJson(response.setCode(update ? Code.SUCCESS : Code.FAIL).setMessage(update ? "update success" : "update failed")); 270 | } else { 271 | renderArgumentError("must set profile"); 272 | } 273 | } 274 | 275 | /** 276 | * 修改密码 277 | */ 278 | public void password(){ 279 | if (!"put".equalsIgnoreCase(getRequest().getMethod())) { 280 | render404(); 281 | return; 282 | } 283 | //根据用户id,查出这个用户的密码,再跟传递的旧密码对比,一样就更新,否则提示旧密码错误 284 | String oldPwd = getPara("oldPwd"); 285 | String newPwd = getPara("newPwd"); 286 | if(!notNull(Require.me() 287 | .put(oldPwd, "old password can not be null") 288 | .put(newPwd, "new password can not be null"))){ 289 | return; 290 | } 291 | //用户真实的密码 292 | User nowUser = getUser(); 293 | if(StringUtils.encodePassword(oldPwd, "md5").equalsIgnoreCase(nowUser.getStr(PASSWORD))){ 294 | boolean flag = nowUser.set(User.PASSWORD, StringUtils.encodePassword(newPwd, "md5")).update(); 295 | renderJson(new BaseResponse(flag?Code.SUCCESS:Code.FAIL, flag?"success":"failed")); 296 | }else{ 297 | renderJson(new BaseResponse(Code.FAIL, "oldPwd is invalid")); 298 | } 299 | } 300 | 301 | /** 302 | * 修改头像接口 303 | * /api/account/avatar 304 | */ 305 | public void avatar() { 306 | if (!"put".equalsIgnoreCase(getRequest().getMethod())) { 307 | renderJson(new BaseResponse(Code.NOT_FOUND)); 308 | return; 309 | } 310 | String avatar=getPara("avatar"); 311 | if(!notNull(Require.me() 312 | .put(avatar, "avatar url can not be null"))){ 313 | return; 314 | } 315 | getUser().set(User.AVATAR, avatar).update(); 316 | renderSuccess("success"); 317 | } 318 | } 319 | 320 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/httpclient/HttpRequester.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common.httpclient; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.net.HttpURLConnection; 8 | import java.net.URL; 9 | import java.nio.charset.Charset; 10 | import java.util.Map; 11 | import java.util.Vector; 12 | 13 | /** 14 | * HTTP请求对象 15 | *

16 | * 默认使用utf-8编码 17 | * 18 | * @author malongbo 19 | */ 20 | public class HttpRequester { 21 | private String defaultContentEncoding; 22 | private static final String METHOD_GET = "GET"; 23 | private static final String METHOD_POST = "POST"; 24 | private static final String METHOD_DELETE = "DELETE"; 25 | private static final String METHOD_PUT = "PUT"; 26 | 27 | public HttpRequester() { 28 | this.defaultContentEncoding = Charset.defaultCharset().name(); 29 | } 30 | 31 | /** 32 | * 发送GET请求 33 | * 34 | * @param urlString 35 | * URL地址 36 | * @return 响应对象 37 | * @throws java.io.IOException 38 | */ 39 | public HttpResponse sendGet(String urlString) throws IOException { 40 | return this.send(urlString, METHOD_GET, null, null); 41 | } 42 | 43 | /** 44 | * 发送GET请求 45 | * 46 | * @param urlString 47 | * URL地址 48 | * @param params 49 | * 参数集合 50 | * @return 响应对象 51 | * @throws java.io.IOException 52 | */ 53 | public HttpResponse sendGet(String urlString, Map params) 54 | throws IOException { 55 | return this.send(urlString, METHOD_GET, params, null); 56 | } 57 | 58 | /** 59 | * 发送GET请求 60 | * 61 | * @param urlString 62 | * URL地址 63 | * @param params 64 | * 参数集合 65 | * @param propertys 66 | * 请求属性 67 | * @return 响应对象 68 | * @throws java.io.IOException 69 | */ 70 | public HttpResponse sendGet(String urlString, Map params, 71 | Map propertys) throws IOException { 72 | return this.send(urlString, METHOD_GET, params, propertys); 73 | } 74 | 75 | /** 76 | * 发送POST请求 77 | * 78 | * @param urlString 79 | * URL地址 80 | * @return 响应对象 81 | * @throws java.io.IOException 82 | */ 83 | public HttpResponse sendPost(String urlString) throws IOException { 84 | return this.send(urlString, METHOD_POST, null, null); 85 | } 86 | 87 | /** 88 | * 发送POST请求 89 | * 90 | * @param urlString 91 | * URL地址 92 | * @param params 93 | * 参数集合 94 | * @return 响应对象 95 | * @throws java.io.IOException 96 | */ 97 | public HttpResponse sendPost(String urlString, Map params) 98 | throws IOException { 99 | return this.send(urlString, METHOD_POST, params, null); 100 | } 101 | 102 | /** 103 | * 发送POST请求 104 | * 105 | * @param urlString 106 | * URL地址 107 | * @param params 108 | * 参数集合 109 | * @param propertys 110 | * 请求属性 111 | * @return 响应对象 112 | * @throws java.io.IOException 113 | */ 114 | public HttpResponse sendPost(String urlString, Map params, 115 | Map propertys) throws IOException { 116 | return this.send(urlString, METHOD_POST, params, propertys); 117 | } 118 | 119 | /** 120 | * 发送DELETE请求 121 | * 122 | * @param urlString 123 | * URL地址 124 | * @return 响应对象 125 | * @throws java.io.IOException 126 | */ 127 | public HttpResponse sendDelete(String urlString) throws IOException { 128 | return this.send(urlString, METHOD_DELETE, null, null); 129 | } 130 | 131 | /** 132 | * 发送DELETE请求 133 | * 134 | * @param urlString 135 | * URL地址 136 | * @param params 137 | * 参数集合 138 | * @return 响应对象 139 | * @throws java.io.IOException 140 | */ 141 | public HttpResponse sendDelete(String urlString, Map params) 142 | throws IOException { 143 | return this.send(urlString, METHOD_DELETE, params, null); 144 | } 145 | 146 | /** 147 | * 发送DELETE请求 148 | * 149 | * @param urlString 150 | * URL地址 151 | * @param params 152 | * 参数集合 153 | * @param propertys 154 | * 请求属性 155 | * @return 响应对象 156 | * @throws java.io.IOException 157 | */ 158 | public HttpResponse sendDelete(String urlString, Map params, 159 | Map propertys) throws IOException { 160 | return this.send(urlString, METHOD_DELETE, params, propertys); 161 | } 162 | 163 | /** 164 | * 发送PUT请求 165 | * 166 | * @param urlString 167 | * URL地址 168 | * @return 响应对象 169 | * @throws java.io.IOException 170 | */ 171 | public HttpResponse sendPut(String urlString) throws IOException { 172 | return this.send(urlString, METHOD_PUT, null, null); 173 | } 174 | 175 | /** 176 | * 发送PUT请求 177 | * 178 | * @param urlString 179 | * URL地址 180 | * @param params 181 | * 参数集合 182 | * @return 响应对象 183 | * @throws java.io.IOException 184 | */ 185 | public HttpResponse sendPut(String urlString, Map params) 186 | throws IOException { 187 | return this.send(urlString, METHOD_PUT, params, null); 188 | } 189 | 190 | /** 191 | * 发送PUT请求 192 | * 193 | * @param urlString 194 | * URL地址 195 | * @param params 196 | * 参数集合 197 | * @param propertys 198 | * 请求属性 199 | * @return 响应对象 200 | * @throws java.io.IOException 201 | */ 202 | public HttpResponse sendPut(String urlString, Map params, 203 | Map propertys) throws IOException { 204 | return this.send(urlString, METHOD_PUT, params, propertys); 205 | } 206 | 207 | /** 208 | * 拼接一个参数多个值情况 209 | * @param key 210 | * @param values 211 | * @return 212 | */ 213 | private String concatParams(String key, Object[] values) { 214 | StringBuffer buffer = new StringBuffer(); 215 | int i = 0; 216 | for (Object value: values) { 217 | if (i != 0) 218 | buffer.append("&"); 219 | buffer.append(key).append("=").append(value); 220 | i++; 221 | } 222 | return buffer.toString(); 223 | } 224 | 225 | /** 226 | * 发送HTTP请求 227 | * 228 | * @param urlString 229 | * @return 响映对象 230 | * @throws java.io.IOException 231 | */ 232 | private HttpResponse send(String urlString, String method, 233 | Map parameters, Map propertys) 234 | throws IOException { 235 | HttpURLConnection urlConnection = null; 236 | 237 | /* 238 | GET、DELETE和PUT函数,使用此参数拼接形式 239 | */ 240 | if ((method.equalsIgnoreCase(METHOD_GET) || method.equalsIgnoreCase(METHOD_DELETE) 241 | || method.equalsIgnoreCase(METHOD_PUT)) && parameters != null) { 242 | StringBuffer param = new StringBuffer(); 243 | int i = 0; 244 | for (String key : parameters.keySet()) { 245 | if (i == 0) 246 | param.append("?"); 247 | else 248 | param.append("&"); 249 | 250 | Object value = parameters.get(key); 251 | if (value == null) continue; 252 | 253 | //如果是数据,说明需要拼接一个参数多个值 254 | if (value.getClass().isArray()) 255 | param.append(concatParams(key, (Object[]) value)); 256 | else 257 | param.append(key).append("=").append(value); 258 | i++; 259 | } 260 | urlString += param.toString(); 261 | } 262 | URL url = new URL(urlString); 263 | urlConnection = (HttpURLConnection) url.openConnection(); 264 | 265 | urlConnection.setRequestMethod(method); 266 | urlConnection.setDoOutput(true); 267 | urlConnection.setDoInput(true); 268 | urlConnection.setUseCaches(false); 269 | 270 | if (propertys != null) 271 | for (String key : propertys.keySet()) { 272 | urlConnection.addRequestProperty(key, propertys.get(key)); 273 | } 274 | /* 275 | POST情况方式使用此参数拼接 276 | */ 277 | if (method.equalsIgnoreCase(METHOD_POST) && parameters != null) { 278 | StringBuffer param = new StringBuffer(); 279 | for (String key : parameters.keySet()) { 280 | param.append("&"); 281 | Object value = parameters.get(key); 282 | if (value == null) continue; 283 | 284 | //如果是数据,说明需要拼接一个参数多个值 285 | if (value.getClass().isArray()) 286 | param.append(concatParams(key, (Object[]) value)); 287 | else 288 | param.append(key).append("=").append(value); 289 | } 290 | urlConnection.getOutputStream().write(param.toString().getBytes()); 291 | urlConnection.getOutputStream().flush(); 292 | urlConnection.getOutputStream().close(); 293 | } 294 | 295 | //处理响应 296 | return this.makeContent(urlString, urlConnection); 297 | } 298 | 299 | /** 300 | * 处理响应 301 | * 302 | * @param urlConnection 303 | * @return 响应对象 304 | * @throws java.io.IOException 305 | */ 306 | private HttpResponse makeContent(String urlString, 307 | HttpURLConnection urlConnection) throws IOException { 308 | HttpResponse httpResponser = new HttpResponse(); 309 | try { 310 | InputStream in = urlConnection.getInputStream(); 311 | BufferedReader bufferedReader = new BufferedReader( 312 | new InputStreamReader(in)); 313 | httpResponser.contentCollection = new Vector(); 314 | StringBuffer temp = new StringBuffer(); 315 | String line = bufferedReader.readLine(); 316 | while (line != null) { 317 | httpResponser.contentCollection.add(line); 318 | temp.append(line).append("\r\n"); 319 | line = bufferedReader.readLine(); 320 | } 321 | bufferedReader.close(); 322 | 323 | String ecod = urlConnection.getContentEncoding(); 324 | if (ecod == null) 325 | ecod = this.defaultContentEncoding; 326 | 327 | httpResponser.urlString = urlString; 328 | 329 | httpResponser.defaultPort = urlConnection.getURL().getDefaultPort(); 330 | httpResponser.file = urlConnection.getURL().getFile(); 331 | httpResponser.host = urlConnection.getURL().getHost(); 332 | httpResponser.path = urlConnection.getURL().getPath(); 333 | httpResponser.port = urlConnection.getURL().getPort(); 334 | httpResponser.protocol = urlConnection.getURL().getProtocol(); 335 | httpResponser.query = urlConnection.getURL().getQuery(); 336 | httpResponser.ref = urlConnection.getURL().getRef(); 337 | httpResponser.userInfo = urlConnection.getURL().getUserInfo(); 338 | 339 | httpResponser.content = new String(temp.toString().getBytes(), ecod); 340 | httpResponser.contentEncoding = ecod; 341 | httpResponser.code = urlConnection.getResponseCode(); 342 | httpResponser.message = urlConnection.getResponseMessage(); 343 | httpResponser.contentType = urlConnection.getContentType(); 344 | httpResponser.method = urlConnection.getRequestMethod(); 345 | httpResponser.connectTimeout = urlConnection.getConnectTimeout(); 346 | httpResponser.readTimeout = urlConnection.getReadTimeout(); 347 | 348 | return httpResponser; 349 | } catch (IOException e) { 350 | throw e; 351 | } finally { 352 | if (urlConnection != null) 353 | urlConnection.disconnect(); 354 | } 355 | } 356 | 357 | /** 358 | * 默认的响应字符集 359 | */ 360 | public String getDefaultContentEncoding() { 361 | return this.defaultContentEncoding; 362 | } 363 | 364 | /** 365 | * 设置默认的响应字符集 366 | */ 367 | public void setDefaultContentEncoding(String defaultContentEncoding) { 368 | this.defaultContentEncoding = defaultContentEncoding; 369 | } 370 | } -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/version/VersionProperty.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.version; 2 | 3 | import org.dom4j.Attribute; 4 | import org.dom4j.Document; 5 | import org.dom4j.Element; 6 | import org.dom4j.io.OutputFormat; 7 | import org.dom4j.io.SAXReader; 8 | import org.dom4j.io.XMLWriter; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.*; 13 | import java.util.*; 14 | 15 | /** 16 | * @author malongbo 17 | */ 18 | public class VersionProperty { 19 | 20 | private static final Logger Log = LoggerFactory.getLogger(VersionProperty.class); 21 | private static long lastModified = 0L; 22 | private static final Object lock = new Object(); 23 | private File file; 24 | private Document document; 25 | 26 | private Map nowVersion = new HashMap(); 27 | 28 | /** 29 | * Creates a new XMLPropertiesTest object. 30 | * 31 | * @param fileName the full path the file that properties should be read from 32 | * and written to. 33 | * @throws java.io.IOException if an error occurs loading the properties. 34 | */ 35 | public VersionProperty(String fileName) throws IOException { 36 | this(new File(fileName)); 37 | } 38 | 39 | /* *//** 40 | * Loads XML properties from a stream. 41 | * 42 | * @param in the input stream of XML. 43 | * @throws java.io.IOException if an exception occurs when reading the stream. 44 | *//* 45 | public VersionProperty(InputStream in) throws IOException { 46 | if (in != null) { 47 | Reader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); 48 | buildDoc(reader); 49 | } 50 | }*/ 51 | 52 | /** 53 | * Creates a new XMLPropertiesTest object. 54 | * 55 | * @param file the file that properties should be read from and written to. 56 | * @throws java.io.IOException if an error occurs loading the properties. 57 | */ 58 | public VersionProperty(File file) throws IOException { 59 | this.file = file; 60 | if (!file.exists()) { 61 | // Attempt to recover from this error case by seeing if the 62 | // tmp file exists. It's possible that the rename of the 63 | // tmp file failed the last time Jive was running, 64 | // but that it exists now. 65 | File tempFile; 66 | tempFile = new File(file.getParentFile(), file.getName() + ".tmp"); 67 | if (tempFile.exists()) { 68 | Log.error("WARNING: " + file.getName() + " was not found, but temp file from " + 69 | "previous write operation was. Attempting automatic recovery." + 70 | " Please check file for data consistency."); 71 | tempFile.renameTo(file); 72 | } 73 | // There isn't a possible way to recover from the file not 74 | // being there, so throw an error. 75 | else { 76 | throw new FileNotFoundException("XML properties file does not exist: " 77 | + file.getName()); 78 | } 79 | } 80 | // Check read and write privs. 81 | if (!file.canRead()) { 82 | throw new IOException("XML properties file must be readable: " + file.getName()); 83 | } 84 | if (!file.canWrite()) { 85 | throw new IOException("XML properties file must be writable: " + file.getName()); 86 | } 87 | 88 | FileReader reader = new FileReader(file); 89 | lastModified = file.lastModified(); 90 | buildDoc(reader); 91 | } 92 | 93 | /** 94 | * Builds the document XML model up based the given reader of XML data. 95 | * @param in the input stream used to build the xml document 96 | * @throws java.io.IOException thrown when an error occurs reading the input stream. 97 | */ 98 | private void buildDoc(Reader in) throws IOException { 99 | try { 100 | SAXReader xmlReader = new SAXReader(); 101 | xmlReader.setEncoding("UTF-8"); 102 | document = xmlReader.read(in); 103 | buildNowVersion(); 104 | } 105 | catch (Exception e) { 106 | Log.error("Error reading XML properties", e); 107 | throw new IOException(e.getMessage()); 108 | } 109 | finally { 110 | if (in != null) { 111 | in.close(); 112 | } 113 | } 114 | } 115 | 116 | private void reCheck() { 117 | if (lastModified < file.lastModified()) { 118 | synchronized (lock) { 119 | if (lastModified < file.lastModified()) { 120 | lastModified = file.lastModified(); 121 | try { 122 | buildDoc(new FileReader(file)); 123 | } catch (IOException e) { 124 | e.printStackTrace(); 125 | } 126 | } 127 | } 128 | } 129 | } 130 | private void buildNowVersion() { 131 | nowVersion.put(ClientType.ANDROID.getType(),loadNowVersion(ClientType.ANDROID)); 132 | nowVersion.put(ClientType.IPHONE.getType(),loadNowVersion(ClientType.IPHONE)); 133 | } 134 | 135 | private Version loadNowVersion(ClientType type) { 136 | Element clientElement = null; 137 | List versions = null; 138 | String nowVersion = ""; 139 | switch (type) { 140 | case ANDROID: 141 | clientElement = getElement("client.android"); 142 | break; 143 | case IPHONE: 144 | clientElement = getElement("client.iphone"); 145 | break; 146 | default: 147 | return null; 148 | } 149 | nowVersion = clientElement.attributeValue("default"); 150 | versions = new ArrayList(); 151 | List elements = clientElement.elements(); 152 | 153 | if (elements == null) { 154 | return null; 155 | } 156 | 157 | Version versionTmp = null; 158 | for (Object ele : elements) { 159 | Element versionEle = (Element) ele; 160 | String version = versionEle.elementText("version"); 161 | if (version == null || !nowVersion.equalsIgnoreCase(version)) { 162 | continue; 163 | } 164 | 165 | String url = versionEle.elementText("url"); 166 | String message = versionEle.elementText("message"); 167 | 168 | versionTmp = new Version(); 169 | versionTmp.setMessage(message); 170 | versionTmp.setUrl(url); 171 | versionTmp.setVersion(version); 172 | 173 | return versionTmp; 174 | } 175 | return null; 176 | } 177 | 178 | /** 179 | * Returns an array representation of the given Jive property. Jive 180 | * properties are always in the format "prop.name.is.this" which would be 181 | * represented as an array of four Strings. 182 | * 183 | * @param name the name of the Jive property. 184 | * @return an array representation of the given Jive property. 185 | */ 186 | private String[] parsePropertyName(String name) { 187 | List propName = new ArrayList(5); 188 | // Use a StringTokenizer to tokenize the property name. 189 | StringTokenizer tokenizer = new StringTokenizer(name, "."); 190 | while (tokenizer.hasMoreTokens()) { 191 | propName.add(tokenizer.nextToken()); 192 | } 193 | return propName.toArray(new String[propName.size()]); 194 | } 195 | 196 | public void addVersion(Version version) { 197 | Element clientElement = null; 198 | ClientType type = version.getType(); 199 | switch (type) { 200 | case ANDROID: 201 | clientElement = getElement("client.android"); 202 | break; 203 | case IPHONE: 204 | clientElement = getElement("client.iphone"); 205 | break; 206 | default: 207 | return; 208 | } 209 | 210 | Element entry = clientElement.addElement("entry"); 211 | Element element; 212 | 213 | element = entry.addElement("version"); 214 | element.setText(version.getVersion()); 215 | 216 | element = entry.addElement("url"); 217 | element.setText(version.getUrl()); 218 | 219 | element = entry.addElement("message"); 220 | element.setText(version.getMessage()); 221 | 222 | saveProperties(); 223 | } 224 | 225 | public void deleteVersion(String version, ClientType type) { 226 | Element clientElement = null; 227 | switch (type) { 228 | case ANDROID: 229 | clientElement = getElement("client.android"); 230 | break; 231 | case IPHONE: 232 | clientElement = getElement("client.iphone"); 233 | break; 234 | default: 235 | return; 236 | } 237 | 238 | List elements = clientElement.elements(); 239 | 240 | if (elements == null) { 241 | return; 242 | } 243 | Element element = null; 244 | for (Object ele : elements) { 245 | Element versionEle = (Element) ele; 246 | String versionTmp = versionEle.elementText("version"); 247 | 248 | if (version.equalsIgnoreCase(versionTmp)) { 249 | element = versionEle; 250 | break; 251 | } 252 | } 253 | 254 | if (element != null) { 255 | elements.remove(element); 256 | saveProperties(); 257 | } 258 | } 259 | 260 | public void setVersion(ClientType type, String version) { 261 | Element clientElement = null; 262 | switch (type) { 263 | case ANDROID: 264 | clientElement = getElement("client.android"); 265 | break; 266 | case IPHONE: 267 | clientElement = getElement("client.iphone"); 268 | break; 269 | default: 270 | return; 271 | } 272 | 273 | Attribute attribute = clientElement.attribute("default"); 274 | 275 | if (attribute != null) { 276 | attribute.setValue(version); 277 | saveProperties(); 278 | } 279 | } 280 | 281 | public Version getNowVersion(ClientType type) { 282 | reCheck(); 283 | return nowVersion.get(type.getType()); 284 | } 285 | 286 | public List getVersions(ClientType type) { 287 | Element clientElement = null; 288 | List versions = null; 289 | switch (type) { 290 | case ANDROID: 291 | clientElement = getElement("client.android"); 292 | break; 293 | case IPHONE: 294 | clientElement = getElement("client.iphone"); 295 | break; 296 | default: 297 | return null; 298 | } 299 | 300 | versions = new ArrayList(); 301 | List elements = clientElement.elements(); 302 | 303 | if (elements == null) { 304 | return versions; 305 | } 306 | 307 | Version versionTmp = null; 308 | for (Object ele : elements) { 309 | Element versionEle = (Element) ele; 310 | String version = versionEle.elementText("version"); 311 | String url = versionEle.elementText("url"); 312 | String message = versionEle.elementText("message"); 313 | 314 | versionTmp = new Version(); 315 | versionTmp.setMessage(message); 316 | versionTmp.setUrl(url); 317 | versionTmp.setVersion(version); 318 | 319 | versions.add(versionTmp); 320 | } 321 | return versions; 322 | } 323 | 324 | private Element getElement(String name) { 325 | String[] propName = parsePropertyName(name); 326 | // Search for this property by traversing down the XML heirarchy. 327 | Element element = document.getRootElement(); 328 | for (int i = 0; i < propName.length; i++) { 329 | element = element.element(propName[i]); 330 | // Can't find the property so return. 331 | if (element == null) { 332 | break; 333 | } 334 | } 335 | return element; 336 | } 337 | 338 | /** 339 | * Saves the properties to disk as an XML document. A temporary file is 340 | * used during the writing process for maximum safety. 341 | */ 342 | private synchronized void saveProperties() { 343 | boolean error = false; 344 | // Write data out to a temporary file first. 345 | File tempFile = null; 346 | Writer writer = null; 347 | try { 348 | tempFile = new File(file.getParentFile(), file.getName() + ".tmp"); 349 | writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(tempFile), "UTF-8")); 350 | OutputFormat prettyPrinter = OutputFormat.createPrettyPrint(); 351 | XMLWriter xmlWriter = new XMLWriter(writer, prettyPrinter); 352 | xmlWriter.write(document); 353 | } 354 | catch (Exception e) { 355 | Log.error(e.getMessage(), e); 356 | // There were errors so abort replacing the old property file. 357 | error = true; 358 | } 359 | finally { 360 | if (writer != null) { 361 | try { 362 | writer.close(); 363 | } 364 | catch (IOException e1) { 365 | Log.error(e1.getMessage(), e1); 366 | error = true; 367 | } 368 | } 369 | } 370 | 371 | // No errors occured, so delete the main file. 372 | if (!error) { 373 | // Delete the old file so we can replace it. 374 | if (!file.delete()) { 375 | Log.error("Error deleting property file: " + file.getAbsolutePath()); 376 | return; 377 | } 378 | // Copy new contents to the file. 379 | try { 380 | copy(tempFile, file); 381 | } 382 | catch (Exception e) { 383 | Log.error(e.getMessage(), e); 384 | // There were errors so abort replacing the old property file. 385 | error = true; 386 | } 387 | // If no errors, delete the temp file. 388 | if (!error) { 389 | tempFile.delete(); 390 | } 391 | } 392 | } 393 | 394 | /** 395 | * Copies the inFile to the outFile. 396 | * 397 | * @param inFile The file to copy from 398 | * @param outFile The file to copy to 399 | * @throws java.io.IOException If there was a problem making the copy 400 | */ 401 | private static void copy(File inFile, File outFile) throws IOException { 402 | FileInputStream fin = null; 403 | FileOutputStream fout = null; 404 | try { 405 | fin = new FileInputStream(inFile); 406 | fout = new FileOutputStream(outFile); 407 | copy(fin, fout); 408 | } 409 | finally { 410 | try { 411 | if (fin != null) fin.close(); 412 | } 413 | catch (IOException e) { 414 | // do nothing 415 | } 416 | try { 417 | if (fout != null) fout.close(); 418 | } 419 | catch (IOException e) { 420 | // do nothing 421 | } 422 | } 423 | } 424 | 425 | /** 426 | * Copies data from an input stream to an output stream 427 | * 428 | * @param in the stream to copy data from. 429 | * @param out the stream to copy data to. 430 | * @throws java.io.IOException if there's trouble during the copy. 431 | */ 432 | private static void copy(InputStream in, OutputStream out) throws IOException { 433 | // Do not allow other threads to intrude on streams during copy. 434 | synchronized (in) { 435 | synchronized (out) { 436 | byte[] buffer = new byte[256]; 437 | while (true) { 438 | int bytesRead = in.read(buffer); 439 | if (bytesRead == -1) break; 440 | out.write(buffer, 0, bytesRead); 441 | } 442 | } 443 | } 444 | } 445 | 446 | public static void main(String[] args) { 447 | try { 448 | VersionProperty property = new VersionProperty("version.xml"); 449 | 450 | List androidVersions = property.getVersions(ClientType.ANDROID); 451 | System.out.println(androidVersions.size()); 452 | 453 | Version nowVersion = property.getNowVersion(ClientType.ANDROID); 454 | System.out.println(nowVersion.getVersion()); 455 | 456 | Version add = new Version(); 457 | add.setVersion("1.1.1.1"); 458 | add.setUrl("www.baidu.com"); 459 | add.setMessage("baidu"); 460 | add.setType(ClientType.ANDROID); 461 | property.addVersion(add); 462 | 463 | property.deleteVersion("1.1.1.1", ClientType.ANDROID); 464 | 465 | property.setVersion(ClientType.ANDROID,"0.1.0"); 466 | } catch (IOException e) { 467 | e.printStackTrace(); 468 | } 469 | } 470 | 471 | } 472 | -------------------------------------------------------------------------------- /src/main/java/com/mlongbo/jfinal/common/XmlProperty.java: -------------------------------------------------------------------------------- 1 | package com.mlongbo.jfinal.common; 2 | 3 | import org.dom4j.CDATA; 4 | import org.dom4j.Document; 5 | import org.dom4j.Element; 6 | import org.dom4j.Node; 7 | import org.dom4j.io.OutputFormat; 8 | import org.dom4j.io.SAXReader; 9 | import org.dom4j.io.XMLWriter; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.*; 14 | import java.util.*; 15 | 16 | /** 17 | * @author malongbo 18 | */ 19 | public class XmlProperty { 20 | 21 | private static final Logger Log = LoggerFactory.getLogger(XmlProperty.class); 22 | 23 | private static long lastModified = 0L; 24 | private static final Object lock = new Object(); 25 | private File file; 26 | private Document document; 27 | 28 | private Map propertyCache = new HashMap(); 29 | 30 | 31 | /** 32 | * Creates a new XMLPropertiesTest object. 33 | * 34 | * @param fileName the full path the file that properties should be read from 35 | * and written to. 36 | * @throws java.io.IOException if an error occurs loading the properties. 37 | */ 38 | public XmlProperty(String fileName) throws IOException { 39 | this(new File(fileName)); 40 | } 41 | 42 | /** 43 | * Creates a new XMLPropertiesTest object. 44 | * 45 | * @param file the file that properties should be read from and written to. 46 | * @throws java.io.IOException if an error occurs loading the properties. 47 | */ 48 | public XmlProperty(File file) throws IOException { 49 | this.file = file; 50 | if (!file.exists()) { 51 | // Attempt to recover from this error case by seeing if the 52 | // tmp file exists. It's possible that the rename of the 53 | // tmp file failed the last time Jive was running, 54 | // but that it exists now. 55 | File tempFile; 56 | tempFile = new File(file.getParentFile(), file.getName() + ".tmp"); 57 | if (tempFile.exists()) { 58 | Log.error("WARNING: " + file.getName() + " was not found, but temp file from " + 59 | "previous write operation was. Attempting automatic recovery." + 60 | " Please check file for data consistency."); 61 | tempFile.renameTo(file); 62 | } 63 | // There isn't a possible way to recover from the file not 64 | // being there, so throw an error. 65 | else { 66 | throw new FileNotFoundException("XML properties file does not exist: " 67 | + file.getName()); 68 | } 69 | } 70 | // Check read and write privs. 71 | if (!file.canRead()) { 72 | throw new IOException("XML properties file must be readable: " + file.getName()); 73 | } 74 | if (!file.canWrite()) { 75 | throw new IOException("XML properties file must be writable: " + file.getName()); 76 | } 77 | 78 | FileReader reader = new FileReader(file); 79 | lastModified = file.lastModified(); 80 | buildDoc(reader); 81 | } 82 | 83 | /** 84 | * Builds the document XML model up based the given reader of XML data. 85 | * @param in the input stream used to build the xml document 86 | * @throws java.io.IOException thrown when an error occurs reading the input stream. 87 | */ 88 | private void buildDoc(Reader in) throws IOException { 89 | try { 90 | SAXReader xmlReader = new SAXReader(); 91 | xmlReader.setEncoding("UTF-8"); 92 | document = xmlReader.read(in); 93 | propertyCache.clear(); 94 | } 95 | catch (Exception e) { 96 | Log.error("Error reading XML properties", e); 97 | throw new IOException(e.getMessage()); 98 | } 99 | finally { 100 | if (in != null) { 101 | in.close(); 102 | } 103 | } 104 | } 105 | 106 | /** 107 | * 检查文件是否有更新* 108 | */ 109 | private void reCheck() { 110 | if (lastModified < file.lastModified()) { 111 | synchronized (lock) { 112 | if (lastModified < file.lastModified()) { 113 | lastModified = file.lastModified(); 114 | try { 115 | buildDoc(new FileReader(file)); 116 | } catch (IOException e) { 117 | e.printStackTrace(); 118 | } 119 | } 120 | } 121 | } 122 | } 123 | 124 | /** 125 | * Returns the value of the specified property. 126 | * 127 | * @param name the name of the property to get. 128 | * @return the value of the specified property. 129 | */ 130 | public synchronized String getProperty(String name) { 131 | String value = propertyCache.get(name); 132 | if (value != null) { 133 | return value; 134 | } 135 | 136 | String[] propName = parsePropertyName(name); 137 | // Search for this property by traversing down the XML heirarchy. 138 | Element element = document.getRootElement(); 139 | for (String aPropName : propName) { 140 | element = element.element(aPropName); 141 | if (element == null) { 142 | // This node doesn't match this part of the property name which 143 | // indicates this property doesn't exist so return null. 144 | return null; 145 | } 146 | } 147 | // At this point, we found a matching property, so return its value. 148 | // Empty strings are returned as null. 149 | value = element.getTextTrim(); 150 | if ("".equals(value)) { 151 | return null; 152 | } 153 | else { 154 | // Add to cache so that getting property next time is fast. 155 | propertyCache.put(name, value); 156 | return value; 157 | } 158 | } 159 | /** 160 | * Return all values who's path matches the given property 161 | * name as a String array, or an empty array if the if there 162 | * are no children. This allows you to retrieve several values 163 | * with the same property name. For example, consider the 164 | * XML file entry: 165 | *

166 |      * <foo>
167 |      *     <bar>
168 |      *         <prop>some value</prop>
169 |      *         <prop>other value</prop>
170 |      *         <prop>last value</prop>
171 |      *     </bar>
172 |      * </foo>
173 |      * 
174 | * If you call getProperties("foo.bar.prop") will return a string array containing 175 | * {"some value", "other value", "last value"}. 176 | * 177 | * @param name the name of the property to retrieve 178 | * @return all child property values for the given node name. 179 | */ 180 | public String[] getProperties(String name) { 181 | String[] propName = parsePropertyName(name); 182 | // Search for this property by traversing down the XML heirarchy, 183 | // stopping one short. 184 | Element element = document.getRootElement(); 185 | for (int i = 0; i < propName.length - 1; i++) { 186 | element = element.element(propName[i]); 187 | if (element == null) { 188 | // This node doesn't match this part of the property name which 189 | // indicates this property doesn't exist so return empty array. 190 | return new String[]{}; 191 | } 192 | } 193 | // We found matching property, return names of children. 194 | Iterator iter = element.elementIterator(propName[propName.length - 1]); 195 | List props = new ArrayList(); 196 | String value; 197 | while (iter.hasNext()) { 198 | // Empty strings are skipped. 199 | value = ((Element)iter.next()).getTextTrim(); 200 | if (!"".equals(value)) { 201 | props.add(value); 202 | } 203 | } 204 | String[] childrenNames = new String[props.size()]; 205 | return props.toArray(childrenNames); 206 | } 207 | 208 | /** 209 | * Return all values who's path matches the given property 210 | * name as a String array, or an empty array if the if there 211 | * are no children. This allows you to retrieve several values 212 | * with the same property name. For example, consider the 213 | * XML file entry: 214 | *
215 |      * <foo>
216 |      *     <bar>
217 |      *         <prop>some value</prop>
218 |      *         <prop>other value</prop>
219 |      *         <prop>last value</prop>
220 |      *     </bar>
221 |      * </foo>
222 |      * 
223 | * If you call getProperties("foo.bar.prop") will return a string array containing 224 | * {"some value", "other value", "last value"}. 225 | * 226 | * @param name the name of the property to retrieve 227 | * @return all child property values for the given node name. 228 | */ 229 | public Iterator getChildProperties(String name) { 230 | String[] propName = parsePropertyName(name); 231 | // Search for this property by traversing down the XML heirarchy, 232 | // stopping one short. 233 | Element element = document.getRootElement(); 234 | for (int i = 0; i < propName.length - 1; i++) { 235 | element = element.element(propName[i]); 236 | if (element == null) { 237 | // This node doesn't match this part of the property name which 238 | // indicates this property doesn't exist so return empty array. 239 | return Collections.EMPTY_LIST.iterator(); 240 | } 241 | } 242 | // We found matching property, return values of the children. 243 | Iterator iter = element.elementIterator(propName[propName.length - 1]); 244 | ArrayList props = new ArrayList(); 245 | while (iter.hasNext()) { 246 | props.add(((Element)iter.next()).getText()); 247 | } 248 | return props.iterator(); 249 | } 250 | 251 | /** 252 | * Returns the value of the attribute of the given property name or null 253 | * if it doesn't exist. Note, this 254 | * 255 | * @param name the property name to lookup - ie, "foo.bar" 256 | * @param attribute the name of the attribute, ie "id" 257 | * @return the value of the attribute of the given property or null if 258 | * it doesn't exist. 259 | */ 260 | public String getAttribute(String name, String attribute) { 261 | if (name == null || attribute == null) { 262 | return null; 263 | } 264 | String[] propName = parsePropertyName(name); 265 | // Search for this property by traversing down the XML heirarchy. 266 | Element element = document.getRootElement(); 267 | for (String child : propName) { 268 | element = element.element(child); 269 | if (element == null) { 270 | // This node doesn't match this part of the property name which 271 | // indicates this property doesn't exist so return empty array. 272 | break; 273 | } 274 | } 275 | if (element != null) { 276 | // Get its attribute values 277 | return element.attributeValue(attribute); 278 | } 279 | return null; 280 | } 281 | 282 | 283 | /** 284 | * Return all children property names of a parent property as a String array, 285 | * or an empty array if the if there are no children. For example, given 286 | * the properties X.Y.A, X.Y.B, and X.Y.C, then 287 | * the child properties of X.Y are A, B, and 288 | * C. 289 | * 290 | * @param parent the name of the parent property. 291 | * @return all child property values for the given parent. 292 | */ 293 | public String[] getChildrenProperties(String parent) { 294 | String[] propName = parsePropertyName(parent); 295 | // Search for this property by traversing down the XML heirarchy. 296 | Element element = document.getRootElement(); 297 | for (String aPropName : propName) { 298 | element = element.element(aPropName); 299 | if (element == null) { 300 | // This node doesn't match this part of the property name which 301 | // indicates this property doesn't exist so return empty array. 302 | return new String[]{}; 303 | } 304 | } 305 | // We found matching property, return names of children. 306 | List children = element.elements(); 307 | int childCount = children.size(); 308 | String[] childrenNames = new String[childCount]; 309 | for (int i = 0; i < childCount; i++) { 310 | childrenNames[i] = ((Element)children.get(i)).getName(); 311 | } 312 | return childrenNames; 313 | } 314 | 315 | 316 | /** 317 | * Returns an array representation of the given Jive property. Jive 318 | * properties are always in the format "prop.name.is.this" which would be 319 | * represented as an array of four Strings. 320 | * 321 | * @param name the name of the Jive property. 322 | * @return an array representation of the given Jive property. 323 | */ 324 | private String[] parsePropertyName(String name) { 325 | List propName = new ArrayList(5); 326 | // Use a StringTokenizer to tokenize the property name. 327 | StringTokenizer tokenizer = new StringTokenizer(name, "."); 328 | while (tokenizer.hasMoreTokens()) { 329 | propName.add(tokenizer.nextToken()); 330 | } 331 | return propName.toArray(new String[propName.size()]); 332 | } 333 | 334 | private Element getElement(String name) { 335 | String[] propName = parsePropertyName(name); 336 | // Search for this property by traversing down the XML heirarchy. 337 | Element element = document.getRootElement(); 338 | for (int i = 0; i < propName.length; i++) { 339 | element = element.element(propName[i]); 340 | // Can't find the property so return. 341 | if (element == null) { 342 | break; 343 | } 344 | } 345 | return element; 346 | } 347 | 348 | /** 349 | * Saves the properties to disk as an XML document. A temporary file is 350 | * used during the writing process for maximum safety. 351 | */ 352 | private synchronized void saveProperties() { 353 | boolean error = false; 354 | // Write data out to a temporary file first. 355 | File tempFile = null; 356 | Writer writer = null; 357 | try { 358 | tempFile = new File(file.getParentFile(), file.getName() + ".tmp"); 359 | writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(tempFile), "UTF-8")); 360 | OutputFormat prettyPrinter = OutputFormat.createPrettyPrint(); 361 | XMLWriter xmlWriter = new XMLWriter(writer, prettyPrinter); 362 | xmlWriter.write(document); 363 | } 364 | catch (Exception e) { 365 | Log.error(e.getMessage(), e); 366 | // There were errors so abort replacing the old property file. 367 | error = true; 368 | } 369 | finally { 370 | if (writer != null) { 371 | try { 372 | writer.close(); 373 | } 374 | catch (IOException e1) { 375 | Log.error(e1.getMessage(), e1); 376 | error = true; 377 | } 378 | } 379 | } 380 | 381 | // No errors occured, so delete the main file. 382 | if (!error) { 383 | // Delete the old file so we can replace it. 384 | if (!file.delete()) { 385 | Log.error("Error deleting property file: " + file.getAbsolutePath()); 386 | return; 387 | } 388 | // Copy new contents to the file. 389 | try { 390 | copy(tempFile, file); 391 | } 392 | catch (Exception e) { 393 | Log.error(e.getMessage(), e); 394 | // There were errors so abort replacing the old property file. 395 | error = true; 396 | } 397 | // If no errors, delete the temp file. 398 | if (!error) { 399 | tempFile.delete(); 400 | } 401 | } 402 | } 403 | 404 | /** 405 | * Copies the inFile to the outFile. 406 | * 407 | * @param inFile The file to copy from 408 | * @param outFile The file to copy to 409 | * @throws java.io.IOException If there was a problem making the copy 410 | */ 411 | private static void copy(File inFile, File outFile) throws IOException { 412 | FileInputStream fin = null; 413 | FileOutputStream fout = null; 414 | try { 415 | fin = new FileInputStream(inFile); 416 | fout = new FileOutputStream(outFile); 417 | copy(fin, fout); 418 | } 419 | finally { 420 | try { 421 | if (fin != null) fin.close(); 422 | } 423 | catch (IOException e) { 424 | // do nothing 425 | } 426 | try { 427 | if (fout != null) fout.close(); 428 | } 429 | catch (IOException e) { 430 | // do nothing 431 | } 432 | } 433 | } 434 | 435 | /** 436 | * Copies data from an input stream to an output stream 437 | * 438 | * @param in the stream to copy data from. 439 | * @param out the stream to copy data to. 440 | * @throws java.io.IOException if there's trouble during the copy. 441 | */ 442 | private static void copy(InputStream in, OutputStream out) throws IOException { 443 | // Do not allow other threads to intrude on streams during copy. 444 | synchronized (in) { 445 | synchronized (out) { 446 | byte[] buffer = new byte[256]; 447 | while (true) { 448 | int bytesRead = in.read(buffer); 449 | if (bytesRead == -1) break; 450 | out.write(buffer, 0, bytesRead); 451 | } 452 | } 453 | } 454 | } 455 | 456 | public void destroy() { 457 | document = null; 458 | file = null; 459 | propertyCache.clear(); 460 | propertyCache = null; 461 | 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /src/main/webapp/doc/file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | file.html 6 | 7 | 8 | 9 | 10 | 11 | 12 |

文件上传

接口说明

文件上传是一个通用的总接口,凡是涉及文件上传操作的功能,请先调用文件上传接口,返回正确文件访问路径后,再提交纯文本表单。即提交文本表单时,用户头像、图片相关的表单字段,为上传接口返回的URL值。

url

/api/fs/upload
14 | 

method

post
16 | 

参数

    17 |
  • token:必要参数,为空或错误将不能上传。token可以从登录后的用户信息里获得(不登录是无法获取上传权限的)

该接口支持单文件及多文件上传, 每个文件对应一个请求参数, 如: file1对应a.jpg, file2对应b.jpg

响应结果说明

节点说明

    18 |
  • code:表示响应结果状态,1表示成功,0表示有一或多个文件上传失败
  • message:响应结果的文字说明
  • failed: 此字段标记上传失败的请求参数名称(名称对应上传时所传递的文件),如: [‘file1’,’file2’]
  • datum: 此字段返回了上传成功的文件所对应的文件地址, key为上传时传递的请求参数, value为文件地址

上传成功

{
26 |     code: 1,
27 |     datum: {
28 |         fileUpload1: "/imgs/2015/02/01/20131117223307_JMMX5.thumb.700_0.jpeg",
29 |         fileUpload2: "/imgs/2015/02/01/shortcut.png"
30 |     }
31 | }
32 | 

包含上传失败的文件

{
37 |     code: 0,
38 |     failed: ['fileUpload1']
39 | }
40 | 

请求中未包含文件

{
45 |     message: "uploadFileName can not be null",
46 |     code: 2
47 | }
48 | 
49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/main/webapp/doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | index.html 6 | 7 | 8 | 9 | 10 | 11 | 12 |

HTTP API文档 总览

文件上传

用户相关


意见反馈

URL

/api/feedback
14 | 

METHOD

POST(必须是POST)
16 | 

参数

    17 |
  • token:令牌(可选项)
  • suggestion: 内容(必需项)

响应结果说明

节点说明

    18 |
  • code:表示响应结果状态,1表示成功,0表示失败
  • message:响应结果的文字说明
简要示例
{
23 |   "code": 1,
24 |   "message": "意见反馈成功"
25 | }
26 | 

版本更新检查

URL

/api/version/check
28 | 

METHOD

GET
30 | 

参数

    31 |
  • version:当前客户端版本号(必需项)
  • client: 客户端类型, 可选值只能是android和iphone(必需项)

响应结果说明

节点说明

    32 |
  • code:表示响应结果状态,1表示有更新,0表示无更新
  • message:响应结果状态的文字说明

datum节点说明:

    33 |
  • message: 更新说明
  • url: 新版本下载地址
  • version: 新版本号
简要示例
{
42 |    "code": 1,
43 |    "datum": {
44 |        "message": "修复bug",
45 |        "url": "http://mlongbo.com/android_0_1_1.apk",
46 |        "version": "0.1.2"
47 |    }
48 | }
49 | 

附录(很重要)

文件地址说明

接口中所有的文件地址都是相对路径, 需要拼接地址前缀才能正常使用.

在登录成功后,登录接口返回的resourceServer字段为地址前缀.

如: resourceServer为http://mlongbo.com, 用户头像地址为/img/avatar/rose.jpg,
那么完整的地址就是http://mlongbo.com/img/avatar/rose.jpg

时间戳说明

文档中提到的时间戳全部精确到毫秒

code对照表

    50 |
  • 1 ok - 成功状态
  • 0 faild
  • 2 argument error - 表示请求参数值有误, 或未携带需要的参数值
  • 3 帐号已存在
  • 4 验证码错误
  • 500 error - 服务器错误
  • 404 not found - 请求的资源或接口不存在
  • 422 token error - 未传递token参数,或token值非法
51 | 52 | 53 | 54 | 55 | --------------------------------------------------------------------------------