代碼優化---拒絕寫死代碼

Controller 層返回值封裝

對返回結果封裝能夠有效的減少硬編碼。提升開發效率,方便維護。以前如果我們不對返回結構進行封裝就是直接 return 一個狀態碼啥的。實在不雅觀

Result
這是對返回結果封裝的一個類

package com.cpc.miaosha_02.result;

/**
 * 這是類是對返回結果進行處理的類主要的目的是同樣返回的結果
 * @param <T>
 */
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.cpc.miaosha_02.result;

import com.alibaba.fastjson.JSON;

/**
 * 對應返回轉態做統一管理,以利於後期維護
 */
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;
	}

	/**
	 * 對msg中的佔位符做處理
	 * @param args
	 * @return
	 */
	public CodeMsg fillArgs(Object... args) {
		int code = this.code;
		String message = String.format(this.msg, args);
		return new CodeMsg(code, message);
	}

	/**
	 * 使用例子
	 * @param args
	 */
	public static void main(String[] args) {
		Result result = Result.error(CodeMsg.SESSION_ERROR);
		System.out.println(JSON.toJSONString(result));
	}
	@Override
	public String toString() {
		return "CodeMsg [code=" + code + ", msg=" + msg + "]";
	}
	
	
}

簡單的使用一下我們封裝好的類:
成功結果集我們可以這樣返回

/**
 * 示例:使用封裝好的代碼來返回結果
 * @return
 */
@RequestMapping(value="/detail/{goodsId}")
@ResponseBody
public Result<GoodsDetailVo> test() {
	GoodsDetailVo vo = new GoodsDetailVo();
	//這裏就簡單的示例子一下就不訪問數據庫了 
	return  Result.success(vo);
}

如果操作過程中出現異常我們可以這樣做:

//這裏返回 在 CodeMsg 中定義好的常量,從根據上拒絕硬編碼

return  Result.error(CodeMsg.PASSWORD_ERROR); //假設發生了異常

PASSWORD_ERROR 在這裏代表的是 ,密碼錯誤、狀態碼是 500215
在這裏插入圖片描述

你可以根據自己的情況進行擴展

JSR303 及 全局異常處理

jsr303 後端數據驗證

JSR-303 是 JAVA EE 6 中的一項子規範,叫做 Bean Validation,官方參考實現是Hibernate Validator。
此實現與 Hibernate ORM 沒有任何關係。 JSR 303 用於對 Java Bean 中的字段的值進行驗證。
Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中對錶單提交的數據方便地驗證。
注:可以使用註解的方式進行驗證

詳請參考:https://www.ibm.com/developerworks/cn/java/j-lo-jsr303/index.html

全局異常處理
系統中Controller總會因爲一些事情發送異常,這個時候我們就要進行異常處理。
傳統的做法是使用 try/catch 來玩,項目中輸不起的 處理方法,那麼就會有無數的 try/catch 代碼。顯然這是非常 冗餘的 代碼也就成了搬磚。
這時候我們是否能通過一種同樣的方式來對這些異常做同樣處理,響應個客戶端呢?當然有就是 全局異常處理

我們對登錄的實體類加上了如下註解:

package com.cpc.miaosha_02.vo;

import com.cpc.miaosha_02.validator.IsMobile;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotNull;


public class LoginVo {


	@NotNull//不能爲空
	@IsMobile //必須是手機號碼格式 
	private String mobile;

	
	@NotNull
	@Length(min=32) //指定密碼的長度最小必須是 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 + "]";
	}
}

ps:IsMobile 這個是我們根據jsr303規範實現的自定義註解,是對jsr303的擴展。自定義代碼下面也會有。

在Controller 接受實體類的方法上加上 @Valid 這個註解就能實現驗證了:

@DisableToken
@RequestMapping("/do_login")
@ResponseBody
public Result<String> doLogin(HttpServletResponse response, @Valid LoginVo loginVo) {
	log.info(loginVo.toString());
	//登錄
	String token = userService.login(response, loginVo);
	return Result.success(token);
}

這就是基本的代碼實現驗證了,是不是和簡單,簡潔。下面我將剩餘的代碼附上:

這是我們自定義的註解實現驗證,不過要按照jsr303的套路來玩

package com.cpc.miaosha_02.validator;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;
//自定義註解類:message() + groups() + payload() 必須;
@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 { };
}

校驗器

package com.cpc.miaosha_02.validator;
import com.cpc.miaosha_02.util.ValidatorUtil;
import org.apache.commons.lang3.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
//註解校驗器類:繼承 ConstraintValidator 類<註解類,註解參數類型> + 兩個方法(initialize:初始化操作、isValid:邏輯處理)
//IsMobile:自定義的註解
//String:註解參數類型
public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {
	//默認值_false,用於接收註解上自定義的 required
	private boolean required = false;
	//1、初始化方法:通過該方法我們可以拿到我們的註解
	public void initialize(IsMobile constraintAnnotation) {
		//constraintAnnotation.required() 接收我們自定義的屬性,是否運行爲空 
		required = constraintAnnotation.required();
	}
	//2、邏輯處理
	public boolean isValid(String value, ConstraintValidatorContext context) {
		//判斷是否運行爲空 
		if(required) {
			return ValidatorUtil.isMobile(value);
		}else {
		   //2.2、不允許爲空
            //2.2.1、驗證是否爲空
			if(StringUtils.isEmpty(value)) {
				return true;
			}else {
				return ValidatorUtil.isMobile(value);
			}
		}
	}

}

