当前位置: 首页 > article >正文

spring boot工程集成jwt 鉴权步骤

#spring boot工程集成jwt 鉴权步骤

1、pom.xml依赖

        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.1</version>
		</dependency>

2、JwtUtils.java



import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

public class JwtUtils {
    private static final String SECRET_KEY = "rBSnM5GAB0T7M6mZ8Yg";//your_secret_key
    private static final long EXPIRATION_TIME = 86400000; // 1 day

//    private static final long EXPIRATION_TIME = 3000;
    public static String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }

    /** 设置token失效*/
    public static void setTokenExpired(String token) {
          getClaimsFromToken(token).setExpiration(new Date(System.currentTimeMillis()));
//        getClaimsFromToken(token).clear();
    }
    public static Claims getClaimsFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
    }

    public static boolean isTokenExpired(String token) {
        return getClaimsFromToken(token).getExpiration().before(new Date());
    }
}

3、JwtAuthenticationFilter.java



import com.ewaycloud.jw.common.core.util.JwtUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;

public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }
        String token = header.substring(7);
        UsernamePasswordAuthenticationToken authentication = getAuthentication(token);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(String token) {
        if (token != null && !JwtUtils.isTokenExpired(token)) {
            String user = JwtUtils.getClaimsFromToken(token).getSubject();
            if (user != null) {
                return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
            }
        }
        return null;
    }
}

4、WebGlobalInterceptor.java





import com.ewaycloud.jw.common.core.config.CoreCommonProperties;
import com.ewaycloud.jw.common.core.config.MyCacheManager;
import com.ewaycloud.jw.common.core.util.JwtUtils;
import com.ewaycloud.jw.common.core.util.SpringContextHolder;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;

/**
 * WEB全局拦截器
 *
 *
 * @date 2022/8/25
 */
@Slf4j
@RequiredArgsConstructor
public class WebGlobalInterceptor implements HandlerInterceptor {

	private final ObjectMapper objectMapper;
	private final CoreCommonProperties coreCommonProperties;

	private final CacheManager cacheManager = SpringContextHolder.getBean(CacheManager.class);

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {


		String header = request.getHeader("Authorization");
		if (header == null || !header.startsWith("Bearer ")) {
			return false;
		}
		String token = header.substring(7);
		if(token == null || JwtUtils.isTokenExpired(token)){
			return false;
		}
		/**从缓存中获取token, 用户主动退出后清除*/
		if(null ==MyCacheManager.get(token)){
			return false;
		}
		UsernamePasswordAuthenticationToken authentication = getAuthentication(token);
		SecurityContextHolder.getContext().setAuthentication(authentication);
		return true;
	}

	private UsernamePasswordAuthenticationToken getAuthentication(String token) {
		if (token != null && !JwtUtils.isTokenExpired(token)) {
			String user = JwtUtils.getClaimsFromToken(token).getSubject();
			if (user != null) {
				return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
			}
		}
		return null;
	}



}

5、WebGlobalInterceptorConfigurer.java





import com.ewaycloud.jw.common.core.config.CoreCommonProperties;
import com.ewaycloud.jw.common.data.resolver.SqlFilterArgumentResolver;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * 拦截器配置
 * @author gwh
 * @date 2024/10/26
 */
@Configuration
@RequiredArgsConstructor
public class WebGlobalInterceptorConfigurer  implements WebMvcConfigurer {

	private final ObjectMapper objectMapper;
	private final CoreCommonProperties coreCommonProperties;

    /**
     * 增加请求参数解析器,对请求中的参数注入SQL 检查
     * @param resolverList 参数解析器
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolverList) {
        resolverList.add(new SqlFilterArgumentResolver());
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(webGlobalInterceptor()).addPathPatterns("/**")
                .excludePathPatterns("/user/login","/user/logout");
    }

    @Bean
    public WebGlobalInterceptor webGlobalInterceptor(){
        return new WebGlobalInterceptor(objectMapper, coreCommonProperties);
    }

}

6、SecurityUtils.java





import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.ewaycloud.jw.common.core.config.MyCacheManager;
import com.ewaycloud.jw.common.core.user.SysUser;
import com.ewaycloud.jw.common.core.user.UserInfo;
import com.ewaycloud.jw.common.core.constant.CommonConstants;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
//import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;

/**
 * 安全工具类
 *
 * @author gwh
 */
@Slf4j
@UtilityClass
public class SecurityUtils {

	private final CacheManager cacheManager = SpringContextHolder.getBean(CacheManager.class);

//	@Resource
//	private  RedisTemplate redisTemplate;
	/**
	 * 获取用户登录信息
	 */
	public UserInfo getUserInfo() {

		// 获取传递的token
		String token = WebUtils.getRequest().getHeader(CommonConstants.AUTHORIZATION);
		if (StrUtil.isBlank(token)) token = WebUtils.getRequest().getParameter(CommonConstants.AUTHORIZATION.toLowerCase());
		log.debug("获取header用户token为:{}", token);

		// 验证登录信息
		if (StrUtil.isNotBlank(token)) {
			UserInfo userInfo = (UserInfo)MyCacheManager.get(token.substring(7));
			if (userInfo != null ) {
				if ( userInfo.getSysUser() == null) {
					return null;
				}
				return userInfo;
			} else {
				return null;
			}
		} else {
			return null;
		}
	}

	/**
	 * 获取用户
	 */
	public SysUser getUser() {
		if (Objects.isNull(getUserInfo())) return null;
		return getUserInfo().getSysUser();
	}

