java代碼優化方案1(後端代碼整理)

前言

把之前的一個代碼給優化和封裝,使得代碼看的更加得體。

本次修改的項目結構爲springboot

結構

1、優化返回結果集,用Result封裝,
2、在登錄註冊進行雙重保護,避免惡意攻擊和截取密碼。
3、JSR303及全局異常的控制
4、通用的key生成策略
5、通用的RedisService方法,集中的使用,避免代碼重複書寫

1、返回結果集的封裝

我們以前可能就是隻返回一個實體類或者一個map集合,而本次我們對其的一個掌控比較的隨意,而且大部分都是Map因爲我們同時要傳狀態碼和結果集。
我們如今就僅僅寫一個類來負責返回結果:
在這裏插入圖片描述
一個基本內容:

在這裏插入圖片描述

Result:

public class Result<T> {
	
	private int code;
	private String msg;
	private T data;
	
	/**
	 *  成功時候的調用
	 * */
	public static  <T> Result<T> success(T data){
		return new Result<T>(data);
	}
	
	/**
	 *  失敗時候的調用
	 * */
	public static  <T> Result<T> error(CodeMsg codeMsg){
		return new Result<T>(codeMsg);
	}
	
	private Result(T data) {
		this.data = data;
	}
	
	private Result(int code, String msg) {
		this.code = code;
		this.msg = msg;
	}
	
	private Result(CodeMsg codeMsg) {
		if(codeMsg != null) {
			this.code = codeMsg.getCode();
			this.msg = codeMsg.getMsg();
		}
	}
	
	
	public int getCode() {
		return code;
	}
	public void setCode(int code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
	public T getData() {
		return data;
	}
	public void setData(T data) {
		this.data = data;
	}
}

CodeMsg:

package com.javaxl.miaosha_02.result;

public class CodeMsg {
	
	private int code;
	private String msg;
	
	//通用的錯誤碼
	public static CodeMsg SUCCESS = new CodeMsg(0, "success");
	public static CodeMsg SERVER_ERROR = new CodeMsg(500100, "服務端異常");
	public static CodeMsg BIND_ERROR = new CodeMsg(500101, "參數校驗異常:%s");
	//登錄模塊 5002XX
	public static CodeMsg SESSION_ERROR = new CodeMsg(500210, "Session不存在或者已經失效");
	public static CodeMsg PASSWORD_EMPTY = new CodeMsg(500211, "登錄密碼不能爲空");
	public static CodeMsg MOBILE_EMPTY = new CodeMsg(500212, "手機號不能爲空");
	public static CodeMsg MOBILE_ERROR = new CodeMsg(500213, "手機號格式錯誤");
	public static CodeMsg MOBILE_NOT_EXIST = new CodeMsg(500214, "手機號不存在");
	public static CodeMsg PASSWORD_ERROR = new CodeMsg(500215, "密碼錯誤");
	
	
	//商品模塊 5003XX
	
	
	//訂單模塊 5004XX
	public static CodeMsg ORDER_NOT_EXIST = new CodeMsg(500400, "訂單不存在");
	
	//秒殺模塊 5005XX
	public static CodeMsg MIAO_SHA_OVER = new CodeMsg(500500, "商品已經秒殺完畢");
	public static CodeMsg REPEATE_MIAOSHA = new CodeMsg(500501, "不能重複秒殺");
	
	
	private CodeMsg( ) {
	}
			
	private CodeMsg( int code,String msg ) {
		this.code = code;
		this.msg = msg;
	}
	
