package com.iamberry.app.api.controller; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.fasterxml.jackson.databind.ObjectMapper; import com.iamberry.app.api.util.AppVersion; import com.iamberry.app.api.util.TokenUtil; import com.iamberry.app.api.util.Utility; import com.iamberry.app.api.util.VerifyCodeUtil; import com.iamberry.app.config.Constants; import com.iamberry.app.config.Response; import com.iamberry.app.config.ResponseHeader; import com.iamberry.app.core.dto.RemoteIpInfoDTO; import com.iamberry.app.core.dto.UserDTO; import com.iamberry.app.core.entity.User; import com.iamberry.app.tool.util.AES; import com.iamberry.app.tool.util.HttpUtility; import com.iamberry.app.tool.util.MD5; import com.iamberry.cache.LocalCache; import com.iamberry.wechat.tools.ResponseJson; import sun.misc.BASE64Decoder; /** * UserController * * @author Moon Cheng * @date 2016年1月18日 上午11:53:04 */ @Controller @RequestMapping("/secure/user") public class UserController extends BaseController { @Autowired HttpServletResponse response; /** * 日志 */ private Logger logger = LoggerFactory.getLogger(UserController.class); /** * 验证码的缓存, 如果存在负载均衡,如此的实现,存在问题,可能导致验证失败( put ----> S1, check ------> S2) */ public LocalCache verifyCodeCache = new LocalCache(100); /** * 保存用户头像的路径 */ private String saveRootPath = "/common/user_head/"; /** * Base64 工具类 */ private BASE64Decoder decoder = new BASE64Decoder(); /** * 主域名 */ private String domain = "http://app.iamberry.com"; @InitBinder("user") public void initBinderUser(WebDataBinder binder) { binder.setFieldDefaultPrefix("user."); } /** * 用户手机注册,成功返回json格式的用户信息 * @author Moon Cheng * @param phone 手机号码 * @param verificationCode 手机收到的短信验证码 * @param password 密码 * @return 2101:验证码错误;2102:验证码超时;1000:请求成功;2000:注册失败; */ @RequestMapping(value = "/register", method = RequestMethod.POST) @ResponseBody private Response register( @RequestParam("phone") String phone, @RequestParam("verification_code") String verificationCode, @RequestParam("password") String password, HttpServletRequest request) { // 校验验证码 int status = checkVerifyCode(phone, verificationCode, 1); //验证手机验证码是否正确 if (status == -1) { //验证码错误 return Response.ERROR_CODE; } else if (status == -2) { //验证码超时 return Response.CODE_TIMEOUT; } else if (status != 1) { // 状态错误 return Response.FAILURE; } // 验证成功, 并开始注册 User user = new User(); String ip = Utility.getIp(request); user = userService.register(phone, password, ip); if (user == null) { //注册失败 return Response.FAILURE; } // 解析App版本 AppVersion v = AppVersion.parseUserAgent(request.getHeader("User-Agent") == null ? "" : request.getHeader("User-Agent")); if (v.getVersion() < 125) { // 兼容低版本APP,因为低版本的APP是自己注册的 UserDTO userInfo = new UserDTO(user.getId(), user.getUsername(), user.getPassword(), user.getToken(), user.getDisplay_name(), user.getDisplay_picture(), user.getCreated_on(), user.getExt_open_id(), user.getExt_name(), user.getExt_type(), user.getLocation(), user.getBaby_nickname(), user.getBaby_dob(), user.getBaby_gender(), user.getStatus_()); userInfo.setEncryptUsername(AES.encrypt(user.getUsername())); userInfo.setUserCode(Constants.TUYA_DEFAULT_PASS); return Response.SUCCESS.setData(userInfo, 1); } // 注册涂鸦,当前用户需要完善宝宝信息 UserDTO userDTO = userService.tuyaConnect(user); userDTO.setIs_perfect_user(2); return Response.SUCCESS.setData(userDTO, 1); } /** * 第三方【微信】用户手机绑定, * @author Moon Cheng * @param phone 手机号 * @param openId 微信的openid * @return 1000:请求成功;2000:注册失败; */ @RequestMapping(value = "/third_part_binding_phone", method = RequestMethod.POST) @ResponseBody private Response thirdPartBindingPhone( @RequestParam("phone") String phone, @RequestParam("open_id") String openId, @RequestParam(value = "code", required = false) String code) { // 兼容低版本APP if (code != null) { ResponseJson json = codeService.validCode(phone, code, 3); if (json.getReturnCode() != 200) { return Response.FAILURE.setData(json); } } // 执行绑定 User user = userService.thirdPartBindingPhone(phone, openId); if (user == null) { return Response.USER_NOT_EXIST; } // 注册涂鸦,并且判断用户当前的状态 UserDTO dto = userService.tuyaConnect(user); // 返回状态填充 if (Constants.USER_NOW_INIT.equals(user.getStatus_())) { // 需要完善手机号码 dto.setIs_perfect_user(1); } else if (Constants.USER_SAVE_TEL.equals(user.getStatus_())) { // 需要完善宝宝信息 dto.setIs_perfect_user(2); } else { // 注册完成 dto.setIs_perfect_user(3); } return Response.SUCCESS.setData(dto, 1); } /** * login * 手机号登录, * @param phone 手机号 * @param password 密码 * @return 2012:身份验证失败;2013:账户冻结;1000:请求成功; */ @RequestMapping(value = "/login", method = RequestMethod.POST) @ResponseBody private Response login( @RequestParam(value = "phone", required = true) String phone, @RequestParam(value = "password", required = true) String password) { // 登录 Object user = userService.login(phone, password); int status = 0; if (user instanceof Integer) { status = (Integer) user; switch (status) { case -1: //账号不存在 return Response.USER_NOT_EXIST; case -2: //密码错误 return Response.AUTHENTICATION_ERROR; case -3: //用户状态不正常 return Response.USER_FROZEN_ERROR; } } return Response.SUCCESS.setData((UserDTO) user, 1); } /** * thirdPartLogin * 第三方登录 * @param openId 微信open_id * @param extType 来源 qq或者微信 * @param extName extname * @param extPicture 图片 * @return 2012:身份验证失败;2013:账户冻结;1000:请求成功; */ @RequestMapping(value = "/third_part_login", method = RequestMethod.POST) @ResponseBody private Response thirdPartLogin( @RequestParam(value = "open_id") String openId, @RequestParam(value = "ext_type") String extType, @RequestParam(value = "ext_name") String extName, @RequestParam(value = "ext_picture") String extPicture) { String ip = Utility.getIp(request); User user = userService.thirdPartLogin(openId, extType, extName, extPicture, ip); if (Constants.USER_INACTIVE.equals(user.getStatus_())) { return Response.USER_FROZEN_ERROR; } // 同步涂鸦 UserDTO dto = userService.tuyaConnect(user); if (Constants.USER_NOW_INIT.equals(user.getStatus_())) { // 需要完善手机号码 dto.setIs_perfect_user(1); } else if (Constants.USER_SAVE_TEL.equals(user.getStatus_())) { // 需要完善宝宝信息 dto.setIs_perfect_user(2); } else { // 注册完成 dto.setIs_perfect_user(3); } return Response.SUCCESS.setData(dto, 1); } /** * getUserInfo * 获取用户基本信息 * @param id 用户id * @token 用户token 登录的时候设置的唯一的token * @return Response 2100:无效token;2020:无效用户;1000:请求成功; */ @RequestMapping(value = "/get_user_info", method = RequestMethod.POST) @ResponseBody private Response getUserInfo(@RequestParam("token") String token) { User userInfo = userService.validateUserToken(token); if (userInfo == null) {//token不存在,表示登录的时候没有.. return Response.INVALID_TOKEN; } if (Constants.USER_NOW_INIT.equals(userInfo.getStatus_())) { // 需要完善手机号码 userInfo.setIs_perfect_user(1); } else if (Constants.USER_SAVE_TEL.equals(userInfo.getStatus_())) { // 需要完善宝宝信息 userInfo.setIs_perfect_user(2); } else { // 注册完成 userInfo.setIs_perfect_user(3); } // 兼容低版本APP if (userInfo.getLocation() == null) { userInfo.setLocation("深圳"); } return Response.SUCCESS.setData(userInfo, 1); } protected static final ObjectMapper mapper = new ObjectMapper(); /** * getLocation * * @param ip * @return String */ public RemoteIpInfoDTO getLocation(String ip) { RemoteIpInfoDTO remoteIpInfoDTO = new RemoteIpInfoDTO(); if (ip.indexOf(",") != -1) { ip = ip.split(",")[0]; } logger.info("IP为:" + ip); try { String result = HttpUtility.httpsGet("http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=" + ip); remoteIpInfoDTO = mapper.readValue(result, RemoteIpInfoDTO.class); } catch (Exception e) { } return remoteIpInfoDTO; } /** * updateProfile * 更新用户基本信息 * @param user 用户信息 * @param token token * @return Response 2100:无效token;1000:请求成功; */ @RequestMapping(value = "/update_profile", method = RequestMethod.POST) @ResponseBody private Response updateProfile( User user, @RequestParam("token") String token, HttpServletRequest request) { User userInfo = userService.updateUserInfo(user, token); if (userInfo == null) { return Response.INVALID_TOKEN; } /** * 完善成功 */ userInfo.setIs_perfect_user(3); UserDTO dto = userService.tuyaConnect(userInfo); return Response.SUCCESS.setData(dto, 1); } /** * updateDisplayPicture * 更改用户头像,注意上传头像成功后,需要一段时间才能同步到所有服务器 * @param user 用户信息 * @param token 唯一标识符 * @return Response * @throws IOException */ @SuppressWarnings({ "deprecation"}) @RequestMapping(value = "/update_display_picture", method = RequestMethod.POST) @ResponseBody private Response updateDisplayPicture(@RequestParam("picture_base64") String pictureBase64, @RequestParam("token") String token) throws IOException { // 判断文件类型 byte [] b = new byte[28]; pictureBase64.getBytes(0, 27, b, 0); String temp = new String(b); try { b = decoder.decodeBuffer(temp); } catch (IOException e) { logger.error("updateDisplayPicture:" + e.getMessage()); return Response.FAILURE; } // 获取上传的文件类型 String type = null; // 文件头 String fileHead = com.iamberry.wechat.file.FileUtils.byte2hex(b); fileHead = fileHead.toUpperCase(); type = com.iamberry.wechat.file.FileUtils.getType(fileHead); if (type == null) { return Response.FAILURE; } // 获取保存地址 String savePath = request.getServletContext().getRealPath(saveRootPath); StringBuilder tempDir = new StringBuilder(savePath); Long ran = new Date().getTime(); tempDir.append(File.separator); tempDir.append(ran); tempDir.append(File.separator); // 判断保存路径是否存在,如果不存在,那么创建 File file=new File(savePath); if (!file.exists()) { logger.info("updateDisplayPicture-exists"); file.mkdirs(); } // 获取保存文件名称 StringBuilder saveName = new StringBuilder(com.iamberry.wechat.file.FileUtils.getToken()); saveName.append(".").append(type); tempDir.append(saveName); // 保存磁盘 String userHeadURL = null; if (!com.iamberry.wechat.file.FileUtils.generateImage(pictureBase64, tempDir.toString())) { logger.error("updateDisplayPicture-generateImage saveFile Error"); return Response.FAILURE; } else { userHeadURL = domain + request.getContextPath() + saveRootPath + ran + "/" + saveName.toString(); } // 保存 String path = userService.updateDisplayPicture(userHeadURL, token); if (path.equals("ERROR")) { return Response.FAILURE; } return Response.SUCCESS.setData(userHeadURL, 1); } /** * 【外包开发的老版本】获取头像,外包是为了完成分布式的文件共享 */ @RequestMapping(value = "/avator/{id}/{rand}", method = RequestMethod.GET) @ResponseBody private void getDisplayPicture() { String url = request.getRequestURI(); url = url.substring(0, url.lastIndexOf('/')); String userID = url.substring(url.lastIndexOf('/') + 1); String avator = userService.selectUserAvator(Long.valueOf(userID)); response.setContentType("image/jpg"); try { OutputStream os = response.getOutputStream(); byte[] decoderBytes = decoder.decodeBuffer(avator); os.write(decoderBytes); os.flush(); os.close(); } catch (Exception e) { } } /** * changePassword * 修改手机密码 * @param oldPassword 新密码 * @param newPassword 旧密码 * @param token token * @return Response 2101:验证码错误;2102:验证码超时;2100:无效token;1000:请求成功; */ @RequestMapping(value = "/change_password", method = RequestMethod.POST) @ResponseBody private Response changePassword( @RequestParam("old_password") String oldPassword, @RequestParam("new_password") String newPassword, @RequestParam("token") String token) { int status = userService.changePassword(oldPassword, newPassword, token); if (status == -1) { //token错误 return Response.INVALID_TOKEN; } else if (status == 0) { //密码错误 return Response.ERROR_OLDPASSWORD; } else { return Response.SUCCESS.setData(null); } } /** * changePhone * 修改手机号码 * @author Moon Cheng, Yin * @param newPhone 新手机号 * @param token token * @param verificationCode 手机验证码 * @return2101:验证码错误;2102:验证码超时;2100:无效token;1000:请求成功; */ @RequestMapping(value = "/change_phone", method = RequestMethod.POST) @ResponseBody private Response changePhone(@RequestParam("new_phone") String newPhone, @RequestParam("verification_code") String verificationCode, @RequestParam("token") String token) { int status = checkVerifyCode(newPhone, verificationCode, 2); User user = null; if (status == -1) { //验证码错误 return Response.ERROR_CODE; } else if (status == -2) { //验证码超时 return Response.CODE_TIMEOUT; } else if (status == 1) { user = userService.changePhone(newPhone, token); if (user == null) { return Response.INVALID_TOKEN; } } return Response.SUCCESS.setData(userService.tuyaConnect(user), 1); } /** * resetPassword * 重置手机密码 * @param username 用户米 * @param newPassword 新密码 * @param verificationCode 验证码 * @return2101:验证码错误;2102:验证码超时;2001:无效用户;2000:重置密码失败; 1000:请求成功; */ @RequestMapping(value = "/reset_password", method = RequestMethod.POST) @ResponseBody private Response resetPassword( @RequestParam(value = "username") String username, @RequestParam("new_password") String newPassword, @RequestParam("verification_code") String verificationCode) { int status = checkVerifyCode(username, verificationCode, 3); if (status == -1) {//验证码错误 return Response.ERROR_CODE; } else if (status == -2) { //验证码超时 return Response.CODE_TIMEOUT; } else if (status == 1) { status = userService.resetPassWord(username, newPassword); if (status == -3) {//账号不存在 return Response.USER_NOT_EXIST; } else if (status == 0) { return Response.FAILURE; } } return Response.SUCCESS.setData(null); } /** * 获取token,用于发送验证码 * @return * @author 献 * @Time 2016年12月5日 */ @RequestMapping(value = "/get_token", method = RequestMethod.POST) @ResponseBody public Response getToken(@RequestParam("phone") String phone) { // 格式校验 if (StringUtils.isEmpty(phone)) { return new Response(new ResponseHeader(404, "手机号码有误!", 0)); } // 校验手机号码格式 if (!isMobileNumber(phone)) { return new Response(new ResponseHeader(404, "手机号码有误!", 0)); } // 防轰炸原则 Response t = codeService.interval(phone); if (t.getHeader().getStatus() == 404) { // 接口限制,不能使用 return t; } // 生成原始token String key = TokenUtil.getToken(); if (key.length() < 16) { key = Utility.getRandomCode(16); } // 获取加密的key key = key.substring(0, 16); String temp = AES.encryptCBC(phone, key, "IAMBERRY321#$%^&"); StringBuilder token = new StringBuilder(key); token.append(temp); // 返回token if (t.getHeader().getStatus() == 403) { // 1001 需要验证码 ** 1.2.6之前都没有此 return new Response(new ResponseHeader(1000, "SUCCESS_TO_VERIFY", 0), token); } else { // 1000 成功 return new Response(new ResponseHeader(1000, "SUCCESS", 0), token); } } /** * 获取验证码 * @param response * @throws IOException * @author 献 * @Time 2016年12月13日 */ @RequestMapping(value = "/get/image_code") public void geImagetVerifyCode(HttpServletResponse response, @RequestParam(value = "phone") String phone, @RequestParam(value = "token") String token, @RequestParam(value = "width", required = false, defaultValue = "130") Integer width, @RequestParam(value = "height", required = false, defaultValue = "30") Integer height ) throws IOException { // 格式校验 /*User user = userService.validateUserToken(token); if (user == null) { // token无效 response.getWriter().write("{\"status\":402}"); return; }*/ if (StringUtils.isEmpty(phone)) { // 手机号码不能为空 response.getWriter().write("{\"status\":403}"); return; } if (!isMobileNumber(phone)) { // 手机号码格式不正确 response.getWriter().write("{\"status\":404}"); return; } // 生成验证码,并保存 String verifyCode = VerifyCodeUtil.generateTextCode(0, 4, null); response.setContentType("image/jpeg"); BufferedImage bufferedImage = VerifyCodeUtil.generateImageCode(verifyCode, width, height, new java.util.Random().nextInt(5), true, Color.WHITE, Color.BLACK, null); ImageIO.write(bufferedImage, "JPEG", response.getOutputStream()); // 保存验证码 K:phone,V:code verifyCodeCache.put(phone, verifyCode); } /** * 验证手机号格式 * @param mobilenumber * @return boolean,true:校验通过;false:表示校验失败 * @author 献 * @Time 2016年12月6日 */ public static boolean isMobileNumber(String mobilenumber) { Pattern p = Pattern.compile("^[1][3,4,5,7,8][0-9]{9}$"); Matcher m = p.matcher(mobilenumber); return m.matches(); } /** * 发生手机验证码
* 1代表注册,2代表更换手机号,3代表忘记密码 * @param phone 手机号码 * @param statusCode 使用场景 * @param token 验证码token * @param timestamp 时间戳 * @param imageCode 图片验证码 * @param signature 签名 * @return * @author 献 * @Time 2016年12月13日 */ @RequestMapping(value = "/send_code", method = RequestMethod.GET) @ResponseBody private Response sendCode( @RequestParam(value = "phone") String phone, @RequestParam("status_code") int statusCode, @RequestParam(value = "code_token", required = false) String token, @RequestParam(value = "timestamp", required = false) String timestamp, @RequestParam(value = "image_code", required = false) String imageCode, @RequestParam(value = "signature", required = false) String signature) { // 放轰炸原则 & 校验验证码 Response t = codeService.interval(phone); if (t.getHeader().getStatus() == 404) { // 接口限制,不能使用 return t; } // 版本控制 AppVersion appVersion = (AppVersion) request.getAttribute("v"); if (appVersion != null && appVersion.getVersion() >= 126 && t.getHeader().getStatus() == 403) { // app 1.2.6 // 校验token & phone if (StringUtils.isEmpty(token) || token.length() <= 16) { return new Response(new ResponseHeader(404, "您的流程有误,或者请下载最新版本APP!", 0)); } String key = token.substring(0, 16); String temp = AES.decryptCBC(token.substring(16), key, "IAMBERRY321#$%^&"); // 校验签名 StringBuilder builder = new StringBuilder("IAMBERRY321"); builder.append(statusCode).append(token).append(timestamp); if (!MD5.md5(builder.toString()).equals(signature)) { return new Response(new ResponseHeader(404, "您的流程有误,或者请下载最新版本APP!", 0)); } if (!phone.equals(temp)) { return new Response(new ResponseHeader(404, "您的流程有误,或者请下载最新版本APP!", 0)); } // 置换数据 phone = temp; // 需要验证码 if (StringUtils.isEmpty(imageCode)) { return new Response(new ResponseHeader(403, "请输入图片验证码", 0)); } if (!StringUtils.equals(imageCode, verifyCodeCache.get(phone))) { return new Response(new ResponseHeader(402, "图片验证码错误,请重新输入!", 0)); } } else { // 1.2.6 以前的版本 if (token != null) { // 1.2.5版本 // 校验token & phone if (StringUtils.isEmpty(token) || token.length() <= 16) { return new Response(new ResponseHeader(404, "您的流程有误,或者请下载最新版本APP!", 0)); } String key = token.substring(0, 16); String temp = AES.decryptCBC(token.substring(16), key, "IAMBERRY321#$%^&"); // 校验签名 StringBuilder builder = new StringBuilder("IAMBERRY321"); builder.append(statusCode).append(token).append(timestamp); if (!MD5.md5(builder.toString()).equals(signature)) { return new Response(new ResponseHeader(404, "您的流程有误,或者请下载最新版本APP!", 0)); } if (!phone.equals(temp)) { return new Response(new ResponseHeader(404, "您的流程有误,或者请下载最新版本APP!", 0)); } // 置换数据 phone = temp; } // 1.2.5以前的版本 } // 通用的逻辑部分 String code = null; Map status = new HashMap(); User user = new User(); try { switch (statusCode) { case 1:// register user = userService.selectUserByUsername(phone); if (user != null) { return Response.ERROR_REGISTER; } break; case 2:// change phone user = userService.selectUserByUsername(phone); if (user != null) { return Response.ERROR_REGISTER; } break; case 3:// reset password /*user = userService.selectUserByUsername(phone); if (user == null) { return Response.USER_NOT_EXIST; }*/ break; default: break; } codeService.sendCode(phone, statusCode); status.put("code", 0); } catch (Exception e) { return Response.SERVER_INTERNAL_ERROR; } if (0 == status.get(Constants.SMS_RETURNCODE)) { return Response.SUCCESS.setData(code, 0); } else { return Response.FAILURE.setHeader(new ResponseHeader(status.get(Constants.SMS_RETURNCODE), "", 0)); } } /** * 获取封面图片 * @return json */ @RequestMapping(value = "/start_page", method = RequestMethod.GET) @ResponseBody private Response getStartPage() { String pageUrl = userService.getStartPage(); return Response.SUCCESS.setData(pageUrl, 1); } /** * 获取地址url * @return */ @RequestMapping(value = "/get_location", method = RequestMethod.GET) @ResponseBody private Response getLocation(HttpServletRequest request) { String IP = Utility.getIp(request); RemoteIpInfoDTO remoteIpInfoDTO = userService.getLocation(IP); return Response.SUCCESS.setData(remoteIpInfoDTO, 1); } /** * 获取app版本号 * @return */ @RequestMapping(value = "/get_version", method = RequestMethod.GET) @ResponseBody private Response getApkVersion() { return Response.SUCCESS.setData(userService.getApkVersion()); } }