	/**
	 * 获取用户角色信息
	 * @return 角色集合
	 */
	public List<Long> getRoleIds() {
		if (Objects.isNull(getUserInfo())) return null;
		return CollUtil.newArrayList(getUserInfo().getRoles());
	}

}

7、SecurityConfig.java



import com.ewaycloud.jw.common.data.interceptor.JwtAuthenticationFilter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/user/login","/user/logout").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilter(new JwtAuthenticationFilter(authenticationManager()));
    }
}

8、SysUserController.java





import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ewaycloud.jw.admin.api.dto.UserDTO;
import com.ewaycloud.jw.admin.api.vo.UserVO;
import com.ewaycloud.jw.admin.mapper.SysUserMapper;
import com.ewaycloud.jw.admin.service.SysUserService;
import com.ewaycloud.jw.channel.model.SysLogInfo;
import com.ewaycloud.jw.channel.service.SysLogInfoService;
import com.ewaycloud.jw.common.core.config.MyCacheManager;
import com.ewaycloud.jw.common.core.constant.CommonConstants;
import com.ewaycloud.jw.common.core.exception.ErrorCodes;
import com.ewaycloud.jw.common.core.user.SysUser;
import com.ewaycloud.jw.common.core.user.UserInfo;
import com.ewaycloud.jw.common.core.util.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.cache.CacheManager;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * 
 * @date 2018/12/16
 */
@RestController
@AllArgsConstructor
@RequestMapping("/user")
@Api(value = "user", tags = "用户管理模块")
public class SysUserController {

	private final SysUserService userService;
	private final CacheManager cacheManager;
//	private final RedisTemplate redisTemplate;

	@Resource
	private SysLogInfoService sysLogInfoService;

	@Resource
	private SysUserMapper sysUserMapper;