	public int getCode() {
		return code;
	}
	public void setCode(int code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
	
	public CodeMsg fillArgs(Object... args) {
		int code = this.code;
		String message = String.format(this.msg, args);
		return new CodeMsg(code, message);
	}

	@Override
	public String toString() {
		return "CodeMsg [code=" + code + ", msg=" + msg + "]";
	}
	
	
}

ok,寫完之後,其實有兩個優點:
1、代碼的規範性,因爲Result的構造方法都是私有的,所以不能從其他類實列一個出乎我們掌控範圍之外的Result,我們只能調用Result裏面的方法,卻不能實列化,CodeMsg也是一樣我們只能調用方法或靜態變量,卻無法實列化。

2、代碼的可理解性,如果做得項目是前後端分離的項目,那麼就要考慮到前端人員是否看的明白,如果我們還是以前的返回一個Map,那麼前端就會表示意義不明確,此時的我們就可以準確規範:Result《Boolean/實體類/String》


2、防止惡意截取密碼

比如說登錄的時候,我們的一個登陸賬號和密碼是一般都是以明文的形式傳輸的,所以就很容易的被截取到,

所以我們的一個策略就是:
1、在前端進行加密傳輸
2、後端再次加密解析,完成登錄

前端加密:

在這裏插入圖片描述

後端加密驗證
在這裏插入圖片描述

login.html(因爲篇幅問題所以就不放一些js了,可以上網查詢前端md5加密)

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>登錄</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    
    <!-- jquery -->
    <script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
    <!-- bootstrap -->
    <link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}" />
    <script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script>
    <!-- jquery-validator -->
    <script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script>
    <script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script>
    <!-- layer -->
    <script type="text/javascript" th:src="@{/layer/layer.js}"></script>
    <!-- md5.js -->
    <script type="text/javascript" th:src="@{/js/md5.min.js}"></script>
    <!-- common.js -->
    <script type="text/javascript" th:src="@{/js/common.js}"></script>
    
</head>
<body>

<form name="loginForm" id="loginForm" method="post"  style="width:50%; margin:0 auto">

	<h2 style="text-align:center; margin-bottom: 20px">用戶登錄</h2>
	
	<div class="form-group">
       	<div class="row">
	       	<label class="form-label col-md-4">請輸入手機號碼</label>
	        <div class="col-md-5">
	        	<input id="mobile" name = "mobile" class="form-control" type="text" placeholder="手機號碼" required="true"  minlength="11" maxlength="11" />
	    	</div>
	    	<div class="col-md-1">
	    	</div>
    	</div>
    </div>
    
    <div class="form-group">
     		<div class="row">
		        <label class="form-label col-md-4">請輸入密碼</label>
		        <div class="col-md-5">
		        	<input id="password" name="password" class="form-control" type="password"  placeholder="密碼" required="true" minlength="6" maxlength="16" />
	       		</div>
      		</div>
	</div>
	
	<div class="row">
		     	<div class="col-md-5">
	       	 		<button class="btn btn-primary btn-block" type="reset" onclick="reset()">重置</button>
	       	 	</div>
	       	 	<div class="col-md-5">
	       	 		<button class="btn btn-primary btn-block" type="submit" onclick="login()">登錄</button>
	       	 	</div>
	 </div>
	 
</form>
</body>
<script>
function login(){
	$("#loginForm").validate({
        submitHandler:function(form){
             doLogin();
        }    
    });
}
function doLogin(){
	g_showLoading();
	
	var inputPass = $("#password").val();
	var salt = g_passsword_salt;
	var str = ""+salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) + salt.charAt(4);
	var password = md5(str);
	
	$.ajax({
		url: "/login/do_login",
	    type: "POST",
	    data:{
	    	mobile:$("#mobile").val(),
	    	password: password
	    },
	    success:function(data){
	    	layer.closeAll();
	    	if(data.code == 0){
	    		layer.msg("成功");
	    		window.location.href="/goods/to_list";
	    	}else{
	    		layer.msg(data.msg);
	    	}
	    },
	    error:function(){
	    	layer.closeAll();
	    }
	});
}
</script>
</html>

MiaoshaUserService

@Service
public class MiaoshaUserService {
	
	
	public static final String COOKI_NAME_TOKEN = "token";
	
	@Autowired
	MiaoshaUserDao miaoshaUserDao;
	
	@Autowired
	RedisService redisService;
	
