每次做完项目之后,自己想重新回顾一下以前写的代码,整理出一些东西,却发现如同看天书一般,头晕眼花,完全感觉不像自己的写的代码,辣眼睛,犹如下图
所以为了爱护本人的眼睛,所以觉得很有必要整理一下一些优化代码的套路…
首先说一个最重要的优化原则:代码优化是你觉得你代码很繁琐、阅读性很差的时候一定要马上优化,立刻马上,不管你现在有多忙,每天优化才叫重构,每年优化那叫重写
这个原则为什么重要?因为很多程序员会在写代码的时候说「先不优化了,等不忙的时候再优化」,然后……就没有然后了,我也是这样,所以就导致了大量捞比代码的产生
1、逻辑复杂的业务代码一定要有注释(可能你写的是爽了,后面维护你代码的人可能会想往你头上暴扣)
2、首先是变量名、方法名这些,命名一定要规范,千万别出现aa、bb这种命名,然后我们可以对我们的一些状态变量进行集中管理
这个什么意思呢,比如我们在项目中一个订单的状态,0代码已下单、1代表已付款、2代表交易中等等…这一大堆的状态代表数据。
可能前期我们写的时候印象很深刻,万一后期你要改动,又或者需求有变动?你确定你的一堆状态数字还记得吗
所以我们在项目开始初期就可以写一个工具类,来专门管理我们状态结果
比如
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, "密码错误");
//订单模块 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 + "]";
}
}
在实际开发中如果项目比较大,甚至可以分模块来管理,每一个模块都专门写一个工具类来管理你的状态代码
3、尽量避免重复代码
当你发现某些代码重复出现的次数一多,你就应该有想法把它们抽取出来进行优化了
比如我们在做前后端分离项目的时候,后端每一个方法都需要返回固定的Json格式,以前我们是这样干的,我们可能会封装一个JSON格式的工具类JsonData,里面有3个参数,第一个是返回码、第二个是消息提示、第三个是结果集
比如我下面的登录方法
public JsonData login(Staff staff){
JsonData jsonData = null;
Staff login = staffService.login(staff);
if(login != null){
//登录成功
jsonData = new JsonData(1,"欢迎管理员"+staff.getStaffName()+"",login);
}
else{
jsonData = new JsonData(0,"用户名或密码错误",login);
}
return jsonData;
}
然后我们发现我们每次都要重复写我们的状态码、消息提示这些东西,那么我们就可以想办法优化一下了,在固定的地方写好,我们调用就好了,我们用泛型T指定类型,成功就返回成功的类型,失败了返回失败的类型
package com.p2p.p2pstaff.config;
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;
}
}
我们就在全局状态管理类中添加我们的失败状态
//staff
public static CodeMsg STAFF_FAIL = new CodeMsg(0,"登录失败");
然后最终优化后的代码
public Result<Staff> login(Staff staff){
Staff login = staffService.login(staff);
if(login != null){
//登录成功
return Result.success(login);
} else{
return Result.error(CodeMsg.STAFF_FAIL);
}
}
后台md5加密相比大家是耳孰能详,我们的shiro等很多权限框架都用到了这一点,而在后台加密依然可能存在密码被截取的可能性。
想象你的密码在被加密前就已经被抓取到了那么加密还有什么用呢?也就是截取我们表单提交的内容,这个是有很多办法能够实现的,比如我们利用抓包工具等等,所以说密码一样存在泄漏的可能。
所以我们就有了在前台就先加密一次然后再提交到后台,这样就算截取到了也是我们加密后的密码了
所以我们需要在登录前进行密码处理
//获取我们输入的密码
var inputPass = $("#password").val();
/* var g_passsword_salt="1a2b3c4d" */
var salt = g_passsword_salt;
var str = ""+salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) + salt.charAt(4);
var password = md5(str);
前台加密完后进入后台,用我们加密过的密码进行二次加密,我们两次加密的密码都要存入数据库的,不然我们登录是无法验证的
后台取出我们需要认证的盐,然后用我们的shiro去认证密码,这个工具类就和我们以前shiro使用的验证的是一样的
/**
* 进行密码验证
*
* @param credentials 未加密的密码
* @param salt 盐
* @param encryptCredentials 加密后的密码
* @return
*/
public static boolean checkCredentials(String credentials, String salt, String encryptCredentials) {
return encryptCredentials.equals(createCredentials(credentials, salt));
}
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;
}
就这样两次加密就完成了
全局异常处理
如果系统发生了异常,不做统一异常处理,前端会给用户展示一大片看不懂的文字。做统一异常处理后当异常发生后可以给用户一个温馨的提示,不至于使用户满头雾水,所以一方面是为了更好的用户体验 如果不统一全局异常,服务端和前端在遇到异常的时候处理起来杂乱无章非常费力。所以另一方面是为了制定规范提高工作效率
我们这里也就是通过写一个全局异常处理类,来处理我们的运行异常,并给与相对应的提示,而不是返回500错误i西南西
package com.javaxl.miaosha_02.exception;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import com.javaxl.miaosha_02.result.CodeMsg;
import com.javaxl.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;
@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);
}
}
}
我们的信息提示类,也就是前面接收的封装全局信息的类,去继承我们的全局异常处理类,来返回错误提示信息
package com.javaxl.miaosha_02.exception;
import com.javaxl.miaosha_02.result.CodeMsg;
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;
}
}
JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。
此实现与 Hibernate ORM 没有任何关系。 JSR 303 用于对 Java Bean 中的字段的值进行验证。
Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中对表单提交的数据方便地验证。
我们大部分前台项目都是做了JS正则判断的代码,那么如果别人知道了你的请求地址,它是不是就能跳过你的js验证,直接去访问你的数据库的某一个方法呢?这当然是可以的,所以我们就需要用JSR303来处理这种请求
比如我们在注册的时候信息必须满足格式才能插入数据库,而果然跳过js验证,那么数据库就会多很多垃圾数据,这样肯定是不行的,所以我们就加了验证在后台
public Result<String> doLogin(HttpServletResponse response, @Valid LoginVo loginVo) {
log.info(loginVo.toString());
//登录
String token = userService.login(response, loginVo);
return Result.success(token);
}
我在外面登录的方法中加了自定义注解,验证格式是否正确,只有通过了验证才能访问方法,否则就进入异常处理
自定义注解的代码
package com.javaxl.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;
@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.javaxl.miaosha_02.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import com.javaxl.miaosha_02.util.ValidatorUtil;
import org.apache.commons.lang3.StringUtils;
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);
}
}
}
}
所以只要加了这个注解的就都会先进入验证才能访问数据库
所以我们最后直接通过错误格式并访问不了,而是进了我们的错误处理页面
通用key生成是个什么概念呢,也就相当于分组了,我们在项目中需要用到redis的地方肯定不止一个模块,肯定很多模块都需要用到redis,所以我们在存储的时候生成一个文件夹,然后每一个key的名字我们以固定的格式给它拼接上,就如下图效果
BasePrefix
我们通过放射获取类名,然后拼接上我们的prefix
package com.javaxl.miaosha_02.redis;
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生成策略
这也就是生成我们的prefix和规定我们的过期时间的类
最终我们生成看到了就是我们的ClassName+prefix所生成的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");
}
通用的Redis操作类
高并发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();
}
}
}
第一阶段:
《C语言程序与设计》
《c++进阶宝典》
《Swift入门与实践》
第二阶段:
《教你怎么不生气》
《老子》
《沉默的愤怒》
第三阶段:
《颈椎病康复指南》
《腰椎间盘突出日常护理》
《强迫症的自我恢复》
第四阶段: