Ver Fonte

开发平台基础后台功能

stjdydayou há 4 anos atrás
pai
commit
785fee9350
26 ficheiros alterados com 1705 adições e 1 exclusões
  1. 19 0
      src/main/java/com/zhiqiyun/open/annotation/Permission.java
  2. 97 0
      src/main/java/com/zhiqiyun/open/manager/Result.java
  3. 34 0
      src/main/java/com/zhiqiyun/open/manager/controller/AuthorityInfoController.java
  4. 1 1
      src/main/java/com/zhiqiyun/open/manager/controller/IndexController.java
  5. 231 0
      src/main/java/com/zhiqiyun/open/manager/controller/OauthController.java
  6. 86 0
      src/main/java/com/zhiqiyun/open/manager/controller/RoleInfoController.java
  7. 188 0
      src/main/java/com/zhiqiyun/open/manager/controller/UserInfoController.java
  8. 1 0
      src/main/java/com/zhiqiyun/open/manager/package-info.java
  9. 48 0
      src/main/java/com/zhiqiyun/open/manager/params/ChangePasswordParam.java
  10. 27 0
      src/main/java/com/zhiqiyun/open/manager/params/LoginParam.java
  11. 58 0
      src/main/java/com/zhiqiyun/open/manager/params/QueryPageParams.java
  12. 10 0
      src/main/java/com/zhiqiyun/open/manager/params/QueryRoleParam.java
  13. 30 0
      src/main/java/com/zhiqiyun/open/manager/params/QueryUserParam.java
  14. 15 0
      src/main/java/com/zhiqiyun/open/manager/params/SaveRoleParam.java
  15. 16 0
      src/main/java/com/zhiqiyun/open/manager/params/SaveUserBaseInfoParam.java
  16. 33 0
      src/main/java/com/zhiqiyun/open/manager/params/SaveUserParam.java
  17. 48 0
      src/main/java/com/zhiqiyun/open/models/RangeDate.java
  18. 31 0
      src/main/java/com/zhiqiyun/open/service/CaptchaService.java
  19. 172 0
      src/main/java/com/zhiqiyun/open/utils/ServletContext.java
  20. 366 0
      src/main/java/com/zhiqiyun/open/utils/Token.java
  21. 40 0
      src/main/java/com/zhiqiyun/open/utils/validation/DomainName.java
  22. 40 0
      src/main/java/com/zhiqiyun/open/utils/validation/MobileNumber.java
  23. 40 0
      src/main/java/com/zhiqiyun/open/utils/validation/StrongPassword.java
  24. 25 0
      src/main/java/com/zhiqiyun/open/utils/validation/constraints/DomainNameValidate.java
  25. 24 0
      src/main/java/com/zhiqiyun/open/utils/validation/constraints/MobileNumberValidate.java
  26. 25 0
      src/main/java/com/zhiqiyun/open/utils/validation/constraints/StrongPasswordValidate.java

+ 19 - 0
src/main/java/com/zhiqiyun/open/annotation/Permission.java

@@ -0,0 +1,19 @@
+package com.zhiqiyun.open.annotation;
+
+
+import java.lang.annotation.*;
+
+/**
+ * @author jtoms
+ */
+
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Permission {
+    String[] value() default {};
+
+    String tags();
+
+    boolean writeLog() default true;
+}

+ 97 - 0
src/main/java/com/zhiqiyun/open/manager/Result.java