	public static String getIpAddr(HttpServletRequest request) {
		String ipAddress = null;
		try {
			ipAddress = request.getHeader("x-forwarded-for");
			if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
				ipAddress = request.getHeader("Proxy-Client-IP");
			}
			if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
				ipAddress = request.getHeader("WL-Proxy-Client-IP");
			}
			if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
				ipAddress = request.getRemoteAddr();
				if (ipAddress.equals("127.0.0.1")) {
					// 根据网卡取本机配置的IP
					InetAddress inet = null;
					try {
						inet = InetAddress.getLocalHost();
					} catch (UnknownHostException e) {
						e.printStackTrace();
					}
					ipAddress = inet.getHostAddress();
				}
			}
			// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
			if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
				if (ipAddress.indexOf(",") > 0) {
					ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
				}
			}
		} catch (Exception e) {
			ipAddress = "";
		}
		return ipAddress;
	}

	/**
	 * 用户登录
	 * @return 用户信息
	 */
	@GetMapping("/login")
	public R login(@RequestParam String username, @RequestParam String password, HttpServletRequest request) {
		UserInfo userInfo = userService.login(username, password);
		if (null != userInfo && userInfo.getLoginCount()==0) {
			return R.ok(userInfo);
		}
		if (null == userInfo ) {
			return R.failed("用户名或密码错误");
		}
		if(null != userInfo && userInfo.getLoginCount()>0 && userInfo.getLoginCount()<5) {

			return R.failed("用户名或密码错误");
		}
		if(null != userInfo && userInfo.getLoginCount()>5 ) {
			return R.failed("密码错误次数过多,账户已被锁定");
		}
		/** 记录日志 */
		SysLogInfo vo = new SysLogInfo();
		vo.setOperate("Get登录系统/user/login");
		if(null != userInfo && null!= userInfo.getSysUser()){
			vo.setCreateBy(userInfo.getSysUser().getName());
		}
		if(null != userInfo && null!= userInfo.getAreaName()){
			vo.setArea(userInfo.getAreaName());
		}
		vo.setCreateTime(new Date());
		//查询角色
		if(null != userInfo && null != userInfo.getSysUser()){
			UserVO userVO = sysUserMapper.getUserVoById(userInfo.getSysUser().getUserId());
			vo.setRole(userVO.getRoleList().get(0).getRoleName());
		}
		vo.setIp(getIpAddr(request));
		sysLogInfoService.addSysLogInfo(vo);
		return R.ok();
	}

	/**
	 * 获取指定用户全部信息
	 * @return 用户信息
	 */
	@GetMapping("/info/{username}")
	public R info(@PathVariable String username, HttpServletRequest request) {
		SysUser user = userService.getOne(Wrappers.<SysUser>query().lambda().eq(SysUser::getUsername, username));
		if (user == null) {
			return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_USERINFO_EMPTY, username));
		}
		SysLogInfo vo = new SysLogInfo();
		vo.setOperate("Get获取指定用户全部信息/user/"+username);
		vo.setIp(getIpAddr(request));
		sysLogInfoService.addSysLogInfo(vo);
		return R.ok(userService.findUserInfo(user));
	}

	/**
	 * 获取当前用户登出
	 * @return 用户信息
	 */
	@DeleteMapping(value = { "/logout" })
	public R logout( HttpServletRequest request) {
		if (Objects.isNull(SecurityUtils.getUser())) return R.ok();
		String token = WebUtils.getRequest().getHeader(CommonConstants.AUTHORIZATION).substring(7);
		/** 设置token失效*/
		if(null != token){
			JwtUtils.setTokenExpired(token);
			MyCacheManager.set(token, null);

		}
		SysLogInfo vo = new SysLogInfo();

		vo.setOperate("Delete退出系统/user/logout"+ token);
		vo.setIp(getIpAddr(request));
		sysLogInfoService.addSysLogInfo(vo);
		return R.ok();
	}

	/**
	 * 获取当前用户全部信息
	 * @return 用户信息
	 */
	@GetMapping(value = { "/info" })
	public R info() {
		String username = SecurityUtils.getUser().getUsername();
		SysUser user = userService.getOne(Wrappers.<SysUser>query().lambda().eq(SysUser::getUsername, username));
		if (user == null) {
			return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_QUERY_ERROR));
		}
		return R.ok(userService.findUserInfo(user));
	}

	/**
	 * 通过ID查询用户信息
	 * @param id ID
	 * @return 用户信息
	 */
	@GetMapping("/{id}")
	public R user(@PathVariable Long id) {
		return R.ok(userService.selectUserVoById(id));
	}

	/**
	 * 根据用户名查询用户信息
	 * @param username 用户名
	 * @return
	 */
	@GetMapping("/details/{username}")
	public R user(@PathVariable String username) {
		SysUser condition = new SysUser();
		condition.setUsername(username);
		return R.ok(userService.getOne(new QueryWrapper<>(condition)));
	}

	/**
	 * 删除用户信息
	 * @param id ID
	 * @return R
	 */
	@DeleteMapping("/{id}")
	@ApiOperation(value = "删除用户", notes = "根据ID删除用户")
	@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "int", paramType = "path")
	public R userDel(@PathVariable Long id, HttpServletRequest request) {
		SysUser sysUser = userService.getById(id);
		SysLogInfo vo = new SysLogInfo();
		vo.setOperate("Delete删除用户信息/id"+id);
		vo.setIp(getIpAddr(request));
		sysLogInfoService.addSysLogInfo(vo);
		return R.ok(userService.deleteUserById(sysUser));
	}

	/**
	 * 添加用户
	 * @param userDto 用户信息
	 * @return success/false
	 */
	@PostMapping
	public R user(@RequestBody UserDTO userDto, HttpServletRequest request) {
		SysLogInfo vo = new SysLogInfo();
		vo.setOperate("Post添加用户/name"+userDto.getName());
		vo.setIp(getIpAddr(request));
		sysLogInfoService.addSysLogInfo(vo);
		return R.ok(userService.saveUser(userDto));
	}

	/**
	 * 更新用户信息
	 * @param userDto 用户信息
	 * @return R
	 */
	@PutMapping
	public R updateUser(@Valid @RequestBody UserDTO userDto) {
		return R.ok(userService.updateUser(userDto));
	}

	/**
	 * 分页查询用户
	 * @param page 参数集
	 * @param userDTO 查询参数列表
	 * @return 用户集合
	 */
	@GetMapping("/page")
	public R getUserPage(Page page, UserDTO userDTO) {
		//根据当前登录人所在的区县 areaId 做权限控制, 市级用户看全部
		Long userId = SecurityUtils.getUser().getUserId();
		UserVO userVO = sysUserMapper.getUserVoById(userId);
		if(userVO.getAreaId() == 370300 || userVO.getUsername().equals("admin")){
			return R.ok(userService.getUsersWithRolePage(page, userDTO));
		}else {
			userDTO.setUserId(userId);
			return R.ok(userService.getUsersWithRolePage(page, userDTO));
		}
	}

	/**
	 * 修改个人信息
	 * @param userDto userDto
	 * @return success/false
	 */
	@PutMapping("/edit")
	public R updateUserInfo(@Valid @RequestBody UserDTO userDto, HttpServletRequest request) {
		SysLogInfo vo = new SysLogInfo();
		vo.setOperate("Put修改个人信息/edit/"+userDto.getUserId());
		vo.setIp(getIpAddr(request));
		sysLogInfoService.addSysLogInfo(vo);
		return userService.updateUserInfo(userDto);
	}

	/**
	 * @param username 用户名称
	 * @return 上级部门用户列表
	 */
	@GetMapping("/ancestor/{username}")
	public R listAncestorUsers(@PathVariable String username) {
		return R.ok(userService.listAncestorUsers(username));
	}

	/**
	 * 锁定指定用户
	 * @param username 用户名
	 * @return R
	 */
	@PutMapping("/lock/{username}")
	public R lockUser(@PathVariable String username) {
		SysLogInfo vo = new SysLogInfo();
		vo.setOperate("Put锁定指定用户/lock/username/"+username);
		sysLogInfoService.addSysLogInfo(vo);
		return userService.lockUser(username);
	}


	/**
	 * 通过ID查询用户
	 *
	 * @param id ID
	 * @return 用户信息
	 */
	@GetMapping("/id/{id}")
	public R getByUserId(@PathVariable Long id) {
		return R.ok(userService.getById(id));
	}

	/**
	 * 查询租户所有用户携带部门名称
	 */
	@GetMapping("/list/dept-name")
	public R listUsersWithDeptName(){
		return R.ok(userService.listUsersWithDeptName());
	}

	/**
	 * 查询选择办理人/角色分页
	 *
	 * @param userVO 查询参数列表
	 * @return 用户及角色集合
	 */
	@GetMapping("/user-role/page")
	public R getUserRolePage(Page page, UserVO userVO) {
		return R.ok(userService.getUserRolePage(page, userVO));
	}

	/**
	 * 查询租户所有用户
	 */
	@GetMapping("/list")
	public R listUsers(){
		if(null == SecurityUtils.getUser().getUserId()){
			return null;
		}
		//根据当前登录人所在的区县 areaId 做权限控制, 市级用户看全部
		Long userId = SecurityUtils.getUser().getUserId();
		UserVO userVO = sysUserMapper.getUserVoById(userId);
		if(userVO.getAreaId() == 370300){
			return R.ok(userService.list());
		}else {
			UserDTO userDTO = new UserDTO();
			userDTO.setUserId(userId);
			List<UserVO> userList =  sysUserMapper.selectVoListByScope(userDTO);
			return R.ok(userList);
		}
	}

}

