企业中,需要知道以下几点:
项目搭建之初就会有的几个东西
1.拦截器:
(项目搭建之初的配置,不需要会写,能看懂,主要只会改里面的业务逻辑)
在没有使用权限框架时,就会使用原生的springmvc拦截器做拦截:
1.1配置自定义拦截器,实现HandlerInterceptor接口:
/**--------------------------------------------------------
登录检查拦截器
--------------------------------------------------------**/
@Component
public class LoginCheckInterceptor implements HandlerInterceptor{
/**--------------------------------------------------------
方法说明:方法执行前做登录检查
handler:就是当前要请求的方法
步骤说明:
1.从Session中获取Employee
2.如果为空,没登录,跳转登录页面
3.如果不为空,放行
--------------------------------------------------------**/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
1.2配置springmvc: 拦截器注入:
/**--------------------------------------------------------
针对web的配置
@Configuration:Spring的配置标签
--------------------------------------------------------**/
@Configuration
public class WebConfig implements WebMvcConfigurer{
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
/**--------------------------------------------------------
注册拦截器
--------------------------------------------------------**/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor)
//拦截所有资源
.addPathPatterns("/**")
//放行资源
.excludePathPatterns(
"/login",
"/login.html",
"/assets/**"
);
1.3以上是因为项目中没有使用权限框架做拦截,真实项目中会直接使用权限框架
外面市面上主流的 springboot框架使用权限框架有两种:apache shiro,Spring Security.
外面市面上主流的springcloud框架使用权限框架只有一种:OAuth2+JWT, oauth2其实也是对spring Security做的二次封装.
那么上面三种框架 apache shiro, Spring Security,OAuth2+JWT其实都是原生的框架,主要是中小企业为了快速开发而使用的.
真正的中大型企业,或者自主研发的项目 (ps:自主研发的项目就是该项目只属于公司,而不是小老板在外面接活帮别人做,或者外包,一般自主研发公司不分大小,都是从小做起来的,这个看老板对产品的定义) 都是自己封装的权限框架(手写拦截器Interceptor,或者过滤器Firelt),手写,因为每个机构对应的权限力度都不一样,目前市面上的框架拿去做权限,都不太好用,或者说很难用(难上手)
2.全局异常捕获器:
(项目搭建之初的配置,不需要会写,能看懂,主要只会改里面的业务逻辑)
2.1其实就是通过aop实现的,回忆一下aop的 几个方法,其中有一个@After throwing注解,就是用来捕获异常的,
全局异常捕获器使用,就是避免了在写代码时平凡的去try catch,
2.2使用方式:定义一个class类,打上注解 @ControllerAdvice
再自定义一个方法,在方法上打上注解@ExceptionHandler(XXXException.class)
@ExceptionHandler 该注解中的参数就是你们抛出的异常类,例如:Exception.class
package com.app;
import java.io.IOException;
import java.io.PrintWriter;import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MaxUploadSizeExceededException;/**
- ContorllerAdvice 最常见的使用场景是全局异常处理
- 一般搭配 @ExceptionHandler @ModelAttribute 以及 @InitBinder 使用
- 如下是, 当单个文件超出最大size时 对应的自定义处理方法
- all Controllers.
- spring:
servlet:
multipart:
max-file-size: 50KB
*/
@ControllerAdvice
public class CustomExceptionHandler {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public void uploadException(MaxUploadSizeExceededException e, HttpServletResponse resp) throws IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("文件大小超出限制!");
out.flush();
out.close();
}
}
具体理解可百度:https://www.cnblogs.com/luffystory/p/12010494.html
根据该案例可详细理解作用.
3.全局参数校验:
3.1全局参数校验就是指我不在需要判断从前端传入的参数到Controller层时,还需要手动判断,该参数或者该对象是否等于null,
或者根据业务判断是否是邮箱 还是手机号,还是说长度超长,直接通过注解判断
以前我们需要这样判断前端传入的参数:
3.2使用方式:
3.2.1先在pom.xml项目中引入 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
3.2.2然后开始在实体类对象上打上对应注解:
3.2.3有且不仅有以下对参数做校验的注解: 以下参数都是作用在实体类也就是你们写的domain对象上
3.2.4 domain对象中使用该注解后,还需要在Controller层的参数入口打上注解@valida注解,
下面的例子使用的是@validated 注解,该注解还可以使用分组,在这不做过多说明,详情可
百度:该链接会比较简单直观,适合新手上手
4.全局事务处理器:
4.1全局事务管理使用:@Transactional
一般该注解会作用于service层,那么作用service层原因在与,一般业务都会在这一层,所以容易报错,或者抛出异常,
出现异常就会出现事务问题:
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import cn.qlq.bean.user.User;import cn.qlq.mapper.user.UserMapper;
import cn.qlq.service.user.UserService;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public void addUser(User user) {
userMapper.insert(user);
int i = 1 / 0;
}
}
以上代码做新增操作时,明显就报错了,那么该条sql就不应该执行,所以需要加上@Transactional 注解,来保证数据库不会因为逻辑问题导致存入脏数据,
上面例子不够直观,打个比方,做员工修改 userMapper.update(user);此时我在员工修改之前,改变了他的密码,但是由于我们代码操作失误导致抛出异常,该修改就应该回滚(rollback)而不应该提交(commit)
5:自定义日志:
5.1这个地方有的同学会费解,为什么需要自定义日志,明明控制台就可以打印日志,配合lombok使用 log.info()就能很好的打印日志,
那么为何需要自定义日志?
这里需要注意,今后真实开发,都是直接将项目打包到服务器,也就是linux操作系统的,你不可能去服务器上在去查看控制台日志,当然也可以将日志打印成一个xx.log的文件,但是这样查看也很麻烦,
真实企业会手动封装 ,通过aop 或者过滤器来做日志打印,将需要的信息,存入mysql数据库,方便后续线上出现问题排查
5.2如果使用aop,可以先自定义注解,或者看情况直接将切面定义层所有Controller,这个需要根据业务需要,在此我先演示自定义注解玩法,
帮助回顾自定义注解作用:
/**
* 自定义注解LogAop,正常情况下注解没有任何实际含义,一般都会配合Filter 或者aop使用
*/
@Documented
//@Target({ElementType.TYPE}) 标识该注解作用在哪个范围,type表示在类,或者接口
@Target({
ElementType.TYPE})
public @interface LogAop {
}
mysql中创建一张log表:
/*
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 50525
Source Host : localhost:3306
Source Schema : rbac
Target Server Type : MySQL
Target Server Version : 50525
File Encoding : 65001
Date: 08/01/2022 19:58:21
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for log
-- ----------------------------
DROP TABLE IF EXISTS `log`;
CREATE TABLE `log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '当前访问的url',
`ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '当前用户ip',
`aclass` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '当前调用的类',
`method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '当前调用的方法',
`args` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '当前传入的参数',
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '当前访问的用户id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
简单版aop:该aop在获取参数时只能拿到对象的地址值:
//@Aspect 开启切面
//@Component 交给spring容器管理
@Aspect
@Component
@Slf4j
public class MyLogAop {
@Autowired
LogMapper logMapper;
@Pointcut("@annotation(myLog)")
public void cut(MyLog myLog){
}
// 在方法之前执行
@Before("cut(Object)")
public void before(JoinPoint joinPoint){
//RequestContextHolder 从tomcat里面拿到 ServletRequest 拿 HttpServletRequest
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
//拿到对应的url
String url = request.getRequestURI();
//拿到对应的ip
String ip = request.getRemoteHost();
String remoteAddr = request.getRemoteAddr();
//拿到所有参数的名称
// Enumeration<String> attributeNames = request.getAttributeNames();
// //获取所有的参数
// StringBuilder stringBuilder = new StringBuilder();
//
// while (attributeNames.hasMoreElements()){
// stringBuilder.append("参数名:")
// .append(attributeNames.nextElement())
// .append("参数值:")
// .append(request.getAttribute(attributeNames.nextElement()));
// }
// String s = stringBuilder.toString();
Object[] argss = joinPoint.getArgs();
String args = argss.toString();
//获取对应的类以及方法
Object target = joinPoint.getTarget();
Class<?> aClass = target.getClass();
//获取当前类
String typeName = aClass.getTypeName();
//获取当前方法
String method = joinPoint.getSignature().getName();
log.info("获取到当前的ip:{},获取到当前的url:{}",ip,url);
//获取session
HttpSession session = request.getSession();
Employee employee = (Employee) session.getAttribute(Constant.USER_SESSION);
Long id = 0L;
if (Objects.nonNull(employee)){
id = employee.getId();
}
logMapper.insert(new Log(url,ip,typeName,method,args,id));
}
}
下面是通过aop 去完成 ,该aop可以将所有参数以及返回值全部打印出来
@Aspect
@Component
@Log4j
@Order(0)
public class LogAop {
@Autowired
LogEntityMapper logEntityMapper;
private static String[] types = {
"java.lang.Integer", "java.lang.Double",
"java.lang.Float", "java.lang.Long", "java.lang.Short",
"java.lang.Byte", "java.lang.Boolean", "java.lang.Char",
"java.lang.String", "int", "double", "long", "short", "byte",
"boolean", "char", "float" };
@AfterReturning(value = "@annotation(around)")
public void log(JoinPoint joinPoint,LogAnnotation around)throws Throwable{
//获取request
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = servletRequestAttributes.getRequest();
//获取当前类
UserInfo user = TokenKit.getUser (request);
String classType = joinPoint.getTarget().getClass().getName();
Class<?> clazz = Class.forName(classType);
String clazzName = clazz.getName();
String methodName = joinPoint.getSignature().getName();
String[] paramNames = getFieldsName(this.getClass(), clazzName, methodName);
LogAnnotation.Logtype actionvalue = around.actionvalue ();
String actiondese = around.actiondese ();
String returnValue = getReturnValue(paramNames, joinPoint);
String ipAddress = ZhaolaobaoIPUtil.getIPAddress (request);
String requestURI = request.getRequestURI ();
//最后返回目标方法
LogEntity logEntity = new LogEntity (user.getId (), actiondese,returnValue, ipAddress, requestURI);
logEntityMapper.insertSelective (logEntity);
log.info("logaop切面执行结束");
}
private static String getReturnValue(String[] paramNames, JoinPoint joinPoint){
String typeName = null;
Object[] args = joinPoint.getArgs();
StringBuilder sb = new StringBuilder();
boolean clazzFlag = true;
for(int k=0; k<args.length; k++){
Object arg = args[k];
sb.append(paramNames[k]+" ");
// 获取对象类型
if (arg==null){
arg="null";
}else{
typeName= arg.getClass().getTypeName();}
for (String t : types) {
if (t.equals(typeName)) {
sb.append("=" + arg+"; ");
}
}
if (clazzFlag) {
sb.append(getFieldsValue(arg));
}
}
return sb.toString();
}
/**
* 得到参数的值
* @param obj
*/
public static String getFieldsValue(Object obj) {
Field[] fields = obj.getClass().getDeclaredFields();
String typeName = obj.getClass().getTypeName();
for (String t : types) {
if(t.equals(typeName)) {
return "";
}
}
StringBuilder sb = new StringBuilder();
sb.append("【");
for (Field f : fields) {
f.setAccessible(true);
try {
for (String str : types) {
if (f.getType().getName().equals(str)){
sb.append(f.getName() + " = " + f.get(obj)+"; ");
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
sb.append("】");
return sb.toString();
}
/**
* 得到方法参数的名称
* @param cls
* @param clazzName
* @param methodName
* @return
* @throws
*/
private static String[] getFieldsName(Class cls, String clazzName, String methodName) throws NotFoundException {
ClassPool pool = ClassPool.getDefault();
//ClassClassPath classPath = new ClassClassPath(this.getClass());
ClassClassPath classPath = new ClassClassPath(cls);
pool.insertClassPath(classPath);
CtClass cc = pool.get(clazzName);
CtMethod cm = cc.getDeclaredMethod(methodName);
MethodInfo methodInfo = cm.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
if (attr == null) {
// exception
}
String[] paramNames = new String[cm.getParameterTypes().length];
int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
for (int i = 0; i < paramNames.length; i++){
paramNames[i] = attr.variableName(i + pos); //paramNames即参数名
}
return paramNames;
}
}
6.自定义过滤器
通过注解@WebFilter(“/*”) 定义一个类成为一个Filter,并且需要去实现filter的方法,
实现过滤器的逻辑,其实本质就是拦截器intercept,都是用来做一些权限校验以及静态资源放行的
/**
* 登录过滤器
* @version 1.0
* 文件名称:LoginFilter.java
* 类说明:过滤不符合要求的请求,使请求跳转到登录页面
*/
@Configuration
@WebFilter("/*")
@Slf4j
public class LoginFilter implements Filter {
// 白名单 url 集合,符合条件的请求不需要登录也可以访问目标资源
private List<String> whiteUrlList = new ArrayList<String>();
/**
* 初始化方法
* @方法名: init
* @方法说明: 项目启动时会执行该方法,初始化处理写在这个方法中
* @参数 @param filterConfig
* @参数 @throws ServletException
* @创建时间: 2018年6月8日
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("登陆过滤器初始化");
// 添加白名单
whiteUrlList.add("/resource/");
whiteUrlList.add("/login");
whiteUrlList.add("/index");
whiteUrlList.add("/register");
//支付 回调以及 后期需要添加 支付成功 或者失败页面
whiteUrlList.add("/wxpay");
whiteUrlList.add("/wxpaynotify");
whiteUrlList.add("/alipayNotify");
whiteUrlList.add("/order/my/wxpaysuccess");
whiteUrlList.add("/order/my/wxpayfail");
whiteUrlList.add("/order/my/alipaysuccess");
whiteUrlList.add("/alirefund");
whiteUrlList.add("/wxrefund");
}
/**
* filter处理方法
* @方法名: doFilter
* @方法说明: 每次请求都会执行,在方法中判断用户是否可用访问目标资源
* @参数 @param request 请求
* @参数 @param response 响应
* @参数 @param chain
* @参数 @throws IOException
* @参数 @throws ServletException
* @创建时间: 2018年6月8日
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 判断是否在白名单列表中,如果在就直接放行
String requestURI = httpRequest.getRequestURI();
log.info("验证 requestURI = " + requestURI);
for (String whiteUrl : whiteUrlList) {
if (requestURI.indexOf(whiteUrl) >= 0) {
log.info("属于白名单请求 " + whiteUrl + " ,验证通过");
chain.doFilter(request, response);
return;
}
}
// 不在白名单列表中的请求需要验证,判断是否登陆,如果已经登陆直接放行
HttpSession session = httpRequest.getSession();
Object user = session.getAttribute(GlobalConstants.SessionKey.CURRENT_LOGIN_USER);
log.info("验证 session 中的用户 = " + user);
if (user != null) {
chain.doFilter(request, response);
return;
}
// 登录验证不通过,跳转到登录页面
log.info("未通过登录验证,跳转到登录页面");
httpResponse.sendRedirect("/login");
}
/**
* 销毁方法
* @方法名: destroy
* @方法说明:销毁时执行
* @参数
*/
@Override
public void destroy() {
log.info("登陆过滤器被销毁");
}
}
7:关于第三方的一些插件;
7.1lombok
7.2pageHpler
7.3hutool工具类
8.关于db(数据库层面)
8.1:表设计三范式
8.2表设计字段命名规范
8.3索引:聚簇索引与非聚簇索引
8.4表中固定几个字段:
id ———索引
create_time(gmt_create)——创建时间
update_time(gmt_update)——更新时间
status(state)———状态
del——————-逻辑删除字段
8.5逻辑删除&物理删除
物理删除:直接使用 delete from table 删除表中的某一行 或者多行数据
逻辑删除:在删除时只会做update操作, 整体应该是 update table set del = 0 ,将del逻辑删除字段从1改为0
注意:数据库中,0代表false ,1代表true
真实企业开发,很少使用 物理删除,就是直接使用 delete sql去删
还没有评论,来说两句吧...