@@ -0,0 +1,97 @@
+package com.zhiqiyun.open.manager;
+
+/**
+ * @author jtoms
+ */
+public class Result {
+
+    private String code = Code.SUCCESS.getCode();
+
+    private String message = Code.SUCCESS.getMessage();
+
+    private Object data;
+
+    public static Result instance(String message) {
+        Result json = new Result();
+        json.setCode(Code.MESSAGE_ERROR);
+        json.setMessage(message);
+        return json;
+    }
+
+    public static Result instance(Code code) {
+        Result json = new Result();
+        json.setCode(code);
+        json.setMessage(code.getMessage());
+        return json;
+    }
+
+    public static Result instance(Code code, String message) {
+        Result json = new Result();
+        json.setCode(code);
+        json.setMessage(message);
+        return json;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public Result setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+
+    public Object getData() {
+        return data;
+    }
+
+    public Result setData(Object data) {
+        this.data = data;
+        return this;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public Result setCode(Code code) {
+        this.code = code.getCode();
+        return this;
+    }
+
+    public enum Code {
+        SUCCESS("SUCCESS", "请求成功"),
+        MESSAGE_ERROR("MESSAGE_ERROR", "操作失败,请稍后再试"),
+        MESSAGE_SUCCESS("MESSAGE_SUCCESS", "操作成功"),
+        SERVER_ERROR("SERVER_ERROR", "服务器忙,请稍后再试"),
+        NO_FOUND("NO_FOUND", "您访问的资源不存在"),
+        FORBIDDEN("FORBIDDEN", "您没的权限访问此资源"),
+        NO_LOGIN("NO_LOGIN", "您还没有登录,请先登录"),
+        NOT_SUFFICIENT_FUNDS("NOT_SUFFICIENT_FUNDS", "您账户全额不足,请先充值");
+
+        private String code;
+        private String message;
+
+        Code(String code, String message) {
+            this.code = code;
+            this.message = message;
+        }
+
+        public String getCode() {
+            return code;
+        }
+
+        public void setCode(String code) {
+            this.code = code;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+
+        public void setMessage(String message) {
+            this.message = message;
+        }
+    }
+
+}

+ 34 - 0
src/main/java/com/zhiqiyun/open/manager/controller/AuthorityInfoController.java

@@ -0,0 +1,34 @@
+package com.zhiqiyun.open.manager.controller;
+
+import com.zhiqiyun.open.annotation.Permission;
+import com.zhiqiyun.open.manager.Result;
+import com.zhiqiyun.open.models.AuthorityInfo;
+import com.zhiqiyun.open.service.AuthorityInfoService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * @author jtoms
+ */
+@RestController
+@RequestMapping("/user/authority")
+public class AuthorityInfoController {
+
+	@Autowired
+	private AuthorityInfoService authorityInfoService;
+
+	@Permission(value = {"oauth.authority.find", "oauth.role.authority"}, tags = "查询系统权限")
+	@GetMapping("/findAllAuthorities")
+	public Result findAllAuthorities() {
+		List<AuthorityInfo> listData = this.authorityInfoService.findChildren(0L);
+		for (AuthorityInfo authority : listData) {
+			List<AuthorityInfo> children = this.authorityInfoService.findChildren(authority.getId());
+			authority.setChildren(children);
+		}
+		return Result.instance(Result.Code.SUCCESS).setData(listData);
+	}
+}

+ 1 - 1
src/main/java/com/zhiqiyun/open/controller/IndexController.java → src/main/java/com/zhiqiyun/open/manager/controller/IndexController.java

@@ -1,4 +1,4 @@
-package com.zhiqiyun.open.controller;
+package com.zhiqiyun.open.manager.controller;
 
 import com.zhiqiyun.open.service.Ip2RegionService;
 import com.zhiqiyun.open.service.SequenceService;

+ 231 - 0
src/main/java/com/zhiqiyun/open/manager/controller/OauthController.java

@@ -0,0 +1,231 @@
+package com.zhiqiyun.open.manager.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.zhiqiyun.open.annotation.Permission;
+import com.zhiqiyun.open.enmus.AccountType;
+import com.zhiqiyun.open.enmus.PasswordType;
+import com.zhiqiyun.open.enmus.UserLoginLogState;
+import com.zhiqiyun.open.manager.Result;
+import com.zhiqiyun.open.manager.params.ChangePasswordParam;
+import com.zhiqiyun.open.manager.params.LoginParam;
+import com.zhiqiyun.open.manager.params.QueryPageParams;
+import com.zhiqiyun.open.manager.params.SaveUserBaseInfoParam;
+import com.zhiqiyun.open.models.*;
+import com.zhiqiyun.open.mybatis.paginator.domain.PageResult;
+import com.zhiqiyun.open.service.CaptchaService;
+import com.zhiqiyun.open.service.OauthService;
+import com.zhiqiyun.open.service.UserBaseInfoService;
+import com.zhiqiyun.open.utils.*;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@RestController
+@RequestMapping("/oauth")
+public class OauthController {
+
+	@Autowired
+	private OauthService oauthService;
+
+	@Autowired
+	private UserBaseInfoService userBaseInfoService;
+
+	@Autowired
+	private CaptchaService captchaService;
+
+	@Autowired
+	private ThreadPoolTaskExecutor threadPoolTaskExecutor;
+
+	@GetMapping("/getCaptcha")
+	public Result captcha() {
+		String token = ServletContext.getAccessToken();
+		if (StringUtils.isBlank(token) || !Token.checkToken(token)) {
+			token = Token.init(ServletContext.getRemoteIPAddress()).toTokenString();
+		}
+		String captcha = captchaService.generateCaptchaBase64(token);
+		Map<String, String> dataMap = new HashMap<>();
+		dataMap.put("token", token);
+		dataMap.put("captcha", captcha);
+		if (StringUtils.isNotBlank(captcha)) {
+			return Result.instance(Result.Code.SUCCESS).setData(dataMap);
+		} else {
+			return Result.instance(Result.Code.SERVER_ERROR, "获取验证码失败");
+		}
+	}
+
+	@PostMapping("/login")
+	public Result login(@Valid @RequestBody LoginParam loginParam) {
+		UserLoginLog userLoginLog = new UserLoginLog();
+		try {
+			AccountType accountType = AccountType.userName;
+			if (PatternUtils.validExpression(loginParam.getLoginAccount(), PatternUtils.MOBILE_EXPRESSION)) {
+				accountType = AccountType.mp;
+			}
+			if (PatternUtils.validExpression(loginParam.getLoginAccount(), PatternUtils.EMAIL_EXPRESSION)) {
+				accountType = AccountType.email;
+			}
+			userLoginLog.setLoginAccount(loginParam.getLoginAccount());
+			userLoginLog.setAccountType(accountType);
+
+			boolean compareCaptchaResult = this.captchaService.compareCaptcha(loginParam.getCaptcha(), loginParam.getToken(), true);
+			if (!compareCaptchaResult) {
+				userLoginLog.setState(UserLoginLogState.CAPTCHA_ERROR);
+				return Result.instance("验证码错误,请重新输入");
+			}
+
+			UserLoginAccount userLoginAccount = this.userBaseInfoService.findLoginAccount(loginParam.getLoginAccount(), accountType);
+			if (userLoginAccount == null) {
+				userLoginLog.setState(UserLoginLogState.ACCOUNT_NOT_EXIST);
+				return Result.instance("用户名或密码错误,请重新输入");
+			} else {
+				UserBaseInfo userBaseInfo = this.userBaseInfoService.findById(userLoginAccount.getUid());
+
+				UserPassword userPassword = this.userBaseInfoService.findPassword(userBaseInfo.getId(), PasswordType.login);
+
+				String loginPassword = this.oauthService.generatePassword(loginParam.getLoginPassword(), userPassword.getSalt());
+				if (loginPassword.equals(userPassword.getPasswd())) {
+
+					List<String> listAuthorities = this.userBaseInfoService.findUserAuthorities(userBaseInfo.getId());
+
+					String accessToken = Token.init(ServletContext.getRemoteIPAddress()).toTokenString();
+					OauthInfo oauthInfo = new OauthInfo()
+							.setId(userBaseInfo.getId())
+							.setAccessToken(accessToken)
+							.setNickName(userBaseInfo.getNickName())
+							.setAvatar(userBaseInfo.getAvatar())
+							.setLoginTime(DateUtil.current())
+							.setGender(userBaseInfo.getGender())
+							.setAuthorities(listAuthorities);
+
+					this.oauthService.setAuth(accessToken, oauthInfo);
+					userLoginLog.setState(UserLoginLogState.SUCCESS);
+					return Result.instance(Result.Code.SUCCESS).setData(oauthInfo);
+				} else {
+					userLoginLog.setState(UserLoginLogState.PASSWORD_ERROR);
+					return Result.instance("用户名或密码错误,请重新输入");
+				}
+			}
+		} catch (Exception e) {
+			log.error("", e);
+			userLoginLog.setState(UserLoginLogState.LOGIN_EXCEPTION);
+			return Result.instance("系统异常,请稍后再试");
+		} finally {
+			userLoginLog.setLoginIp(ServletContext.getRemoteIPAddress());
+			userLoginLog.setFromSource(ServletContext.getFromSource());
+			userLoginLog.setDeviceId("");
+			userLoginLog.setLoginTime(DateUtil.current());
+			userLoginLog.setLoginLocal("未知地址");
+
+			log.debug(JSON.toJSONString(userLoginLog));
+			this.threadPoolTaskExecutor.execute(() -> userBaseInfoService.insertLoginLog(userLoginLog));
+		}
+	}
+
+
+	@Permission(tags = "获取用户登录信息", writeLog = false)
+	@GetMapping("/getOauthInfo")
+	public Result getOauthInfo() {
+		String accessToken = ServletContext.getAccessToken();
+		OauthInfo oauthInfo = oauthService.getAuth(accessToken);
+		if (oauthInfo != null) {
+
+			UserBaseInfo userBaseInfo = this.userBaseInfoService.findById(oauthInfo.getId());
+			List<String> listAuthorities = this.userBaseInfoService.findUserAuthorities(userBaseInfo.getId());
+
+			oauthInfo.setNickName(userBaseInfo.getNickName())
+					.setAvatar(userBaseInfo.getAvatar())
+					.setLoginTime(DateUtil.current())
+					.setGender(userBaseInfo.getGender())
+					.setAuthorities(listAuthorities);
+			this.oauthService.setAuth(accessToken, oauthInfo);
+		}
+		return Result.instance(Result.Code.SUCCESS).setData(oauthInfo);
+	}
+
+	@GetMapping("/loginOut")
+	public Result loginOut() {
+		String accessToken = ServletContext.getAccessToken();
+		if (StringUtils.isNotBlank(accessToken)) {
+			this.oauthService.destroy(accessToken);
+		}
+		return Result.instance(Result.Code.SUCCESS);
+	}
+
+
+	@Permission(tags = "获取个人登录账号", writeLog = false)
+	@GetMapping("/getLoginAccounts")
+	public Result getLoginAccounts() {
+		String accessToken = ServletContext.getAccessToken();
+		OauthInfo oauthInfo = oauthService.getAuth(accessToken);
+		Map<String, Object> dataMap = new HashMap<>();
+		List<UserLoginAccount> loginAccounts = this.userBaseInfoService.findLoginAccounts(oauthInfo.getId());
+		for (UserLoginAccount account : loginAccounts) {
+			dataMap.put(account.getAccountType().name(), account.getLoginAccount());
+		}
+		return Result.instance(Result.Code.SUCCESS).setData(dataMap);
+	}
+
+	@Permission(tags = "获取个人基本信息", writeLog = false)
+	@GetMapping("/getUserBaseInfo")
+	public Result getUserBaseInfo() {
+		String accessToken = ServletContext.getAccessToken();
+		OauthInfo oauthInfo = oauthService.getAuth(accessToken);
+		UserBaseInfo userBaseInfo = this.userBaseInfoService.findById(oauthInfo.getId());
+		return Result.instance(Result.Code.SUCCESS).setData(userBaseInfo);
+	}
+
+
+	@Permission(tags = "保存个人基本信息")
+	@PostMapping("/saveUserBaseInfo")
+	public Result saveUserBaseInfo(@Valid @RequestBody SaveUserBaseInfoParam param) {
+		String accessToken = ServletContext.getAccessToken();
+		OauthInfo oauthInfo = oauthService.getAuth(accessToken);
+		UserBaseInfo userBaseInfo = new UserBaseInfo();
+		BeanUtils.copyProperties(param, userBaseInfo);
+		this.userBaseInfoService.updateBaseInfo(oauthInfo.getId(), userBaseInfo);
+		return Result.instance(Result.Code.MESSAGE_SUCCESS);
+	}
+
+	@Permission(tags = "获取用户登录记录", writeLog = false)
+	@PostMapping("/getLoginLogs")
+	public Result getLoginLogs(@RequestBody QueryPageParams params) {
+		String accessToken = ServletContext.getAccessToken();
+		OauthInfo oauthInfo = oauthService.getAuth(accessToken);
+
+		PageResult<UserLoginLog> pageResult = this.userBaseInfoService.findLoginLogPage(params.getPageBounds(), oauthInfo.getId());
+
+		return Result.instance(Result.Code.SUCCESS).setData(pageResult);
+	}
+
+	@Permission(tags = "用户自己修改密码")
+	@PostMapping("/changePassword")
+	public Result changePassword(@Valid @RequestBody ChangePasswordParam param) {
+		String accessToken = ServletContext.getAccessToken();
+		OauthInfo oauthInfo = oauthService.getAuth(accessToken);
+
+		if (!param.getNewPassword().equals(param.getConfirmNewPassword())) {
+			return Result.instance("新密码与确认新密码不一致");
+		}
+		UserPassword userPassword = this.userBaseInfoService.findPassword(oauthInfo.getId(), PasswordType.login);
+		String loginPassword = this.oauthService.generatePassword(param.getLoginPassword(), userPassword.getSalt());
+		if (loginPassword.equals(userPassword.getPasswd())) {
+			String salt = RandomUtil.getSalt();
+			String newLoginPassword = this.oauthService.generatePassword(param.getNewPassword(), salt);
+
+			this.userBaseInfoService.updatePassword(oauthInfo.getId(), newLoginPassword, salt, PasswordType.login);
+			return Result.instance(Result.Code.MESSAGE_SUCCESS, "修改密码成功,下次请用新密码登录");
+
+		} else {
+			return Result.instance("旧的登录密码输入错误");
+		}
+	}
+}

+ 86 - 0
src/main/java/com/zhiqiyun/open/manager/controller/RoleInfoController.java

@@ -0,0 +1,86 @@
+package com.zhiqiyun.open.manager.controller;
+
+import com.zhiqiyun.open.annotation.Permission;
+import com.zhiqiyun.open.manager.Result;
+import com.zhiqiyun.open.manager.params.QueryRoleParam;
+import com.zhiqiyun.open.manager.params.SaveRoleParam;
+import com.zhiqiyun.open.models.RoleInfo;
+import com.zhiqiyun.open.mybatis.paginator.domain.PageResult;
+import com.zhiqiyun.open.service.RoleInfoService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * @author jtoms
+ */
+@Slf4j
+@RestController
+@RequestMapping("/user/role")
+public class RoleInfoController {
+
+    @Autowired
+    private RoleInfoService roleInfoService;
+
+    @Permission(value = "oauth.role.find", tags = "查询系统角色")
+    @PostMapping("/findPage")
+    public Result findRolePage(@RequestBody QueryRoleParam params) {
+
+        PageResult<RoleInfo> resultData = this.roleInfoService.findPage(params.getPageBounds(), params.getQueryWrapper());
+
+        List<RoleInfo> listData = resultData.getData();
+
+        for (RoleInfo roleInfo : listData) {
+            roleInfo.setListAuthorities(this.roleInfoService.findAuthorities(roleInfo.getId()));
+        }
+
+        return Result.instance(Result.Code.SUCCESS).setData(resultData);
+    }
+
+    @Permission(value = "oauth.role.add", tags = "添加系统角色")
+    @PostMapping("/save")
+    public Result save(@Valid @RequestBody SaveRoleParam param) {
+        RoleInfo roleInfo = new RoleInfo();
+        BeanUtils.copyProperties(param, roleInfo);
+        this.roleInfoService.insert(roleInfo);
+        return Result.instance(Result.Code.MESSAGE_SUCCESS);
+    }
+
+    @Permission(value = "oauth.role.edit", tags = "更新系统角色")
+    @PostMapping("/updateById")
+    public Result updateById(Long id, @Valid @RequestBody SaveRoleParam param) {
+        if (id == null || id <= 0) {
+            return Result.instance("数据异常,请刷新页面后再试");
+        }
+        RoleInfo roleInfo = new RoleInfo();
+        BeanUtils.copyProperties(param, roleInfo);
+        roleInfo.setId(id);
+        this.roleInfoService.updateById(roleInfo);
+        return Result.instance(Result.Code.MESSAGE_SUCCESS);
+    }
+
+    @Permission(value = "oauth.role.delete", tags = "删除系统角色")
+    @PostMapping("/deleteByIds")
+    public Result deleteByIds(@RequestBody List<Long> ids) throws Exception {
+        if (ids.contains(1000L)) {
+            return Result.instance(Result.Code.MESSAGE_ERROR, "系统管理员不能删除");
+        }
+        this.roleInfoService.deleteByIds(ids);
+        return Result.instance(Result.Code.MESSAGE_SUCCESS);
+    }
+
+    @Permission(value = "oauth.role.authority", tags = "保存用户的权限值")
+    @PostMapping("/saveAuthorities")
+    public Result saveAuthorities(Long roleId, @RequestBody Long[] authorities) throws Exception {
+        this.roleInfoService.saveAuthorities(roleId, authorities);
+        return Result.instance(Result.Code.MESSAGE_SUCCESS);
+
+    }
+}

+ 188 - 0
src/main/java/com/zhiqiyun/open/manager/controller/UserInfoController.java

@@ -0,0 +1,188 @@
+package com.zhiqiyun.open.manager.controller;
+
+import com.zhiqiyun.open.annotation.Permission;
+import com.zhiqiyun.open.enmus.AccountType;
+import com.zhiqiyun.open.enmus.PasswordType;
+import com.zhiqiyun.open.enmus.UserState;
+import com.zhiqiyun.open.manager.Result;
+import com.zhiqiyun.open.manager.params.QueryUserParam;
+import com.zhiqiyun.open.manager.params.SaveUserParam;
+import com.zhiqiyun.open.models.RoleInfo;
+import com.zhiqiyun.open.models.UserBaseInfo;
+import com.zhiqiyun.open.models.UserLoginAccount;
+import com.zhiqiyun.open.mybatis.paginator.domain.PageResult;
+import com.zhiqiyun.open.service.OauthService;
+import com.zhiqiyun.open.service.UserBaseInfoService;
+import com.zhiqiyun.open.utils.RandomUtil;
+import com.zhiqiyun.open.utils.ServletContext;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author jtoms
+ */
+@RestController
+@RequestMapping("/user")
+public class UserInfoController {
+
+	@Autowired
+	private UserBaseInfoService userBaseInfoService;
+
+	@Autowired
+	private OauthService oauthService;
+
+	@Permission(value = "oauth.user.find", tags = "查询系统所有用户")
+	@PostMapping("/findPage")
+	public Result findRolePage(@RequestBody QueryUserParam params) {
+		PageResult<UserBaseInfo> resultData = this.userBaseInfoService.findPage(params.getPageBounds(), params.getQueryWrapper());
+
+		List<Map<String, Object>> listData = new ArrayList<>();
+		for (UserBaseInfo info : resultData.getData()) {
+			Map<String, Object> dataMap = new HashMap<>();
+			dataMap.put("id", info.getId());
+			dataMap.put("nickName", info.getNickName());
+			dataMap.put("registerTime", info.getRegisterTime());
+			dataMap.put("registerIp", info.getRegisterIp());
+			dataMap.put("avatar", info.getAvatar());
+			dataMap.put("state", info.getState());
+			dataMap.put("gender", info.getGender());
+			List<UserLoginAccount> loginAccounts = this.userBaseInfoService.findLoginAccounts(info.getId());
+			for (UserLoginAccount account : loginAccounts) {
+				dataMap.put(account.getAccountType().name(), account.getLoginAccount());
+			}
+			List<RoleInfo> listRoles = this.userBaseInfoService.findRoles(info.getId());
+			dataMap.put("listRoles", listRoles);
+
+			listData.add(dataMap);
+		}
+
+		Map<String, Object> dataMap = new HashMap<>();
+		dataMap.put("data", listData);
+		dataMap.put("total", resultData.getTotal());
+		dataMap.put("pageSize", resultData.getPageSize());
+		dataMap.put("current", resultData.getCurrent());
+		return Result.instance(Result.Code.SUCCESS).setData(dataMap);
+	}
+
+	@Permission(value = "oauth.user.add", tags = "添加用户")
+	@PostMapping("/save")
+	public Result save(@Valid @RequestBody SaveUserParam param) throws Exception {
+		return this.insertOrUpdate(null, param);
+	}
+
+	@Permission(value = "oauth.user.edit", tags = "修改用户")
+	@PostMapping("/updateById")
+	public Result updateById(Long id, @Valid @RequestBody SaveUserParam param) throws Exception {
+		if (id == null || id <= 0) {
+			return Result.instance("数据异常,请刷新页面后再试");
+		}
+		return this.insertOrUpdate(id, param);
+	}
+
+	private Result insertOrUpdate(Long uid, SaveUserParam param) throws Exception {
+		List<UserLoginAccount> loginAccounts = new ArrayList<>();
+		UserLoginAccount userNameAccount = this.userBaseInfoService.findLoginAccount(param.getUserName(), AccountType.userName);
+		if (userNameAccount != null && !userNameAccount.getUid().equals(uid)) {
+			return Result.instance("登录账号已被其他用户使用,请使更换登录账号");
+		}
+		loginAccounts.add(new UserLoginAccount(param.getUserName(), AccountType.userName));
+
+		if (StringUtils.isNotBlank(param.getMp())) {
+			UserLoginAccount mpAccount = this.userBaseInfoService.findLoginAccount(param.getMp(), AccountType.mp);
+			if (mpAccount != null && !mpAccount.getUid().equals(uid)) {
+				return Result.instance("手机号已被其他用户使用,请使更换手机号");
+			}
+			loginAccounts.add(new UserLoginAccount(param.getMp(), AccountType.mp));
+		}
+
+		if (StringUtils.isNotBlank(param.getEmail())) {
+			UserLoginAccount emailAccount = this.userBaseInfoService.findLoginAccount(param.getEmail(), AccountType.email);
+			if (emailAccount != null && !emailAccount.getUid().equals(uid)) {
+				return Result.instance("邮箱已被其他用户使用,请使更换邮箱");
+			}
+			loginAccounts.add(new UserLoginAccount(param.getEmail(), AccountType.email));
+		}
+
+		UserBaseInfo userInfo = new UserBaseInfo();
+		BeanUtils.copyProperties(param, userInfo);
+		userInfo.setId(uid);
+		userInfo.setRegisterIp(ServletContext.getRemoteIPAddress());
+		String loginPassword = this.userBaseInfoService.insertOrUpdate(userInfo, loginAccounts);
+		return Result.instance(Result.Code.MESSAGE_SUCCESS).setData(loginPassword);
+	}
+
+
+	@Permission(value = "oauth.user.disable", tags = "禁用用户")
+	@PostMapping("/disable")
+	public Result disable(@RequestBody List<Long> ids) {
+		if (ids.contains(1000L)) {
+			return Result.instance(Result.Code.MESSAGE_ERROR, "系统管理员不能禁用");
+		}
+		if (ids.isEmpty()) {
+			return Result.instance(Result.Code.MESSAGE_ERROR, "请选择要禁用的用户");
+		}
+		for (Long uid : ids) {
+			UserBaseInfo userInfo = new UserBaseInfo();
+			userInfo.setId(uid);
+			userInfo.setState(UserState.disable);
+			this.userBaseInfoService.updateById(userInfo);
+		}
+		return Result.instance(Result.Code.MESSAGE_SUCCESS);
+	}
+
+	@Permission(value = "oauth.user.enable", tags = "启用用户")
+	@PostMapping("/enabled")
+	public Result enabled(@RequestBody List<Long> ids) {
+		if (ids.isEmpty()) {
+			return Result.instance(Result.Code.MESSAGE_ERROR, "请选择要启用的用户");
+		}
+		for (Long uid : ids) {
+			UserBaseInfo userInfo = new UserBaseInfo();
+			userInfo.setId(uid);
+			userInfo.setState(UserState.normal);
+			this.userBaseInfoService.updateById(userInfo);
+		}
+		return Result.instance(Result.Code.MESSAGE_SUCCESS);
+	}
+
+	@Permission(value = "oauth.user.reset.login.password", tags = "重置用户密码")
+	@PostMapping("/resetLoginPassword")
+	public Result resetPasswd(@RequestBody Long[] ids) {
+		String password = RandomUtil.get(6);
+		for (Long uid : ids) {
+			String salt = RandomUtil.getSalt();
+			String hashPassword = this.oauthService.generatePassword(password, salt);
+			this.userBaseInfoService.updatePassword(uid, hashPassword, salt, PasswordType.login);
+		}
+		return Result.instance(Result.Code.MESSAGE_SUCCESS).setData(password);
+	}
+
+
+	@Permission(value = "oauth.user.set.role", tags = "设置用户角色")
+	@PostMapping("/saveRoles")
+	public Result saveRoles(Long uid, @RequestBody Long[] roles) throws Exception {
+		if (uid.equals(1000L)) {
+			return Result.instance(Result.Code.MESSAGE_ERROR, "默认的用户不能修改角色");
+		}
+		this.userBaseInfoService.saveRoles(uid, roles);
+		return Result.instance(Result.Code.MESSAGE_SUCCESS);
+	}
+
+	@Permission(tags = "搜索用户信息")
+	@PostMapping(value = "/findSelectAccounts")
+	public Result findSelectAccounts(String searchKey) {
+		List<Map<String, Object>> listData = this.userBaseInfoService.findSelectAccounts(searchKey);
+		return Result.instance(Result.Code.SUCCESS).setData(listData);
+	}
+}

+ 1 - 0
src/main/java/com/zhiqiyun/open/manager/package-info.java

@@ -0,0 +1 @@
+package com.zhiqiyun.open.manager;

+ 48 - 0
src/main/java/com/zhiqiyun/open/manager/params/ChangePasswordParam.java

@@ -0,0 +1,48 @@
+package com.zhiqiyun.open.manager.params;
+
+import com.zhiqiyun.open.utils.validation.StrongPassword;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author jtoms.shen
+ * @version 1.0
+ * @date 2018/12/21 11:35
+ */
+public class ChangePasswordParam {
+
+    @NotBlank(message = "请输入原始密码")
+    private String loginPassword;
+
+    @NotBlank(message = "请输入新密码")
+    @StrongPassword
+    private String newPassword;
+
+    @NotBlank(message = "请再次输入新密码")
+    @StrongPassword
+    private String confirmNewPassword;
+
+    public String getLoginPassword() {
+        return loginPassword;
+    }
+
+    public void setLoginPassword(String loginPassword) {
+        this.loginPassword = loginPassword;
+    }
+
+    public String getNewPassword() {
+        return newPassword;
+    }
+
+    public void setNewPassword(String newPassword) {
+        this.newPassword = newPassword;
+    }
+
+    public String getConfirmNewPassword() {
+        return confirmNewPassword;
+    }
+
+    public void setConfirmNewPassword(String confirmNewPassword) {
+        this.confirmNewPassword = confirmNewPassword;
+    }
+}

+ 27 - 0
src/main/java/com/zhiqiyun/open/manager/params/LoginParam.java

@@ -0,0 +1,27 @@
+package com.zhiqiyun.open.manager.params;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author jtoms.shen
+ * @version 1.0
+ * @date 2018/12/4 16:09
+ */
+@Data
+public class LoginParam {
+
+    @NotBlank(message = "请输入您的登录手机号或邮箱")
+    private String loginAccount;
+
+    @NotBlank(message = "请输入登录密码")
+    private String loginPassword;
+
+    @NotBlank(message = "参数异常,请刷新页面再试")
+    private String token;
+
+    @NotBlank(message = "请输入正确的验证码")
+    private String captcha;
+
+}

+ 58 - 0
src/main/java/com/zhiqiyun/open/manager/params/QueryPageParams.java

@@ -0,0 +1,58 @@
+package com.zhiqiyun.open.manager.params;
+
+import com.zhiqiyun.open.mybatis.paginator.domain.Order;
+import com.zhiqiyun.open.mybatis.paginator.domain.PageBounds;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author jtoms
+ */
+@Data
+@Slf4j
+public class QueryPageParams {
+    private Integer current = 1;
+    private Integer pageSize = 10;
+    private Map<String, String> sorter;
+
+    public PageBounds getPageBounds() {
+        PageBounds bounds = new PageBounds(this.getCurrent(), this.getPageSize());
+
+        List<Order> orders = new ArrayList<>();
+        if (this.sorter != null && this.sorter.size() > 0) {
+            for (String key : this.sorter.keySet()) {
+                String directionString = this.sorter.get(key);
+                directionString = directionString.replace("end", "");
+                orders.add(Order.create(key, Order.Direction.valueOf(directionString.toUpperCase())));
+            }
+        }
+
+        bounds.setOrders(orders);
+        return bounds;
+    }
+
+
+    public Map<String, Object> getQueryWrapper() {
+        Map<String, Object> result = new HashMap<>();
+
+        Class<?> sourceClass = this.getClass();
+        //拿到所有的字段,不包括继承的字段
+        Field[] sourceFiled = sourceClass.getDeclaredFields();
+        for (Field field : sourceFiled) {
+            try {
+                //设置可访问,不然拿不到private
+                field.setAccessible(true);
+                result.put(field.getName(), field.get(this));
+            } catch (Exception e) {
+                log.error("", e);
+            }
+        }
+        return result;
+    }
+}

+ 10 - 0
src/main/java/com/zhiqiyun/open/manager/params/QueryRoleParam.java

@@ -0,0 +1,10 @@
+package com.zhiqiyun.open.manager.params;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class QueryRoleParam extends QueryPageParams {
+    private String name;
+}

+ 30 - 0
src/main/java/com/zhiqiyun/open/manager/params/QueryUserParam.java

@@ -0,0 +1,30 @@
+package com.zhiqiyun.open.manager.params;
+
+import com.zhiqiyun.open.enmus.Gender;
+import com.zhiqiyun.open.enmus.UserState;
+import com.zhiqiyun.open.models.RangeDate;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+
+/**
+ * @author jtoms
+ */
+@Slf4j
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class QueryUserParam extends QueryPageParams {
+    private String nickName;
+    private String userName;
+    private UserState state;
+    private Gender gender;
+
+    private RangeDate registerTime;
+
+    public void setRegisterTime(List<String> registerTime) {
+        this.registerTime = RangeDate.build(registerTime);
+    }
+}
+

+ 15 - 0
src/main/java/com/zhiqiyun/open/manager/params/SaveRoleParam.java

@@ -0,0 +1,15 @@
+package com.zhiqiyun.open.manager.params;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Data
+public class SaveRoleParam {
+    private Long id;
+
+    @NotBlank(message = "请输入角色名称")
+    private String name;
+    @NotBlank(message = "请输入角色备注说明")
+    private String remark;
+}

+ 16 - 0
src/main/java/com/zhiqiyun/open/manager/params/SaveUserBaseInfoParam.java

@@ -0,0 +1,16 @@
+package com.zhiqiyun.open.manager.params;
+
+import com.zhiqiyun.open.enmus.Gender;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class SaveUserBaseInfoParam {
+    private String nickName;
+    private String avatar;
+    private String profile;
+    private List<String> citiesId;
+    private String address;
+    private Gender gender;
+}

+ 33 - 0
src/main/java/com/zhiqiyun/open/manager/params/SaveUserParam.java

@@ -0,0 +1,33 @@
+package com.zhiqiyun.open.manager.params;
+
+import com.zhiqiyun.open.enmus.Gender;
+import com.zhiqiyun.open.utils.validation.MobileNumber;
+import lombok.Data;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author jtoms.shen
+ * @version 1.0
+ * @date 2019/4/18 14:40
+ */
+@Data
+public class SaveUserParam {
+    @NotBlank(message = "用户昵称不能为空")
+    private String nickName;
+
+    @NotBlank(message = "用户登录账户不能为空")
+    private String userName;
+
+    @NotBlank(message = "用户手机号不能为空")
+    @MobileNumber(message = "请输入正确的手机号")
+    private String mp;
+
+    @Email(message = "请输入正确的邮箱")
+    private String email;
+
+    private String avatar;
+
+    private Gender gender = Gender.secret;
+}

+ 48 - 0
src/main/java/com/zhiqiyun/open/models/RangeDate.java

@@ -0,0 +1,48 @@
+package com.zhiqiyun.open.models;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.time.DateUtils;
+
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+@Data
+@Slf4j
+public class RangeDate implements Serializable {
+    private static final long serialVersionUID = 8140835909075449716L;
+    private Date statDate;
+    private Date endDate;
+
+    public static RangeDate build(List<String> listRangeDate) {
+
+        if (listRangeDate != null && listRangeDate.size() > 1) {
+            try {
+                RangeDate rangeDate = new RangeDate();
+                Calendar cal = new GregorianCalendar();
+                Date statDate = DateUtils.parseDate(listRangeDate.get(0), "yyyy-MM-dd HH:mm:ss");
+                cal.setTime(statDate);
+                cal.set(Calendar.HOUR_OF_DAY, 0);
+                cal.set(Calendar.MINUTE, 0);
+                cal.set(Calendar.SECOND, 0);
+                cal.set(Calendar.MILLISECOND, 0);
+                rangeDate.setStatDate(cal.getTime());
+
+                Date endDate = DateUtils.parseDate(listRangeDate.get(1), "yyyy-MM-dd HH:mm:ss");
+                cal.setTime(endDate);
+                cal.set(Calendar.HOUR_OF_DAY, 23);
+                cal.set(Calendar.MINUTE, 59);
+                cal.set(Calendar.SECOND, 59);
+                cal.set(Calendar.MILLISECOND, 999);
+                rangeDate.setEndDate(cal.getTime());
+                return rangeDate;
+            } catch (Exception e) {
+                log.warn("", e);
+            }
+        }
+        return null;
+    }
+}

+ 31 - 0
src/main/java/com/zhiqiyun/open/service/CaptchaService.java

@@ -0,0 +1,31 @@
+package com.zhiqiyun.open.service;
+
+public interface CaptchaService {
+    /**
+     * 生成一个Base64图片验证码
+     *
+     * @param token
+     * @return base64的字符串
+     */
+    String generateCaptchaBase64(String token);
+
+    /**
+     * 生成一个Base64图片验证码 自己定义高度与宽度
+     *
+     * @param token
+     * @param width
+     * @param height
+     * @return
+     */
+    String generateCaptchaBase64(String token, int width, int height);
+
+    /**
+     * 比较验证码
+     *
+     * @param captcha
+     * @param token
+     * @param destroySession
+     * @return
+     */
+    boolean compareCaptcha(String captcha, String token, boolean destroySession);
+}

+ 172 - 0
src/main/java/com/zhiqiyun/open/utils/ServletContext.java

@@ -0,0 +1,172 @@
+package com.zhiqiyun.open.utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Created on 2015年09月24
+ * <p>
+ * Title: Servlet上下文
+ * </p>
+ * <p>
+ * Description: 获取请求上下文的request,response等信息
+ * </p>
+ * <p>
+ * Copyright: Copyright (c) 2015
+ * </p>
+ *
+ * @author jtoms
+ * @version 1.0
+ */
+public class ServletContext {
+
+    private final static String[] PROXY_REMOTE_IP_ADDRESS = {"x-forwarded-for", "proxy-client-ip", "wl-proxy-client-ip", "x-real-ip"};
+    private final static String[] PROXY_ACCESS_TOKEN = {"x-header-access-token", "x-access-token"};
+    private final static String[] PROXY_FROM_SOURCE = {"x-header-from-source", "x-from-source"};
+
+    private final static ThreadLocal<ServletContext> CONTROLLER_CONTEXT = new ThreadLocal<>();
+
+    private final static String REQUEST_ID = "____REQUEST_ID____";
+    private final static String HTTP_SERVLET_REQUEST = "____HTTP_SERVLET_REQUEST____";
+    private final static String HTTP_SERVLET_RESPONSE = "____HTTP_SERVLET_RESPONSE____";
+    private final static String REMOTE_IP_ADDRESS = "____REMOTE_IP_ADDRESS____";
+    private final static String ACCESS_TOKEN = "____ACCESS_TOKEN____";
+    private final static String FORM_SOURCE = "____FORM_SOURCE____";
+
+    private final Map<String, Object> context;
+
+    public ServletContext(Map<String, Object> context) {
+        this.context = context;
+    }
+
+    /**
+     * Created on 2015-09-24
+     * <p>
+     * Description:初始化 该方法在ControllerFilter中调用其他地方不能调用
+     * </p>
+     */
+    public static void init(HttpServletRequest request, HttpServletResponse response) {
+        CONTROLLER_CONTEXT.set(new ServletContext(new HashMap<>()));
+        String requestId = UUID.randomUUID().toString();
+
+        CONTROLLER_CONTEXT.get().getContext().put(REQUEST_ID, requestId);
+        CONTROLLER_CONTEXT.get().getContext().put(HTTP_SERVLET_REQUEST, request);
+        CONTROLLER_CONTEXT.get().getContext().put(HTTP_SERVLET_RESPONSE, response);
+        CONTROLLER_CONTEXT.get().getContext().put(REMOTE_IP_ADDRESS, getRemoteIPAddress(request));
+        CONTROLLER_CONTEXT.get().getContext().put(ACCESS_TOKEN, getAccessToken(request));
+        CONTROLLER_CONTEXT.get().getContext().put(FORM_SOURCE, getFromSource(request));
+    }
+
+    public static void clean() {
+        CONTROLLER_CONTEXT.set(null);
+        CONTROLLER_CONTEXT.remove();
+    }
+
+    /**
+     * Created on 2015-09-24
+     * <p>
+     * Description:获取 request
+     * </p>
+     */
+    public static HttpServletRequest getRequest() {
+        return getValue(HTTP_SERVLET_REQUEST);
+    }
+
+    public static HttpServletResponse getResponse() {
+        return getValue(HTTP_SERVLET_RESPONSE);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> T getValue(String key) {
+        return (T) CONTROLLER_CONTEXT.get().getContext().get(key);
+    }
+
+    public static void set(String key, Object value) {
+        CONTROLLER_CONTEXT.get().getContext().put(key, value);
+    }
+
+    public static String getFromSource() {
+        return getValue(FORM_SOURCE);
+    }
+
+    public static String getRequestId() {
+        return getValue(REQUEST_ID);
+    }
+
+    public static String getRemoteIPAddress() {
+        return getValue(REMOTE_IP_ADDRESS);
+    }
+
+    public static String getAccessToken() {
+        return getValue(ACCESS_TOKEN);
+    }
+
+    private static String getAccessToken(HttpServletRequest request) {
+        return getHeaderValue(request, PROXY_ACCESS_TOKEN);
+    }
+
+    private static String getFromSource(HttpServletRequest request) {
+        return getHeaderValue(request, PROXY_FROM_SOURCE);
+    }
+
+    public static Map<String, String> getRequestParamMap() {
+        HttpServletRequest request = getRequest();
+        Enumeration<String> parameterNames = request.getParameterNames();
+
+        Map<String, String> dataMap = new HashMap<>();
+        while (parameterNames.hasMoreElements()) {
+            String key = parameterNames.nextElement();
+            String value = request.getParameter(key);
+            dataMap.put(key, value);
+        }
+        return dataMap;
+    }
+
+    private static String getRemoteIPAddress(HttpServletRequest request) {
+        String xforwardIp = getHeaderValue(request, PROXY_REMOTE_IP_ADDRESS);
+        if (StringUtils.isNotBlank(xforwardIp)) {
+            return getRemoteIpFromForward(xforwardIp);
+        } else {
+            return request.getRemoteHost();
+        }
+    }
+
+    private static String getHeaderValue(HttpServletRequest request, String[] nameArrays) {
+        String value = "";
+        for (String s : nameArrays) {
+            value = request.getHeader(s);
+            if (StringUtils.isNotBlank(value)) {
+                break;
+            }
+        }
+        return value;
+    }
+
+    /**
+     * <p>
+     * 从 HTTP Header 的 X-Forward-IP 头中截取客户端连接 IP 地址。如果经过多次反向代理, 在 X-Forward-IP
+     * 中获得的是以“,&lt;SP&gt;”分隔 IP 地址链,第一段为客户端 IP 地址。
+     * </p>
+     *
+     * @param xforwardIp
+     * @return
+     */
+    private static String getRemoteIpFromForward(String xforwardIp) {
+        int commaOffset = xforwardIp.indexOf(',');
+        if (commaOffset < 0) {
+            return xforwardIp;
+        }
+        return xforwardIp.substring(0, commaOffset);
+    }
+
+    private Map<String, Object> getContext() {
+        return context;
+    }
+
+}

+ 366 - 0
src/main/java/com/zhiqiyun/open/utils/Token.java

@@ -0,0 +1,366 @@
+package com.zhiqiyun.open.utils;
+
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.management.ManagementFactory;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * =============================================================================
+ * ==== name bit Byte number len max-len
+ * -----------------------------------------------------------------------------
+ * ---- mac 1 48 6 281,474,976,710,655 mac 2 49 562,949,953,421,311 9
+ * 11,694,146,092,834,140 ------ MAC RAN(12) * E15 + mac
+ * -----------------------------------------------------------------------------
+ * ---- time 0 13,054,752,000 (from 2011.05.16 100ms) time 1 28,833,984,000
+ * (from 2061.05.16 100ms) 7 3,142,742,836,021 ------ TIME RAN(32) * E11 + time
+ * -----------------------------------------------------------------------------
+ * ---- pid 16 2 65535 3 226,980 ------ PID pid
+ * -----------------------------------------------------------------------------
+ * ---- seq 26 67,108,864 5 844,596,300 ------ SEQ RAN(8) * E8 + seq
+ * -----------------------------------------------------------------------------
+ * ---- ip 32 4 4,294,967,296 6 51,520,374,361 ------ IP RAN(5) * E10 + ip
+ * =============================================================================
+ * ==== *
+ *
+ * @author jtoms
+ */
+@Slf4j
+public class Token {
+
+    private final static char[] BASE61 = "3ibqZmxNpXOjlHIPQVSTUhWCBgFDctuYAzKd1fenyE6957rwMaLJ2kR4vso8G".toCharArray();
+    private final static int LEN = BASE61.length;
+    private final static int DATA_LENGTH = 30;
+    private final static int TOKEN_LENGTH = 33;
+    private final static int[] BASE61_MAPPING = {32, 24, 23, 27, 41, 26, 60, 13, 14, 51, 34, 50, 48, 7, 10, 15, 16, 54, 18, 19, 20, 17, 22, 9, 31, 4, 49, 2, 28, 35, 38, 37, 25,
+            21, 1, 11, 53, 12, 5, 39, 58, 8, 3, 46, 57, 29, 30, 56, 47, 6, 40, 33, 36, 52, 0, 55, 44, 42, 45, 59, 43};
+
+    private final static long MAC_ADDR = mac();
+
+    private final static int PID = Integer.parseInt(ManagementFactory.getRuntimeMXBean().getName().split("@")[0]);
+
+    private final static AtomicInteger SEQ = new AtomicInteger((int) (System.currentTimeMillis() / 1000));
+
+    private final static AtomicInteger[] SEN = {new AtomicInteger((int) (System.nanoTime())), new AtomicInteger((int) (System.nanoTime())),
+            new AtomicInteger((int) (System.nanoTime())), new AtomicInteger((int) (System.nanoTime())), new AtomicInteger((int) (System.nanoTime()))};
+
+    private long mac;
+
+    private int pid;
+
+    private int sequence;
+
+    private long tokenTime;
+
+    private String clientIp;
+
+
+    public Token() {
+    }
+
+    public Token(String ipAddr) {
+        this.mac = MAC_ADDR;
+        this.tokenTime = System.currentTimeMillis() / 100;
+        this.pid = PID;
+        this.clientIp = ipAddr;
+        this.sequence = SEQ.incrementAndGet() & 0x3ffffff;
+    }
+
+    public Token(long mac, long tokenTime, int pid, int sequence, String clientIp) {
+        super();
+        this.mac = mac;
+        this.tokenTime = tokenTime;
+        this.pid = pid;
+        this.sequence = sequence;
+        this.clientIp = clientIp;
+    }
+
+    public static Token init(String ipAddr) {
+        return new Token(ipAddr);
+    }
+
+    public static boolean checkToken(String value) {
+        if (value == null || value.length() != Token.TOKEN_LENGTH) {
+            return false;
+        }
+        char[] chs = value.toCharArray();
+        for (int i = 0; i < TOKEN_LENGTH; i++) {
+            if (getNumber(chs[i]) < 0) {
+                log.warn("value index [" + i + "] is invalid encode character [" + chs[i] + "], token: [" + value + "]");
+                return false;
+            }
+        }
+        char[] hash = hash(chs, 0, DATA_LENGTH, TOKEN_LENGTH - DATA_LENGTH);
+        for (int i = DATA_LENGTH, j = 0; i < chs.length; i++, j++) {
+            if (chs[i] != hash[j]) {
+                log.warn("value hash is invalid, value hash: [" + new String(chs, DATA_LENGTH, TOKEN_LENGTH - DATA_LENGTH) + "], calculate hash: [" + new String(hash)
+                        + "], token: [" + value + "]");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static Token decodeTokenString(String value) {
+        if (log.isDebugEnabled()) {
+            log.debug("start check TOKEN_ID: " + value);
+        }
+        if (!Token.checkToken(value)) {
+            log.warn("check token failed");
+            return null;
+        }
+        char[] chs = value.toCharArray();
+        int offset = 0;
+        long mac = Token.decodeNode(chs, offset, 9, 1000000000000000L);
+        if (mac < 0) {
+            log.warn("token mac is invalid, mac: [" + new String(chs, 0, 9) + "]token: [" + value + "]");
+            return null;
+        }
+        long tokenTime = Token.decodeNode(chs, (offset += 9), 7, 100000000000L);
+        if (tokenTime < 0) {
+            log.warn("token time is invalid, tokenTime: [" + new String(chs, offset, 7) + "]token: [" + value + "]");
+            return null;
+        }
+        int pid = (int) Token.decodeNode(chs, (offset += 7), 3, 0);
+        if (pid < 0 || pid > 226980) {
+            log.warn("token pid is invalid, pid: [" + new String(chs, offset, 3) + "], token: [" + value + "]");
+            return null;
+        }
+        int sequence = (int) Token.decodeNode(chs, (offset += 3), 5, 100000000L);
+        if (sequence < 0) {
+            log.warn("token sequence is invalid, sequence: [" + new String(chs, offset, 5) + "], token: " + value);
+            return null;
+        }
+        long clientIp = Token.decodeNode(chs, (offset += 5), 6, 10000000000L);
+        if (clientIp < 0 || clientIp > 0xffffffffL) {
+            log.warn("token clientIp is invalid, clientIp: [" + new String(chs, offset, 6) + "], number client ip: [" + Long.toHexString(clientIp) + "], token: " + value);
+            return null;
+        }
+        return new Token(mac, tokenTime, pid, sequence, Token.toIpAddr(clientIp));
+    }
+
+    static char[] hash(char[] chs, int offset, int length, int hashLength) {
+        char[] hash = new char[hashLength];
+        int p = 0;
+        for (int i = offset, k = offset + length; i < k; i++) {
+            p = p * 31 + chs[i];
+        }
+        p &= Integer.MAX_VALUE;
+        for (int i = 0; i < hashLength; i++, p /= LEN) {
+            hash[i] = BASE61[p % LEN];
+        }
+        return hash;
+    }
+
+    static String encode(long n, int max) {
+        char[] chs = new char[max];
+        int k = 0;
+        while (n > 0) {
+            chs[k++] = BASE61[(int) (n % LEN)];
+            n /= LEN;
+        }
+        while (k < max) {
+            chs[k++] = BASE61[0];
+        }
+        return new String(chs);
+    }
+
+    static Token parse(String token) {
+        if (token == null || token.length() != TOKEN_LENGTH) {
+            return null;
+        }
+        char[] chs = token.toCharArray();
+        for (int i = 0; i < TOKEN_LENGTH; i++) {
+            if (getNumber(chs[i]) < 0) {
+                return null;
+            }
+        }
+        char[] hash = hash(chs, 0, DATA_LENGTH, TOKEN_LENGTH - DATA_LENGTH);
+        for (int i = DATA_LENGTH, j = 0; i < chs.length; i++, j++) {
+            if (chs[i] != hash[j]) {
+                log.warn("token: " + token + " hash error");
+                return null;
+            }
+        }
+        return parsetTokenString(chs);
+    }
+
+    private static int next(int mod, int offset) {
+        return (SEN[offset].incrementAndGet() & 0x7fffffff) % mod;
+    }
+
+    private static Token parsetTokenString(char[] chs) {
+        Token token = new Token();
+        int offset = 0;
+        token.mac = decodeNode(chs, offset, 9, 1000000000000000L);
+        token.tokenTime = (long) decodeNode(chs, (offset += 9), 7, 100000000000L);
+        token.pid = (int) decodeNode(chs, (offset += 7), 3, 0);
+        token.sequence = (int) decodeNode(chs, (offset += 3), 5, 100000000L);
+        token.clientIp = toIpAddr(decodeNode(chs, (offset += 5), 6, 10000000000L));
+        return token;
+    }
+
+    private static int encodeNode(char[] chs, int ran, long value, int offset, int len) {
+        return encodeNode(chs, ran, value, offset, len, 0, 0);
+    }
+
+    private static int encodeNode(char[] chs, int ran, long value, int offset, int len, int random, long randomBase) {
+        if (random > 0) {
+            value = (long) next(random, ran) * randomBase + value;
+        }
+        for (int i = 0; i < len; i++) {
+            chs[offset + i] = BASE61[(int) (value % LEN)];
+            value /= LEN;
+        }
+        return offset + len;
+    }
+
+    static long decode(String str) {
+        char[] chs = str.toCharArray();
+        long p = 0;
+        for (int i = chs.length - 1; i >= 0; i--) {
+            int k = getNumber(chs[i]);
+            if (k < 0) {
+                return -1;
+            }
+            p = p * LEN + k;
+        }
+        return p;
+    }
+
+    public static long decodeNode(char[] chs, int offset, int len, long randomBase) {
+        long n = 0;
+        for (int i = offset + len - 1; i >= offset; i--) {
+            int k = getNumber(chs[i]);
+            if (k < 0) {
+                return -1;
+            }
+            n = n * LEN + k;
+        }
+        return randomBase < 1 ? n : n % randomBase;
+    }
+
+    private static long parseIp(String ip) {
+        char[] chs = ip.toCharArray();
+        long t = 0;
+        int n = 0;
+        for (int i = 0; i < chs.length; i++) {
+            if (chs[i] == '.') {
+                t = (t << 8) | (n & 0xff);
+                n = 0;
+                continue;
+            }
+            if (chs[i] >= '0' && chs[i] <= '9') {
+                n = n * 10 + (chs[i] - '0');
+            }
+        }
+        if (n != 0) {
+            t = (t << 8) + (n & 0xff);
+        }
+        return t & 0xffffffffL;
+    }
+
+    static int getNumber(char c) {
+        if (c >= 'A' && c <= 'Z') {
+            return BASE61_MAPPING[c - 'A'];
+        }
+        if (c >= 'a' && c <= 'z') {
+            return BASE61_MAPPING[c - 'a' + 26];
+        }
+        if (c >= '1' && c <= '9') {
+            return BASE61_MAPPING[c - '1' + 52];
+        }
+        return -1;
+    }
+
+    public static long mac() {
+        byte[] bys = null;
+        try {
+            for (Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements(); ) {
+                byte[] mac = e.nextElement().getHardwareAddress();
+                if (mac != null && mac.length == 6) {
+                    bys = mac;
+                    break;
+                }
+            }
+        } catch (SocketException e) {
+            log.error("can not get local machine MAC address, generate random 47bit simulate MAC", e);
+        }
+        if (bys == null) {
+            bys = new byte[7];
+            new Random().nextBytes(bys);
+            bys[0] &= 1;
+        }
+        long mac = 0;
+        for (int i = 0; i < bys.length; i++) {
+            mac = mac * 256L + (bys[i] & 0xff);
+        }
+        mac &= 0x1ffffffffffffL;
+        log.info("current MAC number is: " + mac + ", HEX: " + Long.toHexString(mac));
+        return mac;
+    }
+
+    public static String toIpAddr(long ip) {
+        return ((ip >> 24) & 0xff) + "." + ((ip >> 16) & 0xff) + "." + ((ip >> 8) & 0xff) + "." + (ip & 0xff);
+    }
+
+    public long getMac() {
+        return mac;
+    }
+
+    public int getPid() {
+        return pid;
+    }
+
+    public int getSequence() {
+        return sequence;
+    }
+
+    public long getTokenTime() {
+        return tokenTime * 100L;
+    }
+
+    public String getClientIp() {
+        return clientIp;
+    }
+
+    public String toTokenString() {
+        int offset = 0;
+        char[] chs = new char[TOKEN_LENGTH];
+        int p = 0;
+        offset = encodeNode(chs, p++, mac, offset, 9, 12, 1000000000000000L);
+        offset = encodeNode(chs, p++, tokenTime, offset, 7, 32, 100000000000L);
+        offset = encodeNode(chs, p++, pid, offset, 3);
+        offset = encodeNode(chs, p++, sequence, offset, 5, 8, 100000000L);
+        offset = encodeNode(chs, p++, parseIp(clientIp), offset, 6, 5, 10000000000L);
+        char[] hash = hash(chs, 0, offset, TOKEN_LENGTH - DATA_LENGTH);
+        System.arraycopy(hash, 0, chs, offset, hash.length);
+        return new String(chs);
+    }
+
+    @Override
+    public String toString() {
+        return "Token [mac=" + mac + ", tokenTime=" + String.format("%tF %<tT", getTokenTime()) + ", pid=" + pid + ", sequence=" + sequence + ", clientIp=" + clientIp + "]";
+    }
+
+    public boolean equals(Token token) {
+        if (!this.getClientIp().equals(token.getClientIp())) {
+            return false;
+        }
+        if (this.getMac() != token.getMac()) {
+            return false;
+        }
+        if (this.getPid() != token.getPid()) {
+            return false;
+        }
+        if (this.getSequence() != token.getSequence()) {
+            return false;
+        }
+        return true;
+    }
+}

+ 40 - 0
src/main/java/com/zhiqiyun/open/utils/validation/DomainName.java

@@ -0,0 +1,40 @@
+package com.zhiqiyun.open.utils.validation;
+
+/**
+ * @author jtoms.shen
+ * @version 1.0
+ * @date 2018/12/20 13:32
+ */
+
+import com.zhiqiyun.open.utils.validation.constraints.DomainNameValidate;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Constraint(validatedBy = {DomainNameValidate.class})
+public @interface DomainName {
+    //默认错误消息
+    String message() default "手机号不合法";
+
+    //分组
+    Class<?>[] groups() default {};
+
+    //负载
+    Class<? extends Payload>[] payload() default {};
+
+    //指定多个时使用
+    @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
+    @Retention(RUNTIME)
+    @Documented
+    @interface List {
+        DomainName[] value();
+    }
+}

+ 40 - 0
src/main/java/com/zhiqiyun/open/utils/validation/MobileNumber.java

@@ -0,0 +1,40 @@
+package com.zhiqiyun.open.utils.validation;
+
+/**
+ * @author jtoms.shen
+ * @version 1.0
+ * @date 2018/12/20 13:32
+ */
+
+import com.zhiqiyun.open.utils.validation.constraints.MobileNumberValidate;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Constraint(validatedBy = {MobileNumberValidate.class})
+public @interface MobileNumber {
+    //默认错误消息
+    String message() default "手机号不合法";
+
+    //分组
+    Class<?>[] groups() default {};
+
+    //负载
+    Class<? extends Payload>[] payload() default {};
+
+    //指定多个时使用
+    @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
+    @Retention(RUNTIME)
+    @Documented
+    @interface List {
+        MobileNumber[] value();
+    }
+}

+ 40 - 0
src/main/java/com/zhiqiyun/open/utils/validation/StrongPassword.java

@@ -0,0 +1,40 @@
+package com.zhiqiyun.open.utils.validation;
+
+/**
+ * @author jtoms.shen
+ * @version 1.0
+ * @date 2018/12/20 13:32
+ */
+
+import com.zhiqiyun.open.utils.validation.constraints.StrongPasswordValidate;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Constraint(validatedBy = {StrongPasswordValidate.class})
+public @interface StrongPassword {
+    //默认错误消息
+    String message() default "密码必需同时包含字母与数字,长度不能小于6";
+
+    //分组
+    Class<?>[] groups() default {};
+
+    //负载
+    Class<? extends Payload>[] payload() default {};
+
+    //指定多个时使用
+    @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
+    @Retention(RUNTIME)
+    @Documented
+    @interface List {
+        StrongPassword[] value();
+    }
+}

+ 25 - 0
src/main/java/com/zhiqiyun/open/utils/validation/constraints/DomainNameValidate.java

@@ -0,0 +1,25 @@
+package com.zhiqiyun.open.utils.validation.constraints;
+
+import com.zhiqiyun.open.utils.PatternUtils;
+import com.zhiqiyun.open.utils.validation.DomainName;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+
+/**
+ * @author jtoms.shen
+ * @version 1.0
+ * @date 2018/12/20 13:40
+ */
+public class DomainNameValidate implements ConstraintValidator<DomainName, String> {
+    @Override
+    public void initialize(DomainName domainName) {
+    }
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext context) {
+        return StringUtils.isBlank(value) || PatternUtils.validExpression(value, PatternUtils.DOMAIN_NAME_EXPRESSION);
+    }
+}

+ 24 - 0
src/main/java/com/zhiqiyun/open/utils/validation/constraints/MobileNumberValidate.java

@@ -0,0 +1,24 @@
+package com.zhiqiyun.open.utils.validation.constraints;
+
+import com.zhiqiyun.open.utils.PatternUtils;
+import com.zhiqiyun.open.utils.validation.MobileNumber;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+/**
+ * @author jtoms.shen
+ * @version 1.0
+ * @date 2018/12/20 13:40
+ */
+public class MobileNumberValidate implements ConstraintValidator<MobileNumber, String> {
+    @Override
+    public void initialize(MobileNumber mobileNumber) {
+    }
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext context) {
+        return StringUtils.isBlank(value) || PatternUtils.validExpression(value, PatternUtils.MOBILE_EXPRESSION);
+    }
+}

+ 25 - 0
src/main/java/com/zhiqiyun/open/utils/validation/constraints/StrongPasswordValidate.java

@@ -0,0 +1,25 @@
+package com.zhiqiyun.open.utils.validation.constraints;
+
+import com.zhiqiyun.open.utils.PatternUtils;
+import com.zhiqiyun.open.utils.validation.StrongPassword;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+
+/**
+ * @author jtoms.shen
+ * @version 1.0
+ * @date 2018/12/20 13:40
+ */
+public class StrongPasswordValidate implements ConstraintValidator<StrongPassword, String> {
+    @Override
+    public void initialize(StrongPassword mobileNumber) {
+    }
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext context) {
+        return StringUtils.isBlank(value) || PatternUtils.validExpression(value, PatternUtils.STRONG_PASSWORD_EXPRESSION);
+    }
+}