9、SysUserServiceImpl.java




import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ewaycloud.jw.admin.api.dto.UserDTO;
import com.ewaycloud.jw.admin.api.entity.SysDept;
import com.ewaycloud.jw.admin.api.entity.SysMenu;
import com.ewaycloud.jw.admin.api.entity.SysRole;
import com.ewaycloud.jw.admin.api.entity.SysUserRole;
import com.ewaycloud.jw.admin.api.vo.RoleVO;
import com.ewaycloud.jw.admin.api.vo.UserVO;
import com.ewaycloud.jw.admin.config.LoginConfigProperties;
import com.ewaycloud.jw.admin.mapper.SysUserMapper;
import com.ewaycloud.jw.admin.service.*;
import com.ewaycloud.jw.common.core.config.MyCacheManager;
import com.ewaycloud.jw.common.core.constant.CommonConstants;
import com.ewaycloud.jw.common.core.exception.ErrorCodes;
import com.ewaycloud.jw.common.core.user.SysUser;
import com.ewaycloud.jw.common.core.user.UserInfo;
import com.ewaycloud.jw.common.core.util.JwtUtils;
import com.ewaycloud.jw.common.core.util.MsgUtils;
import com.ewaycloud.jw.common.core.util.R;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.validation.ValidationException;
import java.util.*;
import java.util.stream.Collectors;

/**
 *
 * @date 2017/10/31
 */
