代码优化---拒绝写死代码

标签: 

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

这里做的是登录的,注册就是将前台加密后的密码,再次加盐加密。只不过加的是随机盐而已

原文链接:加载失败,请重新获取