	public MiaoshaUser getById(long id) {
		//取緩存
		MiaoshaUser user = redisService.get(MiaoshaUserKey.getById, ""+id, MiaoshaUser.class);
		if(user != null) {
			return user;
		}
		//取數據庫
		user = miaoshaUserDao.getById(id);
		if(user != null) {
			redisService.set(MiaoshaUserKey.getById, ""+id, user);
		}
		return user;
	}
	// http://blog.csdn.net/tTU1EvLDeLFq5btqiK/article/details/78693323
	
	

	public String login(HttpServletResponse response, LoginVo loginVo) {
		if(loginVo == null) {
			throw new GlobalException(CodeMsg.SERVER_ERROR);
		}
		String mobile = loginVo.getMobile();
		String formPass = loginVo.getPassword();
		//判斷手機號是否存在
		MiaoshaUser user = getById(Long.parseLong(mobile));
		if(user == null) {
			throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
		}
		//驗證密碼
		String dbPass = user.getPassword();
		String saltDB = user.getSalt();
		if(!PasswordHelper.checkCredentials(formPass, saltDB, dbPass)) {
			throw new GlobalException(CodeMsg.PASSWORD_ERROR);
		}
		//生成cookie
		String token	 = UUIDUtil.uuid();
		addCookie(response, token, user);
		return token;
	}
	
	private void addCookie(HttpServletResponse response, String token, MiaoshaUser user) {
		redisService.set(MiaoshaUserKey.token, token, user);
		Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token);
		cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
		cookie.setPath("/");
		response.addCookie(cookie);
	}

}

在這裏插入圖片描述

3、防止異常攻擊,跳過前端規則驗證

如果調過了前端規則驗證,那麼我們就對後臺也寫一個驗證,重點是對不法分子進行攔截。

如何跳過??
在這裏插入圖片描述
1、進行驗證:@valid
在這裏插入圖片描述
2、接收類裏面的定義註解:@IsMobile在這裏插入圖片描述
3、使用自己的一個驗證規則:IsMobileValidator.class
在這裏插入圖片描述
4、最後如果爲false則拋出異常
在這裏插入圖片描述

5、由全局異常捕捉異常:@ControllerAdvice

在這裏插入圖片描述

1、

  @DisableToken
    @RequestMapping("/do_login")
    @ResponseBody
    public Result<String> doLogin(HttpServletResponse response, @Valid LoginVo loginVo) {//在傳過來之前就需要驗證@valid
    	log.info(loginVo.toString());
    	//登錄
    	String token = userService.login(response, loginVo);
    	return Result.success(token);
    }

2、LoginVo

public class LoginVo {
	
	@NotNull
	@IsMobile
	private String mobile;//自己的寫的一個@IsMobile註解規則,在前端請求到達這裏的時候就已經判斷是否拋出異常
	
	@NotNull
	@Length(min=32)
	private String password;
	
	public String getMobile() {
		return mobile;
	}
	public void setMobile(String mobile) {
		this.mobile = mobile;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "LoginVo [mobile=" + mobile + ", password=" + password + "]";
	}
}

3、IsMobile



@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class })//調用了自己寫好的一個驗證規則
public @interface  IsMobile {
	
	boolean required() default true;


	String message() default "手機號碼格式錯誤";

	Class<?>[] groups() default { };

	Class<? extends Payload>[] payload() default { };
}

4、IsMobileValidator

public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {//自己寫的一個驗證規則

	private boolean required = false;
	
	public void initialize(IsMobile constraintAnnotation) {
		required = constraintAnnotation.required();
	}

	public boolean isValid(String value, ConstraintValidatorContext context) {
		if(required) {
			return ValidatorUtil.isMobile(value);
		}else {
			if(StringUtils.isEmpty(value)) {
				return true;
			}else {
				return ValidatorUtil.isMobile(value);
			}
		}
	}

}

5、GlobalException

public class GlobalException extends RuntimeException{

	private static final long serialVersionUID = 1L;
	
	private CodeMsg cm;
	