@Slf4j
@Service
@AllArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {

	private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

	private final SysMenuService sysMenuService;

	private final SysRoleService sysRoleService;

	private final SysDeptService sysDeptService;

	private final SysUserRoleService sysUserRoleService;

	private final LoginConfigProperties loginConfigProperties;

	private final CacheManager cacheManager;

	private final SysUserMapper sysUserMapper;

//	private final RedisTemplate redisTemplate;

	private static final String KEY_ALGORITHM = "AES";

	private static int loginCount = 0;
	private static final int MAX_LOGIN_COUNT = 5;
	private static boolean isLocked = false;

	/**
	 * 保存用户信息
	 * @param userDto DTO 对象
	 * @return success/fail
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean saveUser(UserDTO userDto) {
		SysUser sysUser = new SysUser();
		BeanUtils.copyProperties(userDto, sysUser);
		sysUser.setDelFlag(CommonConstants.STATUS_NORMAL);
		sysUser.setPassword(ENCODER.encode(userDto.getPassword()));
		baseMapper.insert(sysUser);

		// 如果角色为空,赋默认角色
		if (CollUtil.isEmpty(userDto.getRole())) {
			// 默认角色
			SysRole sysRole = sysRoleService
					.getOne(Wrappers.<SysRole>lambdaQuery().eq(SysRole::getRoleCode, 1));
			userDto.setRole(Collections.singletonList(sysRole.getRoleId()));
		}

		List<SysUserRole> userRoleList = userDto.getRole().stream().map(roleId -> {
			SysUserRole userRole = new SysUserRole();
			userRole.setUserId(sysUser.getUserId());
			userRole.setRoleId(roleId);
			return userRole;
		}).collect(Collectors.toList());

		return sysUserRoleService.saveBatch(userRoleList);
	}

	@Override
	public UserInfo login(String username, String password) {
		if (isLocked) {
			System.out.println(username+ " 账户已锁定,请稍后再试");
			return null;
		}
		SysUser user = this.getOne(Wrappers.<SysUser>query().lambda().eq(SysUser::getUsername, username));
		if (user == null) {
			throw new ValidationException("用户名或密码错误");
		}
		if (CommonConstants.STATUS_LOCK.equals(user.getLockFlag())) {
			return null;
		}
	
		if (!ENCODER.matches(this.decryptAES(password), user.getPassword())) {
			loginCount++;
			System.out.println(username+ "密码错误,还剩余" + (MAX_LOGIN_COUNT - loginCount) + "次机会");
			if (loginCount < MAX_LOGIN_COUNT) {
				UserInfo userInfo = new UserInfo();
				userInfo.setLoginCount(loginCount);
				return userInfo;
			}
			if (loginCount >= MAX_LOGIN_COUNT) {
				System.out.println(username+ "密码错误次数过多,账户已被锁定");
//				isLocked = true;
				/** 锁定用户 */
				this.lockUser(username);
				UserInfo userInfo = new UserInfo();
				userInfo.setLoginCount(loginCount);
				return userInfo;
			}
		} else {
			System.out.println(username+ "登录成功!");
			loginCount = 0;
			// 放入缓存
			UserInfo userInfo = this.findUserInfo(user);
			UserInfo userInfoCache = new UserInfo();
			BeanUtil.copyProperties(userInfo, userInfoCache);
			//通userId查询用户的areaId
			UserVO userVO = sysUserMapper.getUserVoById(user.getUserId());
			userInfo.setAreaId(userVO.getAreaId());
			userInfo.setAreaName(userVO.getAreaName());
			userInfo.setLoginCount(0);
			userInfoCache.setToken(JwtUtils.generateToken(username));
			/** 设置token */
			String token = JwtUtils.generateToken(username);
			userInfo.setToken(token);
			/** 本地缓存 1天 */
//			MyCacheManager.set(userInfo.getSysUser().getUserId().toString(), userInfoCache, 24*60*60*1000); //缓存用户信息1天,即 24*60*60*1000
			MyCacheManager.set(token, userInfoCache, 24*60*60*1000); //缓存用户信息1天,即 24*60*60*1000

//			redisTemplate.opsForValue().set(token, userInfoCache, 86400000);
//			redisTemplate.opsForValue().set(username, token, 86400000);
			log.info("登录用户{}",userInfo.getSysUser().getUsername());
//			isLocked = false;
			return userInfo;
		}
		return null;
	}

	/**
	 * 原文解密
	 */
	private String decryptAES(String password) {

		// 构建前端对应解密AES 因子
		AES aes = new AES(Mode.CFB, Padding.NoPadding,
				new SecretKeySpec(loginConfigProperties.getEncodeKey().getBytes(), KEY_ALGORITHM),
				new IvParameterSpec(loginConfigProperties.getEncodeKey().getBytes()));

		return aes.decryptStr(password);
	}

	/**
	 * 通过查用户的全部信息
	 * @param sysUser 用户
	 * @return
	 */
	@Override
	public UserInfo findUserInfo(SysUser sysUser) {
		UserInfo userInfo = new UserInfo();
		userInfo.setSysUser(sysUser);
		// 设置角色列表 (ID)
		List<Long> roleIds = sysRoleService.findRolesByUserId(sysUser.getUserId()).stream().map(RoleVO::getRoleId)
				.collect(Collectors.toList());
		userInfo.setRoles(ArrayUtil.toArray(roleIds, Long.class));

		// 设置权限列表(menu.permission)
		Set<String> permissions = new HashSet<>();
		roleIds.forEach(roleId -> {
			List<String> permissionList = sysMenuService.findMenuByRoleId(roleId).stream()
					.map(SysMenu::getPermission).filter(StrUtil::isNotEmpty)
					.collect(Collectors.toList());
			permissions.addAll(permissionList);
		});
		userInfo.setPermissions(ArrayUtil.toArray(permissions, String.class));
		return userInfo;
	}

	/**
	 * 分页查询用户信息(含有角色信息)
	 * @param page 分页对象
	 * @param userDTO 参数列表
	 * @return
	 */
	@Override
	public IPage getUsersWithRolePage(Page page, UserDTO userDTO) {
		return baseMapper.getUserVosPage(page, userDTO);
	}

	/**
	 * 通过ID查询用户信息
	 * @param id 用户ID
	 * @return 用户信息
	 */
	@Override
	public UserVO selectUserVoById(Long id) {
		return baseMapper.getUserVoById(id);
	}

	/**
	 * 删除用户
	 * @param sysUser 用户
	 * @return Boolean
	 */
	@Override
	@CacheEvict(value = CommonConstants.USER_DETAILS, key = "#sysUser.username")
	public Boolean deleteUserById(SysUser sysUser) {
		sysUserRoleService.deleteByUserId(sysUser.getUserId());
		this.removeById(sysUser.getUserId());
		return Boolean.TRUE;
	}

	@Override
	@CacheEvict(value = CommonConstants.USER_DETAILS, key = "#userDto.username")
	public R<Boolean> updateUserInfo(UserDTO userDto) {
		UserVO userVO = baseMapper.getUserVoByUsername(userDto.getUsername());
		if (!ENCODER.matches(userDto.getPassword(), userVO.getPassword())) {
			log.info("原密码错误,修改个人信息失败:{}", userDto.getUsername());
			return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_UPDATE_PASSWORDERROR));
		}

		SysUser sysUser = new SysUser();
		if (StrUtil.isNotBlank(userDto.getNewpassword1())) {
			sysUser.setPassword(ENCODER.encode(userDto.getNewpassword1()));
		}
		sysUser.setPhone(userDto.getPhone());
		sysUser.setUserId(userVO.getUserId());
		sysUser.setAvatar(userDto.getAvatar());
		sysUser.setNickname(userDto.getNickname());
		sysUser.setName(userDto.getName());
		sysUser.setEmail(userDto.getEmail());
		return R.ok(this.updateById(sysUser));
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	@CacheEvict(value = CommonConstants.USER_DETAILS, key = "#userDto.username")
	public Boolean updateUser(UserDTO userDto) {
		SysUser sysUser = new SysUser();
		BeanUtils.copyProperties(userDto, sysUser);
//		sysUser.setUpdateTime(new Date());
		sysUser.setUpdateTime(new Date());

		if (StrUtil.isNotBlank(userDto.getPassword())) {
			sysUser.setPassword(ENCODER.encode(userDto.getPassword()));
		}
		this.updateById(sysUser);

		sysUserRoleService
				.remove(Wrappers.<SysUserRole>update().lambda().eq(SysUserRole::getUserId, userDto.getUserId()));
		userDto.getRole().forEach(roleId -> {
			SysUserRole userRole = new SysUserRole();
			userRole.setUserId(sysUser.getUserId());
			userRole.setRoleId(roleId);
			userRole.insert();
		});
		return Boolean.TRUE;
	}

	/**
	 * 查询上级部门的用户信息
	 * @param username 用户名
	 * @return R
	 */
	@Override
	public List<SysUser> listAncestorUsers(String username) {
		SysUser sysUser = this.getOne(Wrappers.<SysUser>query().lambda().eq(SysUser::getUsername, username));

		SysDept sysDept = sysDeptService.getById(sysUser.getDeptId());
		if (sysDept == null) {
			return null;
		}

		Long parentId = sysDept.getParentId();
		return this.list(Wrappers.<SysUser>query().lambda().eq(SysUser::getDeptId, parentId));
	}

	/**
	 * 锁定用户
	 * @param username 用户名
	 * @return
	 */
	@Override