效驗工具類

package com.cpc.miaosha_02.util;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;

public class ValidatorUtil {
	
	private static final Pattern mobile_pattern = Pattern.compile("1\\d{10}");
	
	public static boolean isMobile(String src) {
		if(StringUtils.isEmpty(src)) {
			return false;
		}
		Matcher m = mobile_pattern.matcher(src);
		return m.matches();
	}

}

自定義異常:

package com.cpc.miaosha_02.exception;


import com.cpc.miaosha_02.result.CodeMsg;

/**
 *自定義異常類 繼承  RuntimeException(運行時異常類) 我們平臺通過 GlobalExceptionHandler 去處理
 */
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;
	}

}

下面是全局異常處理類,處理所以請求過程中發送的異常,啓動也包括對效驗失敗的處理。接和上面代碼:

package com.cpc.miaosha_02.exception;

import com.cpc.miaosha_02.result.CodeMsg;
import com.cpc.miaosha_02.result.Result;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.List;


/**
 * 全局請求異常處理類
 */
@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) {//這是對 參數驗證錯誤異常的處理
			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);
		}
	}
}

下面我可以測試一下:
在這裏插入圖片描述

這就是jsr303效驗配和全局異常處理,是不是比以前硬編碼舒服多了

通用的key生成策略

這是爲裏最項目中生成到redis中的key做集中化管理。易於項目的維護

KeyPrefix 接口

package com.javaxl.miaosha_02.redis;

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

BasePrefix 基礎的 KeyPrefix 實現。所以同樣key配置的基類

package com.javaxl.miaosha_02.redis;

public abstract class BasePrefix implements KeyPrefix{

	//這是key的過期秒數設置
	private int expireSeconds;

	//這是key的前綴
	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;
	}


	/**
	 * 前綴;返回的是  class類名+prefix
	 * @return
	 */
	public String getPrefix() {
		String className = getClass().getSimpleName();
		return className+":" + prefix;
	}

}

MiaoshaUserKey 對應的也就是 MiaoshaUser key的生成策略

package com.javaxl.miaosha_02.redis;

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");
}

這裏配置了Goods對應的key生成方式

package com.javaxl.miaosha_02.redis;

public class GoodsKey extends BasePrefix{

	private GoodsKey(int expireSeconds, String prefix) {
		super(expireSeconds, prefix);
	}
	//下面是配置的兩個通用key生成方式
	public static GoodsKey getGoodsList = new GoodsKey(60, "gl");
	public static GoodsKey getGoodsDetail = new GoodsKey(60, "gd");
}

實用案例:

	/**
	 * QPS:1267 load:15 mysql
	 * 5000 * 10
	 * QPS:2884, load:5
	 *
	 * 利用redis緩存整個商品列表
	 * */
    @RequestMapping(value="/to_list", produces="text/html")
    @ResponseBody
    public String list(HttpServletRequest request, HttpServletResponse response, Model model, MiaoshaUser user) {
    	model.addAttribute("user", user);
    	//取緩存
    	String html = redisService.get(GoodsKey.getGoodsList, "", String.class);
    	if(!StringUtils.isEmpty(html)) {
    		return html;
    	}
    	List<GoodsVo> goodsList = goodsService.listGoodsVo();
    	model.addAttribute("goodsList", goodsList);
//    	 return "goods_list";
		IWebContext ctx = new WebContext(request,response,
    			request.getServletContext(),request.getLocale(), model.asMap() );
    	//手動渲染
    	html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);
    	if(!StringUtils.isEmpty(html)) {
    		redisService.set(GoodsKey.getGoodsList, "", html);
    	}
    	return html;
    }

在這裏插入圖片描述

生成的key這樣的:
在這裏插入圖片描述

通用的RedisService方法
避免出現重複的redis操作方法,讓操作redis變得簡單便捷

package com.javaxl.miaosha_02.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@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();
		 }
	}

}

實用方式就是這樣的直接注入實用:

@Autowired
RedisService redisService;

md5前臺後臺兩次加鹽加密
會有兩次加密:
一次:客戶端密碼加密,防止明文密碼被劫持
二次:服務端再加密一次

前臺加密要導入 的加油工具包:
鏈接:https://pan.baidu.com/s/1vR6T1CkZXGYoqJk5UssoeA
提取碼:q2bd
在這裏插入圖片描述

貼上前臺js加密代碼:

//鹽(這裏就不做隨機鹽了,有興趣的可以自行研究擴展)
var g_passsword_salt="1a2b3c4d"

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);
	//對前臺密碼進行md5加鹽加密
	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();
	    }
	});
}

貼上後臺代碼:

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;
}

這裏做的是登錄的,註冊就是將前臺加密後的密碼,再次加鹽加密。只不過加的是隨機鹽而已

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