	public GlobalException(CodeMsg cm) {
		super(cm.toString());
		this.cm = cm;
	}

	public CodeMsg getCm() {
		return cm;
	}

}

GlobalExceptionHandler


@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
	@ExceptionHandler(value=Exception.class)
	public Result<String> exceptionHandler(HttpServletRequest request, Exception e){
		e.printStackTrace();
		if(e instanceof GlobalException) {//一般的錯誤異常會進入下面程序,比如我們自己寫的密碼錯誤
			GlobalException ex = (GlobalException)e;
			return Result.error(ex.getCm());
		}else if(e instanceof BindException) {//在驗證失敗的情況下就會進入下面程序,就是使用了@IsMobile後驗證失敗的
			BindException ex = (BindException)e;
			List<ObjectError> errors = ex.getAllErrors();
			ObjectError error = errors.get(0);
			String msg = error.getDefaultMessage();
			return Result.error(CodeMsg.BIND_ERROR.fillArgs(msg));
		}else {
			return Result.error(CodeMsg.SERVER_ERROR);
		}
	}
}

4、通用的key生成策略

先寫好一個固定key生成

在這裏插入圖片描述

在這裏插入圖片描述
KeyPrefix

public interface KeyPrefix {
		
	public int expireSeconds();
	
	public String getPrefix();
	
}

BasePrefix

public abstract class BasePrefix implements KeyPrefix{
	
	private int expireSeconds;
	
	private String prefix;
	
	public BasePrefix(String prefix) {//0代表永不過期
		this(0, prefix);
	}
	
	public BasePrefix( int expireSeconds, String prefix) {
		this.expireSeconds = expireSeconds;
		this.prefix = prefix;
	}
	
	public int expireSeconds() {//默認0代表永不過期
		return expireSeconds;
	}

	public String getPrefix() {
		String className = getClass().getSimpleName();
		return className+":" + prefix;
	}

}

例如:MiaoshaUserKey

public class MiaoshaUserKey extends BasePrefix{

	public static final int TOKEN_EXPIRE = 3600*24 * 2;
	private MiaoshaUserKey(int expireSeconds, String prefix) {
		super(expireSeconds, prefix);
	}
	public static MiaoshaUserKey token = new MiaoshaUserKey(TOKEN_EXPIRE, "tk");
	public static MiaoshaUserKey getById = new MiaoshaUserKey(0, "id");
}

5、集中使用RedisService中的方法

RedisConfig

@Component
@ConfigurationProperties(prefix="redis")
public class RedisConfig {
	private String host;
	private int port;
	private int timeout;//秒
	private String password;
	private int poolMaxTotal;
	private int poolMaxIdle;
	private int poolMaxWait;//秒
	public String getHost() {
		return host;
	}
	public void setHost(String host) {
		this.host = host;
	}
	public int getPort() {
		return port;
	}
	public void setPort(int port) {
		this.port = port;
	}
	public int getTimeout() {
		return timeout;
	}
	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public int getPoolMaxTotal() {
		return poolMaxTotal;
	}
	public void setPoolMaxTotal(int poolMaxTotal) {
		this.poolMaxTotal = poolMaxTotal;
	}
	public int getPoolMaxIdle() {
		return poolMaxIdle;
	}
	public void setPoolMaxIdle(int poolMaxIdle) {
		this.poolMaxIdle = poolMaxIdle;
	}
	public int getPoolMaxWait() {
		return poolMaxWait;
	}
	public void setPoolMaxWait(int poolMaxWait) {
		this.poolMaxWait = poolMaxWait;
	}
}

RedisPoolFactory

@Service
public class RedisPoolFactory {

	@Autowired
	RedisConfig redisConfig;
	
	@Bean
	public JedisPool JedisPoolFactory() {
		JedisPoolConfig poolConfig = new JedisPoolConfig();
		poolConfig.setMaxIdle(redisConfig.getPoolMaxIdle());
		poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());
		poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait() * 1000);
		JedisPool jp = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(),
				redisConfig.getTimeout()*1000, redisConfig.getPassword(), 0);
		return jp;
	}
	
}