//	@CacheEvict(value = CommonConstants.USER_DETAILS, key = "#username")
	public R<Boolean> lockUser(String username) {
		SysUser sysUser = baseMapper.selectOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUsername, username));
		sysUser.setLockFlag(CommonConstants.STATUS_LOCK);
		baseMapper.updateById(sysUser);
		return R.ok();
	}

	@Override
	public List<SysUser> listUsersWithDeptName() {
		List<SysUser> sysUsers = this.list(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getLockFlag, CommonConstants.STATUS_NORMAL));
		// 拼接部门信息
		if (CollUtil.isEmpty(sysUsers)) {
			return sysUsers;
		}
		List<SysDept> sysDepts = sysDeptService.list();
		sysUsers.forEach(user -> {
			SysDept sysDept = sysDepts.stream().filter(f -> f.getDeptId().equals(user.getDeptId())).findAny().get();
			user.setName(user.getName() + "-" + sysDept.getName());
		});
		return sysUsers;
	}

	@Override
	public IPage getUserRolePage(Page page, UserVO userVO) {
		return baseMapper.getUserRolePage(page, userVO);
	}

	@Override
	public List<UserVO> listUsersByRoleIds(List<Long> roleIds) {
		List<SysUserRole> userRoles = sysUserRoleService.list(Wrappers.<SysUserRole>lambdaQuery().in(SysUserRole::getRoleId,roleIds));
		List<Long> list = userRoles.stream().map(SysUserRole::getUserId).collect(Collectors.toList());
		if (CollUtil.isEmpty(list)) return Collections.emptyList();
		return this.listByIds(list).stream().map(m->{
			UserVO userVO = new UserVO();
			BeanUtil.copyProperties(m,userVO);
			List<Long> userRoleIds = userRoles.stream().filter(f -> f.getUserId().equals(m.getUserId())).map(SysUserRole::getRoleId).collect(Collectors.toList());
			List<SysRole> sysRoles = sysRoleService.listByIds(userRoleIds);
			userVO.setRoleList(sysRoles);
			return userVO;
		}).collect(Collectors.toList());
	}

	@Override
	public List<SysUser> listUsersByRoleId(Long roleId) {
		List<Long> list = sysUserRoleService.list(Wrappers.<SysUserRole>lambdaQuery().eq(SysUserRole::getRoleId, roleId))
				.stream().map(SysUserRole::getUserId).collect(Collectors.toList());
		return CollUtil.isEmpty(list) ? Collections.emptyList() : this.list(Wrappers.<SysUser>lambdaQuery().in(SysUser::getUserId, list)
				.eq(SysUser::getLockFlag, CommonConstants.STATUS_NORMAL));
	}

}

10、SysUserService.java





import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ewaycloud.jw.admin.api.dto.UserDTO;
import com.ewaycloud.jw.common.core.user.UserInfo;
import com.ewaycloud.jw.common.core.user.SysUser;
import com.ewaycloud.jw.admin.api.vo.UserVO;
import com.ewaycloud.jw.common.core.util.R;

import java.util.List;

/**
 * 
 * @date 2017/10/31
 */
public interface SysUserService extends IService<SysUser> {

	/**
	 * 用户登录
	 * @param username 用户名
	 * @param password 密码
	 * @return UserInfo
	 */
	UserInfo login(String username, String password);

	/**
	 * 查询用户信息
	 * @param sysUser 用户
	 * @return userInfo
	 */
	UserInfo findUserInfo(SysUser sysUser);

	/**
	 * 分页查询用户信息(含有角色信息)
	 * @param page 分页对象
	 * @param userDTO 参数列表
	 * @return
	 */
	IPage getUsersWithRolePage(Page page, UserDTO userDTO);

	/**
	 * 删除用户
	 * @param sysUser 用户
	 * @return boolean
	 */
	Boolean deleteUserById(SysUser sysUser);

	/**
	 * 更新当前用户基本信息
	 * @param userDto 用户信息
	 * @return Boolean
	 */
	R<Boolean> updateUserInfo(UserDTO userDto);

	/**
	 * 更新指定用户信息
	 * @param userDto 用户信息
	 * @return
	 */
	Boolean updateUser(UserDTO userDto);

	/**
	 * 通过ID查询用户信息
	 * @param id 用户ID
	 * @return 用户信息
	 */
	UserVO selectUserVoById(Long id);

	/**
	 * 查询上级部门的用户信息
	 * @param username 用户名
	 * @return R
	 */
	List<SysUser> listAncestorUsers(String username);

	/**
	 * 保存用户信息
	 * @param userDto DTO 对象
	 * @return success/fail
	 */
	Boolean saveUser(UserDTO userDto);

	/**
	 * 锁定用户
	 * @param username
	 * @return
	 */
	R<Boolean> lockUser(String username);

	/**
	 * 查询租户所有用户携带部门名称
	 */
	List<SysUser> listUsersWithDeptName();

	/**
	 * 查询选择办理人/角色分页
	 *
	 * @param userVO 参数列表
	 */
	IPage getUserRolePage(Page page, UserVO userVO);

