1.框架
2.详细代码
package com.company.project.configurer;
import com.github.pagehelper.PageHelper; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import tk.mybatis.spring.mapper.MapperScannerConfigurer;
import javax.annotation.Resource; import javax.sql.DataSource; import java.util.Properties;
import static com.company.project.core.ProjectConstant.*;
/** * Mybatis & Mapper & PageHelper 配置 */ @Configuration public class MybatisConfigurer {
@Bean public SqlSessionFactory sqlSessionFactoryBean(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setTypeAliasesPackage(MODEL_PACKAGE);
//配置分页插件,详情请查阅官方文档 PageHelper pageHelper = new PageHelper(); Properties properties = new Properties(); properties.setProperty("pageSizeZero", "true");//分页尺寸为0时查询所有纪录不再执行分页 properties.setProperty("reasonable", "true");//页码<=0 查询第一页,页码>=总页数查询最后一页 properties.setProperty("supportMethodsArguments", "true");//支持通过 Mapper 接口参数来传递分页参数 pageHelper.setProperties(properties);
//添加插件 factory.setPlugins(new Interceptor[]{pageHelper});
//添加XML目录 ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); factory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml")); return factory.getObject(); }
@Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean"); mapperScannerConfigurer.setBasePackage(MAPPER_PACKAGE);
//配置通用Mapper,详情请查阅官方文档 Properties properties = new Properties(); properties.setProperty("mappers", MAPPER_INTERFACE_REFERENCE); properties.setProperty("notEmpty", "false");//insert、update是否判断字符串类型!='' 即 test="str != null"表达式内是否追加 and str != '' properties.setProperty("IDENTITY", "MYSQL"); mapperScannerConfigurer.setProperties(properties);
return mapperScannerConfigurer; }
}
|
package com.company.project.configurer;
import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List;
import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.company.project.core.Result; import com.company.project.core.ResultCode; import com.company.project.core.ServiceException; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/** * Spring MVC 配置 */ @Configuration public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
private final Logger logger = LoggerFactory.getLogger(WebMvcConfigurer.class); @Value("${spring.profiles.active}") private String env;//当前激活的配置文件
//使用阿里 FastJson 作为JSON MessageConverter @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); FastJsonConfig config = new FastJsonConfig(); config.setSerializerFeatures(SerializerFeature.WriteMapNullValue);//保留空的字段 //SerializerFeature.WriteNullStringAsEmpty,//String null -> "" //SerializerFeature.WriteNullNumberAsZero//Number null -> 0 // 按需配置,更多参考FastJson文档哈
converter.setFastJsonConfig(config); converter.setDefaultCharset(Charset.forName("UTF-8")); converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON_UTF8)); converters.add(converter); }
//统一异常处理 @Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) { exceptionResolvers.add(new HandlerExceptionResolver() { public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) { Result result = new Result(); if (e instanceof ServiceException) {//业务失败的异常,如“账号或密码错误” result.setCode(ResultCode.FAIL).setMessage(e.getMessage()); logger.info(e.getMessage()); } else if (e instanceof NoHandlerFoundException) { result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在"); } else if (e instanceof ServletException) { result.setCode(ResultCode.FAIL).setMessage(e.getMessage()); } else { result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员"); String message; if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s", request.getRequestURI(), handlerMethod.getBean().getClass().getName(), handlerMethod.getMethod().getName(), e.getMessage()); } else { message = e.getMessage(); } logger.error(message, e); } responseResult(response, result); return new ModelAndView(); }
}); }
//解决跨域问题 @Override public void addCorsMappings(CorsRegistry registry) { //registry.addMapping("/**"); }
//添加拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { //接口签名认证拦截器,该签名认证比较简单,实际项目中可以使用Json Web Token或其他更好的方式替代。 if (!"dev".equals(env)) { //开发环境忽略签名认证 registry.addInterceptor(new HandlerInterceptorAdapter() { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //验证签名 boolean pass = validateSign(request); if (pass) { return true; } else { logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}", request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));
Result result = new Result(); result.setCode(ResultCode.UNAUTHORIZED).setMessage("签名认证失败"); responseResult(response, result); return false; } } }); } }
private void responseResult(HttpServletResponse response, Result result) { response.setCharacterEncoding("UTF-8"); response.setHeader("Content-type", "application/json;charset=UTF-8"); response.setStatus(200); try { response.getWriter().write(JSON.toJSONString(result)); } catch (IOException ex) { logger.error(ex.getMessage()); } }
/** * 一个简单的签名认证,规则: * 1. 将请求参数按ascii码排序 * 2. 拼接为a=value&b=value...这样的字符串(不包含sign) * 3. 混合密钥(secret)进行md5获得签名,与请求的签名进行比较 */ private boolean validateSign(HttpServletRequest request) { String requestSign = request.getParameter("sign");//获得请求签名,如sign=19e907700db7ad91318424a97c54ed57 if (StringUtils.isEmpty(requestSign)) { return false; } List<String> keys = new ArrayList<String>(request.getParameterMap().keySet()); keys.remove("sign");//排除sign参数 Collections.sort(keys);//排序
StringBuilder sb = new StringBuilder(); for (String key : keys) { sb.append(key).append("=").append(request.getParameter(key)).append("&");//拼接字符串 } String linkString = sb.toString(); linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);//去除最后一个'&'
String secret = "Potato";//密钥,自己修改 String sign = DigestUtils.md5Hex(linkString + secret);//混合密钥md5
return StringUtils.equals(sign, requestSign);//比较 }
private String getIpAddress(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } // 如果是多级代理,那么取第一个ip为客户端ip if (ip != null && ip.indexOf(",") != -1) { ip = ip.substring(0, ip.indexOf(",")).trim(); }
return ip; } }
|
package com.company.project.core;
import org.apache.ibatis.exceptions.TooManyResultsException; import org.springframework.beans.factory.annotation.Autowired; import tk.mybatis.mapper.entity.Condition;
import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.util.List;
/** * 基于通用MyBatis Mapper插件的Service接口的实现 */ public abstract class AbstractService<T> implements Service<T> {
@Autowired protected Mapper<T> mapper;
private Class<T> modelClass; // 当前泛型真实类型的Class
public AbstractService() { ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass(); modelClass = (Class<T>) pt.getActualTypeArguments()[0]; }
public void save(T model) { mapper.insertSelective(model); }
public void save(List<T> models) { mapper.insertList(models); }
public void deleteById(Integer id) { mapper.deleteByPrimaryKey(id); }
public void deleteByIds(String ids) { mapper.deleteByIds(ids); }
public void update(T model) { mapper.updateByPrimaryKeySelective(model); }
public T findById(Integer id) { return mapper.selectByPrimaryKey(id); }
@Override public T findBy(String fieldName, Object value) throws TooManyResultsException { try { T model = modelClass.newInstance(); Field field = modelClass.getDeclaredField(fieldName); field.setAccessible(true); field.set(model, value); return mapper.selectOne(model); } catch (ReflectiveOperationException e) { throw new ServiceException(e.getMessage(), e); } }
public List<T> findByIds(String ids) { return mapper.selectByIds(ids); }
public List<T> findByCondition(Condition condition) { return mapper.selectByCondition(condition); }
public List<T> findAll() { return mapper.selectAll(); } }
|
package com.company.project.core;
import tk.mybatis.mapper.common.BaseMapper; import tk.mybatis.mapper.common.ConditionMapper; import tk.mybatis.mapper.common.IdsMapper; import tk.mybatis.mapper.common.special.InsertListMapper;
/** * 定制版MyBatis Mapper插件接口,如需其他接口参考官方文档自行添加。 */ public interface Mapper<T> extends BaseMapper<T>, ConditionMapper<T>, IdsMapper<T>, InsertListMapper<T> { }
|
package com.company.project.core;
/** * 项目常量 */ public final class ProjectConstant { public static final String BASE_PACKAGE = "com.company.project";//生成代码所在的基础包名称,可根据自己公司的项目修改(注意:这个配置修改之后需要手工修改src目录项目默认的包路径,使其保持一致,不然会找不到类)
public static final String MODEL_PACKAGE = BASE_PACKAGE + ".model";//生成的Model所在包 public static final String MAPPER_PACKAGE = BASE_PACKAGE + ".dao";//生成的Mapper所在包 public static final String SERVICE_PACKAGE = BASE_PACKAGE + ".service";//生成的Service所在包 public static final String SERVICE_IMPL_PACKAGE = SERVICE_PACKAGE + ".impl";//生成的ServiceImpl所在包 public static final String CONTROLLER_PACKAGE = BASE_PACKAGE + ".web";//生成的Controller所在包
public static final String MAPPER_INTERFACE_REFERENCE = BASE_PACKAGE + ".core.Mapper";//Mapper插件基础接口的完全限定名 }
|
package com.company.project.core;
import com.alibaba.fastjson.JSON;
/** * 统一API响应结果封装 */ public class Result<T> { private int code; private String message; private T data;
public Result setCode(ResultCode resultCode) { this.code = resultCode.code(); return this; }
public int getCode() { return code; }
public String getMessage() { return message; }
public Result setMessage(String message) { this.message = message; return this; }
public T getData() { return data; }
public Result setData(T data) { this.data = data; return this; }
@Override public String toString() { return JSON.toJSONString(this); } }
|
package com.company.project.core;
/** * 响应码枚举,参考HTTP状态码的语义 */ public enum ResultCode { SUCCESS(200),//成功 FAIL(400),//失败 UNAUTHORIZED(401),//未认证(签名错误) NOT_FOUND(404),//接口不存在 INTERNAL_SERVER_ERROR(500);//服务器内部错误
private final int code;
ResultCode(int code) { this.code = code; }
public int code() { return code; } }
|
package com.company.project.core;
/** * 响应结果生成工具 */ public class ResultGenerator { private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
public static Result genSuccessResult() { return new Result() .setCode(ResultCode.SUCCESS) .setMessage(DEFAULT_SUCCESS_MESSAGE); }
public static <T> Result<T> genSuccessResult(T data) { return new Result() .setCode(ResultCode.SUCCESS) .setMessage(DEFAULT_SUCCESS_MESSAGE) .setData(data); }
public static Result genFailResult(String message) { return new Result() .setCode(ResultCode.FAIL) .setMessage(message); } }
|
package com.company.project.core;
import org.apache.ibatis.exceptions.TooManyResultsException; import tk.mybatis.mapper.entity.Condition;
import java.util.List;
/** * Service 层 基础接口,其他Service 接口 请继承该接口 */ public interface Service<T> { void save(T model);//持久化 void save(List<T> models);//批量持久化 void deleteById(Integer id);//通过主鍵刪除 void deleteByIds(String ids);//批量刪除 eg:ids -> “1,2,3,4” void update(T model);//更新 T findById(Integer id);//通过ID查找 T findBy(String fieldName, Object value) throws TooManyResultsException; //通过Model中某个成员变量名称(非数据表中column的名称)查找,value需符合unique约束 List<T> findByIds(String ids);//通过多个ID查找//eg:ids -> “1,2,3,4” List<T> findByCondition(Condition condition);//根据条件查找 List<T> findAll();//获取所有 }
|
package com.company.project.core;
/** * 服务(业务)异常如“ 账号或密码错误 ”,该异常只做INFO级别的日志记录 @see WebMvcConfigurer */ public class ServiceException extends RuntimeException { public ServiceException() { }
public ServiceException(String message) { super(message); }
public ServiceException(String message, Throwable cause) { super(message, cause); } }
|
3.如有需要完整测试代码,请加QQ:2797205254 免费送