RedisService

@Service
public class RedisService {

	@Autowired
	JedisPool jedisPool;

	/**
	 * 獲取當個對象
	 * */
	public <T> T get(KeyPrefix prefix, String key,  Class<T> clazz) {
		Jedis jedis = null;
		try {
			jedis =  jedisPool.getResource();
			//生成真正的key
			String realKey  = prefix.getPrefix() + key;
			String  str = jedis.get(realKey);
			T t =  stringToBean(str, clazz);
			return t;
		}finally {
			returnToPool(jedis);
		}
	}

	/**
	 * 設置對象
	 * */
	public <T> boolean set(KeyPrefix prefix, String key,  T value) {
		Jedis jedis = null;
		try {
			jedis =  jedisPool.getResource();
			String str = beanToString(value);
			if(str == null || str.length() <= 0) {
				return false;
			}
			//生成真正的key
			String realKey  = prefix.getPrefix() + key;
			int seconds =  prefix.expireSeconds();
			if(seconds <= 0) {
				jedis.set(realKey, str);
			}else {
				jedis.setex(realKey, seconds, str);
			}
			return true;
		}finally {
			returnToPool(jedis);
		}
	}

	/**
	 * 判斷key是否存在
	 * */
	public <T> boolean exists(KeyPrefix prefix, String key) {
		Jedis jedis = null;
		try {
			jedis =  jedisPool.getResource();
			//生成真正的key
			String realKey  = prefix.getPrefix() + key;
			return  jedis.exists(realKey);
		}finally {
			returnToPool(jedis);
		}
	}

	/**
	 * 刪除
	 * */
	public boolean delete(KeyPrefix prefix, String key) {
		Jedis jedis = null;
		try {
			jedis =  jedisPool.getResource();
			//生成真正的key
			String realKey  = prefix.getPrefix() + key;
			long ret =  jedis.del(key);
			return ret > 0;
		}finally {
			returnToPool(jedis);
		}
	}

	/**
	 * 增加值
	 * */
	public <T> Long incr(KeyPrefix prefix, String key) {
		Jedis jedis = null;
		try {
			jedis =  jedisPool.getResource();
			//生成真正的key
			String realKey  = prefix.getPrefix() + key;
			return  jedis.incr(realKey);
		}finally {
			returnToPool(jedis);
		}
	}

	/**
	 * 減少值
	 * */
	public <T> Long decr(KeyPrefix prefix, String key) {
		Jedis jedis = null;
		try {
			jedis =  jedisPool.getResource();
			//生成真正的key
			String realKey  = prefix.getPrefix() + key;
			return  jedis.decr(realKey);
		}finally {
			returnToPool(jedis);
		}
	}

	private <T> String beanToString(T value) {
		if(value == null) {
			return null;
		}
		Class<?> clazz = value.getClass();
		if(clazz == int.class || clazz == Integer.class) {
			return ""+value;
		}else if(clazz == String.class) {
			return (String)value;
		}else if(clazz == long.class || clazz == Long.class) {
			return ""+value;
		}else {
			return JSON.toJSONString(value);
		}
	}

	@SuppressWarnings("unchecked")
	private <T> T stringToBean(String str, Class<T> clazz) {
		if(str == null || str.length() <= 0 || clazz == null) {
			return null;
		}
		if(clazz == int.class || clazz == Integer.class) {
			return (T)Integer.valueOf(str);
		}else if(clazz == String.class) {
			return (T)str;
		}else if(clazz == long.class || clazz == Long.class) {
			return  (T)Long.valueOf(str);
		}else {
			return JSON.toJavaObject(JSON.parseObject(str), clazz);
		}
	}

	private void returnToPool(Jedis jedis) {
		if(jedis != null) {
			jedis.close();
		}
	}

}

後言

最難理解的還是那個驗證與全局異常處理上面,多看一看就行了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章