	/**
	 * 通过角色ID查询用户集合
	 *
	 * @param roleIds 角色ID
	 * @return 用户信息
	 */
	List<UserVO> listUsersByRoleIds(List<Long> roleIds);

	/**
	 * 通过角色ID查询用户集合
	 *
	 * @param roleId 角色ID
	 * @return 用户信息
	 */
	List<SysUser> listUsersByRoleId(Long roleId);

}

11、前端:user.js

import {getStore, setStore} from '@/util/store'
import {isURL, validatenull} from '@/util/validate'
import {loginByUsername, getUserInfo, logout} from '@/api/login'
import {deepClone, encryption} from '@/util'
import webiste from '@/const/website'
import {getMenu, getTopMenu} from '@/api/admin/menu'

function addPath(ele, first) {
  const menu = webiste.menu
  const propsConfig = menu.props
  const propsDefault = {
    label: propsConfig.label || 'name',
    path: propsConfig.path || 'path',
    icon: propsConfig.icon || 'icon',
    children: propsConfig.children || 'children'
  }
  const icon = ele[propsDefault.icon]
  ele[propsDefault.icon] = validatenull(icon) ? menu.iconDefault : icon
  const isChild = ele[propsDefault.children] && ele[propsDefault.children].length !== 0
  if (!isChild) ele[propsDefault.children] = []
  if (!isChild && first && !isURL(ele[propsDefault.path])) {
    ele[propsDefault.path] = ele[propsDefault.path] + '/index'
  } else {
    ele[propsDefault.children].forEach(child => {
      addPath(child)
    })
  }
}

const user = {
  state: {
    userInfo: getStore({
      name: 'userInfo'
    }) || {},
    permissions: getStore({
      name: 'permissions'
    }) || [],
    roles: [],
    menu: getStore({
      name: 'menu'
    }) || [],
    menuAll: [],
    userId: getStore({
      name: 'userId'
    }) || ''
  },
  actions: {
    // 根据用户名登录
    LoginByUsername({commit}, userInfo) {
      let user = {}
      if (webiste.passwordEnc) {
        user = encryption({
          data: userInfo,
          key: '1234567887654321',
          param: ['password']
        })
      } else {
        user = userInfo
      }
      return new Promise((resolve, reject) => {
        loginByUsername(user.username, user.password).then(response => {
          const data = response.data.data || {}
          commit('SET_USER_INFO', data.sysUser)
          commit('SET_ROLES', data.roles || [])
          commit('SET_PERMISSIONS', data.permissions || [])
          commit('SET_USERID', data.sysUser.userId)
          commit('CLEAR_LOCK')
          commit('token', data.token)
					localStorage.setItem('areaName', data.areaName);
          localStorage.setItem('areaId', data.areaId);
          localStorage.setItem('token', data.token);
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },
    // 查询用户信息
    GetUserInfo({commit}) {
      return new Promise((resolve, reject) => {
        getUserInfo().then((res) => {
          const data = res.data.data || {}
          commit('SET_USER_INFO', data.sysUser)
          commit('SET_ROLES', data.roles || [])
          commit('SET_PERMISSIONS', data.permissions || [])

					localStorage.setItem('areaName', data.areaName);

          resolve(data)
        }).catch(() => {
          reject()
        })
      })
    },
    // 登出
    LogOut({commit}) {
      return new Promise((resolve, reject) => {
        logout().then(() => {
          commit('SET_MENU', [])
          commit('SET_PERMISSIONS', [])
          commit('SET_USER_INFO', {})
          commit('SET_USERID', '')
          commit('SET_ROLES', [])
          commit('DEL_ALL_TAG')
          commit('CLEAR_LOCK')
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },
    // 获取系统菜单
    GetMenu({commit}, obj) {
      // 记录用户点击顶部信息,保证刷新的时候不丢失
      commit('LIKE_TOP_MENUID', obj)

      return new Promise(resolve => {
        getMenu(obj.id).then((res) => {
          const data = res.data.data
          const menu = deepClone(data)
          menu.forEach(ele => {
            addPath(ele)
          })
          let type = obj.type
          commit('SET_MENU', {type, menu})
          resolve(menu)
        }).catch(error => {
          reject(error)
        })
      })
    },
    //顶部菜单
    GetTopMenu() {
      return new Promise(resolve => {
        getTopMenu().then((res) => {
          const data = res.data.data || []
          resolve(data)
        }).catch(error => {
          reject(error)
        })
      })
    }
  },
  mutations: {
    SET_USERID: (state, userId) => {
      state.userId = userId
      setStore({
        name: 'userId',
        content: state.userId,
        type: 'session'
      })
    },
    SET_USER_INFO: (state, userInfo) => {
      state.userInfo = userInfo
      setStore({
        name: 'userInfo',
        content: userInfo,
        type: 'session'
      })
    },
    SET_MENU: (state, params = {}) => {
      let {menu, type} = params;
      if (type !== false) state.menu = menu
      setStore({
        name: 'menu',
        content: menu,
        type: 'session'
      })
    },
    SET_MENU_ALL: (state, menuAll) => {
      state.menuAll = menuAll
    },
    SET_ROLES: (state, roles) => {
      state.roles = roles
    },
    SET_PERMISSIONS: (state, permissions) => {
      const list = {}
      for (let i = 0; i < permissions.length; i++) {
        list[permissions[i]] = true
      }

      state.permissions = list
      setStore({
        name: 'permissions',
        content: list,
        type: 'session'
      })
    }
  }

}
export default user

12、前端:axios.js

import axios from 'axios'
import {
  paramsFilter,
  serialize
} from '@/util'
import NProgress from 'nprogress' // progress bar
import errorCode from '@/const/errorCode'
import {
  Message,
  MessageBox
} from 'element-ui'
import 'nprogress/nprogress.css'
import qs from 'qs'
import store from '@/store' // progress bar style
import * as aesSecure from "./aes/CryptoJS";
import {
  dataFilter
} from "@/const/flow";
import BigNumber from 'bignumber.js';

axios.defaults.timeout = 300000
// 返回其他状态吗
axios.defaults.validateStatus = function(status) {
  return status >= 200 && status <= 500 // 默认的
}
// 跨域请求,允许保存cookie
axios.defaults.withCredentials = true
// NProgress Configuration
NProgress.configure({
  showSpinner: false
})

// HTTPrequest拦截
axios.interceptors.request.use(config => {
  NProgress.start() // start progress bar

  const userId = store.getters.userId

  if (userId) {
    // config.headers['USER-ID'] = userId // 用户ID
    config.headers['authorization'] = 'Bearer ' + localStorage.getItem('token') // token
  }

  if (aesSecure.dataAesEnabled === true) config.headers['Content-Type'] = "application/json;charset=UTF-8";


  // 校验参数、加密
  if (typeof config.data === 'object' && config.responseType !== "arraybuffer") {

    for (const dataKey in config.data) {
      if (typeof config.data[dataKey] === 'number' && !isNaN(config.data[dataKey])) {
        // console.log(dataKey)
        // console.log(config.data[dataKey])
        // console.log(new BigNumber(config.data[dataKey] + '').toString())
        config.data[dataKey] = new BigNumber(config.data[dataKey] + '');
      }
    }

    // 这里给后端传参的时候会删除是空的字段,导致只能修改字段,不能删除字段
    // if (aesSecure.dataAesEnabled === false) config.data = dataFilter(config.headers, config.data)
    // else config.data = aesSecure.Encrypt(JSON.stringify(dataFilter(config.headers, config.data)))
  }
  if (typeof config.params === 'object' && config.responseType !== "arraybuffer") config.params = dataFilter(config
    .headers, config.params)

  // serialize为true开启序列化
  if (config.method === 'post' && config.headers.serialize) {
    config.data = serialize(config.data)
    delete config.data.serialize
  }

  if (config.method === 'get') {
    config.paramsSerializer = function(params) {
      return qs.stringify(paramsFilter(params), {
        arrayFormat: 'repeat'
      })
    }
  }


  return config
}, error => {
  return Promise.reject(error)
})

// HTTPresponse拦截
axios.interceptors.response.use(res => {
  NProgress.done()
  // 返回值解密
  if (aesSecure.dataAesEnabled === true && res.config.responseType !== "arraybuffer") res.data = JSON.parse(
    aesSecure.Decrypt(res.data));

  const status = Number(res.status) || 200
  const message = res.data.msg || errorCode[status] || errorCode['default']

  // 后台定义 424
  if (status === 424) {
    MessageBox.confirm('令牌状态已过期,请点击重新登录', '系统提示', {
      confirmButtonText: '重新登录',
      cancelButtonText: '取消',
      type: 'warning'
    }).then(() => {
      store.dispatch('LogOut').then(() => {
        // 刷新登录页面,避免多次弹框
        window.location.reload()
      })
    }).catch(() => {});
    return
  }

  if (status !== 200 || res.data.code === 1) {
    Message({
      message: message,
      type: 'error'
    })
    return Promise.reject(new Error(message))
  }

  return res
}, error => {
  // 处理 503 网络异常
  let response = error.response;
  if (response && response.status === 503) {
    Message({
      message: response.data.msg,
      type: 'error'
    })
  }
  NProgress.done()
  return Promise.reject(new Error(error))
})

export default axios


http://www.kler.cn/a/370290.html

相关文章:

  • 游戏引擎学习第80天
  • Linux下MySQL的简单使用
  • MATLAB中characterListPattern函数用法
  • N个utils(sql)
  • 在 Babylon.js 中使用 Gizmo:交互式 3D 操作工具
  • Express中间件
  • java智能物流管理系统源码(springboot)
  • 智慧旅游微信小程序平台
  • Milvus 与 Faiss:选择合适的向量数据库
  • 【SQL Server】解决因使用 varchar 类型存储 Unicode 字符串导致的中文显示乱码问题
  • 2024开放原子开源生态大会 | 麒麟信安携手openEuler共建开源生态,共塑产业未来
  • 应用架构参考设计
  • 【论文阅读】CNN网络权值的拓扑数据分析
  • 【泰克生物】比较酵母双杂交中的SMART、Gateway和In-Gate技术:优缺点分析
  • 日本HarmonicDrive哈默纳科减速机SHF系列在半导体中的应用
  • Django创建数据模型的列的类型和属性
  • CV - 图像实例分割开源算法 SAM2(Segment Anything) 视频分割 教程 (2)
  • electron Debian arm64 linux设备打包deb安装包 遇到的fpm问题
  • 基于深度学习算法的动物检测系统(含PyQt+代码+训练数据集)
  • 反编译华为-研究功耗联网监控日志
  • 3.1.4 Hyperspace 的临时映射1
  • Golang | Leetcode Golang题解之第509题斐波那契数
  • HttpServer模块 --- 封装TcpServer支持Http协议
  • 基于neo4j的鸟类百科知识图谱问答系统
  • QT 中彻底解决中文乱码问题的指南
  • appium文本输入的多种形式