集成shiro
This commit is contained in:
parent
0f7149f1b0
commit
b1ca1ace25
@ -91,6 +91,17 @@
|
||||
<artifactId>shiro-spring</artifactId>
|
||||
<version>1.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>3.8.3</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.amqp</groupId>-->
|
||||
|
@ -1,77 +0,0 @@
|
||||
package xyz.longicorn.driver.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AuthFilter extends BasicHttpAuthenticationFilter {
|
||||
|
||||
//判断是否允许通过
|
||||
@Override
|
||||
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
|
||||
return super.isAccessAllowed(request, response, mappedValue);
|
||||
}
|
||||
|
||||
//是否允许登录
|
||||
@Override
|
||||
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
|
||||
return super.isLoginAttempt(request, response);
|
||||
}
|
||||
|
||||
// 创建shiro token
|
||||
@Override
|
||||
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
|
||||
return super.createToken(request, response);
|
||||
}
|
||||
|
||||
//isAccessAllowed为false时调用,验证失败
|
||||
@Override
|
||||
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
|
||||
this.sendChallenge(request, response);
|
||||
responseError(response, "he he !");
|
||||
return false;
|
||||
}
|
||||
|
||||
//shiro验证成功调用
|
||||
@Override
|
||||
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
|
||||
return super.onLoginSuccess(token, subject, request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
|
||||
if (!isLoginAttempt(request, response)) {
|
||||
responseError(response, "verify token failure");
|
||||
return false;
|
||||
}
|
||||
return super.preHandle(request, response);
|
||||
}
|
||||
|
||||
private void setCors(ServletResponse response) {
|
||||
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
|
||||
// httpServletResponse.addHeader("Access-Control-Allow-Credentials", "true");
|
||||
httpServletResponse.addHeader("Access-Control-Allow-Origin", "*");
|
||||
httpServletResponse.addHeader("Access-Control-Allow-Methods", "*");
|
||||
httpServletResponse.addHeader("Access-Control-Allow-Headers", "*");
|
||||
}
|
||||
|
||||
private void responseError(ServletResponse response, Object msg) {
|
||||
setCors(response);
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
httpResponse.setStatus(401);
|
||||
httpResponse.setCharacterEncoding("UTF-8");
|
||||
httpResponse.setContentType("application/json;charset=UTF-8");
|
||||
try {
|
||||
String rj = msg instanceof String ? (String) msg : new ObjectMapper().writeValueAsString(msg);
|
||||
httpResponse.getWriter().append(rj);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package xyz.longicorn.driver.config;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.*;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
//redis配置类
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||||
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
// 配置连接工厂
|
||||
template.setConnectionFactory(factory);
|
||||
|
||||
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
|
||||
Jackson2JsonRedisSerializer jackson = new Jackson2JsonRedisSerializer(Object.class);
|
||||
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
|
||||
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
|
||||
//om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
jackson.setObjectMapper(om);
|
||||
|
||||
// 值采用json序列化
|
||||
template.setValueSerializer(jackson);
|
||||
//使用StringRedisSerializer来序列化和反序列化redis的key值
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
|
||||
// 设置hash key 和value序列化模式
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setHashValueSerializer(jackson);
|
||||
template.afterPropertiesSet();
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对hash类型的数据操作
|
||||
*
|
||||
* @param redisTemplate
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
|
||||
return redisTemplate.opsForHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对redis字符串类型数据操作
|
||||
*
|
||||
* @param redisTemplate
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
|
||||
return redisTemplate.opsForValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对链表类型的数据操作
|
||||
*
|
||||
* @param redisTemplate
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
|
||||
return redisTemplate.opsForList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对无序集合类型的数据操作
|
||||
*
|
||||
* @param redisTemplate
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
|
||||
return redisTemplate.opsForSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对有序集合类型的数据操作
|
||||
*
|
||||
* @param redisTemplate
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
|
||||
return redisTemplate.opsForZSet();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,61 +0,0 @@
|
||||
package xyz.longicorn.driver.config;
|
||||
|
||||
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
|
||||
import org.apache.shiro.mgt.DefaultSubjectDAO;
|
||||
import org.apache.shiro.mgt.SecurityManager;
|
||||
import org.apache.shiro.realm.Realm;
|
||||
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
|
||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Configuration
|
||||
public class ShiroConfig {
|
||||
@Autowired
|
||||
private UserRealm userRealm;
|
||||
|
||||
//ShiroFilter过滤所有请求
|
||||
@Bean(name = "shiroFilterFactoryBean")
|
||||
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
|
||||
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
|
||||
Map<String, Filter> filterMap = new LinkedHashMap<>();
|
||||
filterMap.put("auth", new AuthFilter()); // 设置验证过滤器
|
||||
|
||||
shiroFilterFactoryBean.setFilters(filterMap);
|
||||
//给ShiroFilter配置安全管理器
|
||||
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
|
||||
//配置系统公共资源
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
//匿名资源 一定是在受限资源上面
|
||||
map.put("/api/**", "anon");
|
||||
//受限资源需要认证和授权
|
||||
map.put("/**", "auth");
|
||||
|
||||
// 设置认证界面路径
|
||||
// shiroFilterFactoryBean.setLoginUrl("/user/login");
|
||||
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
|
||||
return shiroFilterFactoryBean;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public DefaultWebSecurityManager defaultWebSecurityManager(UserRealm userRealm) {
|
||||
//设置自定义Realm
|
||||
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
||||
securityManager.setRealm(userRealm);
|
||||
//关闭shiro自带的session
|
||||
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
|
||||
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
|
||||
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
|
||||
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
|
||||
securityManager.setSubjectDAO(subjectDAO);
|
||||
return securityManager;
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import java.io.FileInputStream;
|
||||
// 根据文件生成预览
|
||||
@Controller
|
||||
@RestController
|
||||
@RequestMapping("/picture")
|
||||
public class PreviewController {
|
||||
@Resource
|
||||
private FileService fileService;
|
||||
|
@ -0,0 +1,35 @@
|
||||
package xyz.longicorn.driver.controller;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import xyz.longicorn.driver.dto.ApiResult;
|
||||
import xyz.longicorn.driver.dto.LoginModel;
|
||||
import xyz.longicorn.driver.pojo.LoginUser;
|
||||
import xyz.longicorn.driver.service.UserService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
public class UserController {
|
||||
@Resource
|
||||
private UserService userService;
|
||||
|
||||
// @Validated用于验证字段的值
|
||||
// @RequestBody 只能Post请求且 content-type=>application/json
|
||||
@SneakyThrows
|
||||
@PostMapping("/login")
|
||||
public ApiResult login(@Validated @RequestBody LoginModel model) {
|
||||
Thread.sleep(5);
|
||||
final LoginUser user = userService.login(model.getUsername(), model.getPassword());
|
||||
|
||||
return ApiResult.success(user);
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package xyz.longicorn.driver.dao;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.stereotype.Component;
|
||||
import xyz.longicorn.driver.pojo.LoginUser;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class LoginUserDao {
|
||||
private final String REDIS_PREFIX = "driver:user:token:";
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
public LoginUserDao(RedisTemplate redisTemplate) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
private String buildTokenKey(String token) {
|
||||
return REDIS_PREFIX + token;
|
||||
}
|
||||
|
||||
public void refreshToken(String token) {
|
||||
refreshToken(token, 1800);
|
||||
}
|
||||
|
||||
public void refreshToken(String token, long timeout) {
|
||||
redisTemplate.expire(buildTokenKey(token), timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public LoginUser save(LoginUser user) {
|
||||
return save(user, 1800);
|
||||
}
|
||||
|
||||
public LoginUser save(LoginUser user, long timeout) {
|
||||
//更改在redis里面查看key编码问题
|
||||
RedisSerializer redisSerializer = new StringRedisSerializer();
|
||||
redisTemplate.setKeySerializer(redisSerializer);
|
||||
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
|
||||
try {
|
||||
String userJson = (new ObjectMapper()).writeValueAsString(user);
|
||||
vo.set(buildTokenKey(user.getToken()), userJson, timeout, TimeUnit.SECONDS);//设置key并且设置有效时间
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
public LoginUser get(String token) {
|
||||
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
|
||||
final Object o = vo.get(buildTokenKey(token));
|
||||
if (null != o) {
|
||||
try {
|
||||
return (new ObjectMapper()).readValue(o.toString(), LoginUser.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean exists(String token) {
|
||||
return redisTemplate.hasKey(buildTokenKey(token));
|
||||
}
|
||||
|
||||
void deleteByToken(String token) {
|
||||
redisTemplate.delete(buildTokenKey(token));
|
||||
}
|
||||
|
||||
void delete(LoginUser user) {
|
||||
deleteByToken(user.getToken());
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package xyz.longicorn.driver.dao;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import xyz.longicorn.driver.pojo.UserInfo;
|
||||
|
||||
@Mapper
|
||||
public interface UserInfoMapper extends BaseMapper<UserInfo> {
|
||||
}
|
@ -67,8 +67,8 @@ public class FileItem {
|
||||
.setType(f.getType())
|
||||
// .setPath(f.getPath())
|
||||
// .setThumb(f.getPath())
|
||||
.setPath("http://localhost:8080/preview?hash=" + f.getHash())
|
||||
.setThumb("http://localhost:8080/thumb?hash=" + f.getHash())
|
||||
.setPath("http://localhost:8080/picture/preview?hash=" + f.getHash())
|
||||
.setThumb("http://localhost:8080/picture/thumb?hash=" + f.getHash())
|
||||
.setUpdateTime(f.getUpdateTime());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
package xyz.longicorn.driver.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class LoginModel {
|
||||
@NotBlank(message = "用户名必须填写")
|
||||
private String username;
|
||||
@NotBlank(message = "密码必须填写")
|
||||
private String password;
|
||||
// 验证码
|
||||
private String code;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package xyz.longicorn.driver.dto;
|
||||
|
||||
public class UserInfoDto {
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package xyz.longicorn.driver.pojo;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
import org.springframework.data.redis.core.index.Indexed;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Data
|
||||
@RedisHash(value = "driver_login_user", timeToLive = 3600) // 保持名称及有效期
|
||||
public class LoginUser {
|
||||
@Id
|
||||
private String token;
|
||||
@Indexed
|
||||
private String account;
|
||||
private UserInfo userInfo;
|
||||
|
||||
/**
|
||||
* 用户权限
|
||||
*/
|
||||
private Set<String> permissions;
|
||||
/**
|
||||
* 用户角色
|
||||
*/
|
||||
private Set<String> roles;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package xyz.longicorn.driver.pojo;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class ShareInfo {
|
||||
@TableId
|
||||
private String id;
|
||||
private String title;
|
||||
private Integer uid;
|
||||
private Long fileId;
|
||||
private Integer type;
|
||||
private String password;
|
||||
private Integer live;
|
||||
private Date createTime;
|
||||
private Date updateTime;
|
||||
private Integer status;
|
||||
}
|
33
driver/src/main/java/xyz/longicorn/driver/pojo/UserInfo.java
Normal file
33
driver/src/main/java/xyz/longicorn/driver/pojo/UserInfo.java
Normal file
@ -0,0 +1,33 @@
|
||||
package xyz.longicorn.driver.pojo;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class UserInfo {
|
||||
|
||||
private Integer id;
|
||||
private String nickname;
|
||||
private String email;
|
||||
private String account;
|
||||
private String password;
|
||||
private String salt;
|
||||
private String avatar;
|
||||
private Integer sex;
|
||||
private String lastIp;
|
||||
private Date lastLogin;
|
||||
private Date createTime;
|
||||
private Date updateTime;
|
||||
private Integer status;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package xyz.longicorn.driver.pojo.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
|
||||
/**
|
||||
* 文件的分类
|
||||
*/
|
||||
public enum Category {
|
||||
Image(1, "图片"), Document(2, "文档"),
|
||||
Video(3, "视频"), Audio(4, "音频"),
|
||||
Other(5, "其他");
|
||||
|
||||
@EnumValue
|
||||
private final int value;
|
||||
private final String name;
|
||||
|
||||
Category(int value, String name) {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package xyz.longicorn.driver.service;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import xyz.longicorn.driver.config.BizException;
|
||||
import xyz.longicorn.driver.dao.LoginUserDao;
|
||||
import xyz.longicorn.driver.dao.UserInfoMapper;
|
||||
import xyz.longicorn.driver.pojo.LoginUser;
|
||||
import xyz.longicorn.driver.pojo.UserInfo;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class UserService extends ServiceImpl<UserInfoMapper, UserInfo> {
|
||||
@Resource
|
||||
private LoginUserDao loginUserDao;
|
||||
|
||||
/**
|
||||
* 使用用户名和密码登录
|
||||
*
|
||||
* @param account
|
||||
* @param password
|
||||
* @return
|
||||
*/
|
||||
public LoginUser login(String account, String password) {
|
||||
// 查询数据条件
|
||||
QueryWrapper q = new QueryWrapper();
|
||||
// 只能根据账号查询数据
|
||||
q.eq("account", account);
|
||||
q.last("limit 1");
|
||||
UserInfo userInfo = baseMapper.selectOne(q);
|
||||
//
|
||||
if (userInfo == null) throw BizException.create("用户名不正确");
|
||||
// 获取密码并验证
|
||||
String checkPassword = password + (StringUtils.hasLength(userInfo.getSalt()) ? userInfo.getSalt() : "");
|
||||
checkPassword = DigestUtils.md5DigestAsHex(checkPassword.getBytes()); // 生成用于判断的密码
|
||||
|
||||
if (!checkPassword.equals(userInfo.getPassword())) throw BizException.create("登录密码不正确");
|
||||
// 将登录数据保存到redis 用户后续判断
|
||||
LoginUser user = new LoginUser();
|
||||
user.setAccount(account);
|
||||
// 生成接口需要的token
|
||||
user.setToken(IdUtil.fastSimpleUUID()); // 可以使用jwt生成token
|
||||
loginUserDao.save(user); // 保存用户登录信息到redis
|
||||
user.setUserInfo(userInfo);
|
||||
return user;
|
||||
}
|
||||
}
|
162
driver/src/main/java/xyz/longicorn/driver/shiro/AuthFilter.java
Normal file
162
driver/src/main/java/xyz/longicorn/driver/shiro/AuthFilter.java
Normal file
@ -0,0 +1,162 @@
|
||||
package xyz.longicorn.driver.shiro;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import xyz.longicorn.driver.dao.LoginUserDao;
|
||||
import xyz.longicorn.driver.pojo.LoginUser;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
public class AuthFilter extends BasicHttpAuthenticationFilter {
|
||||
|
||||
private LoginUserDao loginUserDao;
|
||||
|
||||
// 构造把需要的资源 由其他对象传输过来
|
||||
public AuthFilter(LoginUserDao loginUserDao){
|
||||
this.loginUserDao = loginUserDao;
|
||||
}
|
||||
|
||||
|
||||
private static final String AUTH_TOKEN_HEADER = "Authorization";
|
||||
|
||||
//判断是否允许通过
|
||||
@Override
|
||||
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
|
||||
log.debug("判断是否允许通过");
|
||||
try {
|
||||
// 执行登录 也可以重写此方法 自行实现登录
|
||||
return executeLogin(request, response);
|
||||
} catch (Exception e) {
|
||||
log.error("执行允许通过异常:" + e.getMessage(), e);
|
||||
//responseError(response, "auth check fail");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 是否需要进行登录请求(false将不会继续后续操纵)
|
||||
* 简单的判断是否存在对应的凭证
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
|
||||
return ((HttpServletRequest) request).getHeader(AUTH_TOKEN_HEADER) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取请求的token头信息
|
||||
* 创建shiro token
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
|
||||
HttpServletRequest req = (HttpServletRequest) request;
|
||||
String token = req.getHeader(AUTHORIZATION_HEADER); //
|
||||
if (StringUtils.hasLength(token)) {
|
||||
return new SimpleHeaderToken(token);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* isAccessAllowed为false时调用,验证失败
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
|
||||
this.sendChallenge(request, response);
|
||||
responseError(response, "login token check failure!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证成功调用 可以将一些信息放入内存,以及更新token有效期
|
||||
*
|
||||
* @param token
|
||||
* @param subject
|
||||
* @param request
|
||||
* @param response
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
|
||||
ServletRequest request, ServletResponse response) throws Exception {
|
||||
// 获取token
|
||||
String tokenValue = (String)token.getPrincipal();
|
||||
// 通过token 获取登录用户信息
|
||||
final LoginUser loginUser = loginUserDao.get(tokenValue);
|
||||
if(null != loginUser){
|
||||
// 更新token有效期
|
||||
loginUserDao.refreshToken(tokenValue);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// 前置判断 - 过滤的主要流程
|
||||
// 过滤的入口 如果返回false 表示不需要过滤 不进行后续代码执行
|
||||
@Override
|
||||
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
|
||||
// 针对 options 直接通过
|
||||
if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
|
||||
((HttpServletResponse) response).setStatus(HttpStatus.OK.value());
|
||||
setCors(response); // 直接允许跨域
|
||||
return false;
|
||||
}
|
||||
//判断用户是否想要登入 -- 判断header中有没有 token
|
||||
if (!isLoginAttempt(request, response)) {
|
||||
responseError(response, "verify token failure(没有token)");
|
||||
return false;
|
||||
}
|
||||
// 如果只是简单判断 可以不用调用父级的preHandle 当然也就不需要重写其他的方法
|
||||
return super.preHandle(request, response);
|
||||
}
|
||||
|
||||
private void setCors(ServletResponse response) {
|
||||
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
|
||||
httpServletResponse.addHeader("Access-Control-Allow-Origin", "*");
|
||||
httpServletResponse.addHeader("Access-Control-Allow-Methods", "*");
|
||||
httpServletResponse.addHeader("Access-Control-Allow-Headers", "*");
|
||||
}
|
||||
|
||||
private void responseError(ServletResponse response, Object msg) {
|
||||
setCors(response);
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
httpResponse.setStatus(401);
|
||||
httpResponse.setCharacterEncoding("UTF-8");
|
||||
httpResponse.setContentType("application/json;charset=UTF-8");
|
||||
try {
|
||||
String rj = msg instanceof String ? "{\"code\":401,\"message\":\"" + (String) msg + "\"}" : new ObjectMapper().writeValueAsString(msg);
|
||||
httpResponse.getWriter().append(rj);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package xyz.longicorn.driver.shiro;
|
||||
|
||||
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
|
||||
import org.apache.shiro.mgt.DefaultSubjectDAO;
|
||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import xyz.longicorn.driver.dao.LoginUserDao;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.Filter;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Configuration
|
||||
public class ShiroConfig {
|
||||
@Resource
|
||||
private LoginUserDao loginUserDao;
|
||||
|
||||
// 配置哪些资源过滤
|
||||
//ShiroFilter过滤所有请求
|
||||
@Bean
|
||||
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
|
||||
|
||||
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
|
||||
|
||||
Map<String, Filter> filterMap = new LinkedHashMap<>(); // 需要有序
|
||||
filterMap.put("auth", new AuthFilter(loginUserDao)); // 设置验证过滤器
|
||||
shiroFilterFactoryBean.setFilters(filterMap);
|
||||
|
||||
//给ShiroFilter配置安全管理器
|
||||
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
|
||||
|
||||
//配置系统公共资源 不要用HashMap来创建Map,资源的配置有先后顺序
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
/**
|
||||
* anon---------------org.apache.shiro.web.filter.authc.AnonymousFilter 没有参数,表示可以匿名使用。
|
||||
* authc--------------org.apache.shiro.web.filter.authc.FormAuthenticationFilter 表示需要认证(登录)才能使用,没有参数
|
||||
* authcBasic---------org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter 没有参数表示httpBasic认证
|
||||
* logout-------------org.apache.shiro.web.filter.authc.LogoutFilter
|
||||
* noSessionCreation--org.apache.shiro.web.filter.session.NoSessionCreationFilter
|
||||
* perms--------------org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter 参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
|
||||
* port---------------org.apache.shiro.web.filter.authz.PortFilter port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。
|
||||
* rest---------------org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter 根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。
|
||||
* roles--------------org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
|
||||
* ssl----------------org.apache.shiro.web.filter.authz.SslFilter 没有参数,表示安全的url请求,协议为https
|
||||
* user---------------org.apache.shiro.web.filter.authz.UserFilter 没有参数表示必须存在用户,当登入操作时不做检查
|
||||
*/
|
||||
//匿名资源 一定是在受限资源上面
|
||||
// 前面时资源的路径 后面是该资源的过滤器
|
||||
map.put("/druid/**", "anon");
|
||||
map.put("/api/user/login", "anon");
|
||||
map.put("/api/user/reg", "anon");
|
||||
map.put("/api/user/forget", "anon");
|
||||
map.put("/api/user/reset", "anon");
|
||||
map.put("/swagger**", "anon"); //
|
||||
map.put("/swagger-resources/**","anon");
|
||||
map.put("/v2/api-docs","anon");
|
||||
map.put("/picture/**","anon");
|
||||
//受限资源需要认证和授权
|
||||
map.put("/**", "auth");
|
||||
//filterChainDefinitions的配置顺序为自上而下,以最上面的为准
|
||||
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
|
||||
|
||||
//身份认证失败,则跳转到登录页面的配置 没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,
|
||||
// 不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。
|
||||
// shiroFilterFactoryBean.setLoginUrl("");
|
||||
//登录成功默认跳转页面,不配置则跳转至”/”。
|
||||
// 如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。
|
||||
// shiroFilterFactoryBean.setSuccessUrl("");
|
||||
//没有权限默认跳转的页面
|
||||
// shiroFilterFactoryBean.setUnauthorizedUrl("");
|
||||
return shiroFilterFactoryBean;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public DefaultWebSecurityManager defaultWebSecurityManager(UserRealm userRealm) {
|
||||
//设置自定义Realm
|
||||
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
||||
securityManager.setRealm(userRealm);
|
||||
//关闭shiro自带的session
|
||||
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
|
||||
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
|
||||
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
|
||||
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
|
||||
securityManager.setSubjectDAO(subjectDAO);
|
||||
return securityManager;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package xyz.longicorn.driver.shiro;
|
||||
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
|
||||
/**
|
||||
* 简单的token
|
||||
*/
|
||||
public class SimpleHeaderToken implements AuthenticationToken {
|
||||
private String token;
|
||||
|
||||
public SimpleHeaderToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return token;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package xyz.longicorn.driver.config;
|
||||
package xyz.longicorn.driver.shiro;
|
||||
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.authc.AuthenticationInfo;
|
||||
@ -9,13 +9,24 @@ import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||
import org.apache.shiro.realm.AuthorizingRealm;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import org.springframework.stereotype.Component;
|
||||
import xyz.longicorn.driver.dao.LoginUserDao;
|
||||
import xyz.longicorn.driver.pojo.LoginUser;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@Component
|
||||
public class UserRealm extends AuthorizingRealm {
|
||||
@Resource
|
||||
private LoginUserDao loginUserDao;
|
||||
// 判断是否支持对应请求token
|
||||
@Override
|
||||
public boolean supports(AuthenticationToken token) {
|
||||
return token != null && token instanceof SimpleHeaderToken;
|
||||
}
|
||||
|
||||
// 处理授权 获取凭证对该凭证赋权
|
||||
@Override
|
||||
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
|
||||
@ -25,9 +36,13 @@ public class UserRealm extends AuthorizingRealm {
|
||||
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
||||
|
||||
Set<String> roleSet = new HashSet<>(); // 角色列表
|
||||
roleSet.add("user");
|
||||
roleSet.add("vip");
|
||||
info.setRoles(roleSet);
|
||||
|
||||
Set<String> permsSet = new HashSet<>(); // 权限列表
|
||||
permsSet.add("query-list");
|
||||
permsSet.add("upload-file");
|
||||
info.setStringPermissions(permsSet);
|
||||
return info;
|
||||
}
|
||||
@ -38,7 +53,8 @@ public class UserRealm extends AuthorizingRealm {
|
||||
throws AuthenticationException {
|
||||
String credentials = (String) authenticationToken.getCredentials(); // 获取凭证
|
||||
System.out.println("用户凭证:" + credentials);
|
||||
//TODO 完成token的验证 返回数据库信息
|
||||
//TODO 完成token的验证(是否有效) 返回数据库信息
|
||||
|
||||
return new SimpleAuthenticationInfo(credentials, credentials, getName());
|
||||
}
|
||||
}
|
98
driver/src/main/java/xyz/longicorn/driver/util/JWTUtil.java
Normal file
98
driver/src/main/java/xyz/longicorn/driver/util/JWTUtil.java
Normal file
@ -0,0 +1,98 @@
|
||||
package xyz.longicorn.driver.util;
|
||||
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.JWTVerifier;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.exceptions.JWTCreationException;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class JWTUtil {
|
||||
|
||||
//这里的token属性配置最好写在配置文件中,这里为了方便直接写成静态属性
|
||||
public static final long EXPIRE_TIME = 60 * 60 * 1000;//token到期时间60分钟,毫秒为单位
|
||||
public static final long REFRESH_EXPIRE_TIME = 30 * 60;//RefreshToken到期时间为30分钟,秒为单位
|
||||
private static final String TOKEN_SECRET = "ljdyaidarking**3nkjnj??"; //密钥盐
|
||||
/**
|
||||
* 发行人
|
||||
*/
|
||||
private static final String ISSUER = "DK";
|
||||
|
||||
/**
|
||||
* 生成token
|
||||
*
|
||||
* @param account
|
||||
* @param currentTime
|
||||
* @return
|
||||
*/
|
||||
public static String create(String account, Long currentTime) {
|
||||
|
||||
String token = null;
|
||||
try {
|
||||
Date expireAt = new Date(currentTime + EXPIRE_TIME);
|
||||
token = JWT.create()
|
||||
.withIssuer(ISSUER)
|
||||
.withClaim("account", account)//存放数据
|
||||
.withClaim("currentTime", currentTime)
|
||||
.withExpiresAt(expireAt)//过期时间
|
||||
.sign(Algorithm.HMAC256(TOKEN_SECRET));
|
||||
} catch (IllegalArgumentException | JWTCreationException je) {
|
||||
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param : [token]
|
||||
* @return : java.lang.Boolean
|
||||
* @throws :
|
||||
* @Description :token验证
|
||||
* @author : lj
|
||||
* @date : 2020-1-31 22:59
|
||||
*/
|
||||
public static Boolean verify(String token) throws Exception {
|
||||
try {
|
||||
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET))
|
||||
.withIssuer(ISSUER).build();//创建token验证器
|
||||
DecodedJWT decodedJWT = jwtVerifier.verify(token);
|
||||
System.out.println("认证通过:");
|
||||
System.out.println("account: " + decodedJWT.getClaim("account").asString());
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
System.out.println("过期时间:" + sdf.format(decodedJWT.getExpiresAt()));
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String getAccount(HttpServletRequest request) {
|
||||
String token = request.getHeader("token");
|
||||
return getAccount(token);
|
||||
}
|
||||
|
||||
public static String getAccount(String token) {
|
||||
if (token == null) return null;
|
||||
try {
|
||||
DecodedJWT decodedJWT = JWT.decode(token);
|
||||
return decodedJWT.getClaim("account").asString();
|
||||
} catch (JWTCreationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Long getCurrentTime(String token) {
|
||||
try {
|
||||
DecodedJWT decodedJWT = JWT.decode(token);
|
||||
return decodedJWT.getClaim("currentTime").asLong();
|
||||
} catch (JWTCreationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package xyz.longicorn.driver.util;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class RedisUtil {
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
-- 编号、用户编号、目录名称、上级目录编号、目录路径、创建时间、状态
|
||||
create table folder_info
|
||||
(
|
||||
id bigint(20) primary key auto_increment,
|
||||
id bigint(20) UNSIGNED primary key auto_increment,
|
||||
uid int(10) null default 1,
|
||||
name varchar(20) not null,
|
||||
parent_id bigint(20) null default 0 comment '上级目录编号,0表示根目录',
|
||||
@ -17,17 +17,17 @@ create table folder_info
|
||||
# 编号、用户编号、文件名、目录编号 、类型、大小、位置、创建时间、状态、hash
|
||||
create table file_info
|
||||
(
|
||||
id bigint(20) not null primary key auto_increment,
|
||||
uid int(10) not null,
|
||||
name varchar(50) not null,
|
||||
hash varchar(32) not null comment '文件MD5特征码',
|
||||
folder_id bigint(20) null default 0 comment '所在目录的编号,0表示根目录',
|
||||
type varchar(5) null,
|
||||
size int null default 0,
|
||||
path varchar(500) not null comment '文件的物理存储位置,可以是本地路径也可以是一个网址', -- file://d:/a/b/c.txt http://
|
||||
create_time datetime default current_timestamp,
|
||||
id bigint(20) UNSIGNED not null primary key auto_increment,
|
||||
uid int(10) not null,
|
||||
name varchar(50) not null,
|
||||
hash varchar(32) not null comment '文件MD5特征码',
|
||||
folder_id bigint(20) null default 0 comment '所在目录的编号,0表示根目录',
|
||||
type varchar(5) null,
|
||||
size int null default 0,
|
||||
path varchar(500) not null comment '文件的物理存储位置,可以是本地路径也可以是一个网址', -- file://d:/a/b/c.txt http://
|
||||
create_time datetime default current_timestamp,
|
||||
update_time datetime on update current_timestamp,
|
||||
status tinyint(1) default 1,
|
||||
status tinyint(1) default 1,
|
||||
index ix_file_name (name)
|
||||
);
|
||||
-- 编号、分享名称、用户编号、分享的数据编号、分类类型(file|folder)、提取码、分享有效期、状态
|
||||
@ -45,3 +45,19 @@ create table share_info
|
||||
status tinyint(1) default 1,
|
||||
index ix_file_name (title)
|
||||
);
|
||||
CREATE TABLE user_info
|
||||
(
|
||||
id int(10) UNSIGNED NOT NULL,
|
||||
nickname varchar(60) NOT NULL COMMENT '昵称',
|
||||
email varchar(60) DEFAULT '邮箱',
|
||||
account varchar(60) NOT NULL COMMENT '登录账号',
|
||||
password varchar(32) NOT NULL COMMENT '登录密码',
|
||||
salt varchar(10) COMMENT '登录密码混淆字符',
|
||||
avatar varchar(255) COMMENT '登录密码',
|
||||
sex tinyint(1) default 0 comment '0:未知 1男 2女',
|
||||
last_login datetime null,
|
||||
last_ip varchar(50) null,
|
||||
create_time datetime default current_timestamp,
|
||||
update_time datetime on update current_timestamp,
|
||||
status tinyint(1) default 1
|
||||
);
|
22
web/package-lock.json
generated
22
web/package-lock.json
generated
@ -18,7 +18,8 @@
|
||||
"vue": "^3.2.25",
|
||||
"vue-router": "^4.0.15",
|
||||
"vue-simple-context-menu": "^4.0.2",
|
||||
"vue-upload-component": "^2.8.22"
|
||||
"vue-upload-component": "^2.8.22",
|
||||
"vuex": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^2.3.1",
|
||||
@ -1213,6 +1214,17 @@
|
||||
"version": "2.8.22",
|
||||
"resolved": "https://registry.npmmirror.com/vue-upload-component/-/vue-upload-component-2.8.22.tgz",
|
||||
"integrity": "sha512-AJpETqiZrgqs8bwJQpWTFrRg3i6s7cUodRRZVnb1f94Jvpd0YYfzGY4zluBqPmssNSkUaYu7EteXaK8aW17Osw=="
|
||||
},
|
||||
"node_modules/vuex": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.0.2.tgz",
|
||||
"integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.0.0-beta.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@ -2020,6 +2032,14 @@
|
||||
"version": "2.8.22",
|
||||
"resolved": "https://registry.npmmirror.com/vue-upload-component/-/vue-upload-component-2.8.22.tgz",
|
||||
"integrity": "sha512-AJpETqiZrgqs8bwJQpWTFrRg3i6s7cUodRRZVnb1f94Jvpd0YYfzGY4zluBqPmssNSkUaYu7EteXaK8aW17Osw=="
|
||||
},
|
||||
"vuex": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.0.2.tgz",
|
||||
"integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
|
||||
"requires": {
|
||||
"@vue/devtools-api": "^6.0.0-beta.11"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,8 @@
|
||||
"vue": "^3.2.25",
|
||||
"vue-router": "^4.0.15",
|
||||
"vue-simple-context-menu": "^4.0.2",
|
||||
"vue-upload-component": "^2.8.22"
|
||||
"vue-upload-component": "^2.8.22",
|
||||
"vuex": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^2.3.1",
|
||||
|
@ -9,13 +9,20 @@
|
||||
file.type == 'folder' ? formatDate(file.createTime) : formatSize(file.size)
|
||||
}}
|
||||
</div>
|
||||
<el-image-viewer v-if="imageViewer.show"
|
||||
:url-list="imageViewer.previewSrcList"
|
||||
:hide-on-click-modal="true" @close="imageViewer.show=false"
|
||||
:teleported="true"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ref} from "@vue/reactivity";
|
||||
import {ref} from "vue";
|
||||
import {dayjs} from "element-plus";
|
||||
import FileIcon from "./FileIcon.vue";
|
||||
import {reactive} from "vue";
|
||||
import strings from "../service/strings";
|
||||
import {useRouter, useRoute} from 'vue-router'
|
||||
|
||||
export default {
|
||||
name: "FileBlockItem",
|
||||
@ -32,19 +39,27 @@ export default {
|
||||
setup(props) {
|
||||
/**
|
||||
*
|
||||
* @type {FileIconInstance}
|
||||
* @type {Ref<FileIconInstance>}
|
||||
*/
|
||||
const fileItemIcon = ref(null)
|
||||
const router = useRouter(), route = useRoute();
|
||||
const file = props.file;
|
||||
const imageViewer = reactive({
|
||||
show: false,
|
||||
previewSrcList: [file.path]
|
||||
})
|
||||
const showFile = (file) => {
|
||||
if (file.type != 'folder') {
|
||||
console.log(fileItemIcon)
|
||||
// fileItemIcon.showPreview();
|
||||
if (file.type == 'folder') {
|
||||
router.push(route.path + '?path=' + file.path);
|
||||
return;
|
||||
} else if (strings.isImage(file.type)) {
|
||||
imageViewer.show = true
|
||||
return;
|
||||
}
|
||||
location.hash = '?path=' + file.path
|
||||
// location.hash = '?path=' + file.path
|
||||
}
|
||||
return {
|
||||
file: props.file, showFile, fileItemIcon
|
||||
file: props.file, showFile, fileItemIcon, imageViewer
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<span style="display:inline-block">
|
||||
<el-image v-if="type == 'picture'"
|
||||
<el-image v-if="type == 'picture'" ref="img"
|
||||
:src="currentSrc" :previewSrcList="[currentPreview]"
|
||||
:initial-index="4" fit="cover" :hide-on-click-modal="true"
|
||||
/>
|
||||
@ -14,6 +14,7 @@ import FolderIcon from '../assets/icons/folder.svg'
|
||||
import UnknownIcon from '../assets/icons/icon.svg'
|
||||
import ExeIcon from '../assets/icons/exe.svg'
|
||||
import {ElMessage} from "element-plus";
|
||||
import strings from "../service/strings";
|
||||
|
||||
export default {
|
||||
// 定义组件所需的属性
|
||||
@ -38,7 +39,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
const type = this.file.type.toLowerCase(); // 拿到文件类型
|
||||
if (['png', 'jpg', 'jpeg', 'gif', 'webp'].includes(type)) {
|
||||
if (strings.isImage(type)) {
|
||||
this.currentSrc = this.file.thumb || UnknownIcon; // 图片
|
||||
this.currentPreview = this.file.path || UnknownIcon; // 图片
|
||||
this.type = 'picture';
|
||||
@ -49,7 +50,9 @@ export default {
|
||||
methods: {
|
||||
showPreview(file) {
|
||||
//
|
||||
ElMessage('暂不支持此文件的预览');
|
||||
// ElMessage('暂不支持此文件的预览');
|
||||
const img = this.$refs.img;
|
||||
img.clickHandler();
|
||||
console.log(file)
|
||||
}
|
||||
}
|
||||
|
@ -3,16 +3,18 @@ import {createApp} from "vue";
|
||||
// 导入element-plus的依赖
|
||||
import ElementPlus from "element-plus";
|
||||
import "element-plus/dist/index.css";
|
||||
import './assets/main.less'
|
||||
|
||||
import ContextMenu from 'vue-simple-context-menu'
|
||||
import 'vue-simple-context-menu/dist/vue-simple-context-menu.css';
|
||||
import './assets/main.less'
|
||||
import store from './service/store'
|
||||
import router from './router'
|
||||
|
||||
// 导入入口组件
|
||||
import App from "./App.vue";
|
||||
import index from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
app.component('ContextMenu', ContextMenu);
|
||||
app.use(ElementPlus)
|
||||
.use(index) // 使用路由实例
|
||||
.use(router) // 使用路由实例
|
||||
.use(store)
|
||||
.mount("#app");
|
||||
|
@ -1,29 +1,92 @@
|
||||
<style scoped>
|
||||
#page-login {
|
||||
width: 500px;
|
||||
margin: 200px auto 0;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
background-color: pink;
|
||||
}
|
||||
|
||||
.login-wrapper {
|
||||
width: 800px;
|
||||
margin: auto;
|
||||
background-color: #fff;
|
||||
padding: 30px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.left-picture {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-right: dashed 1px #ccc;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
margin: auto;
|
||||
fill: #999999;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
width: 350px;
|
||||
padding: 50px;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.input-item {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.forget-tips {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reset-link {
|
||||
text-decoration: none;
|
||||
color: #999999;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div id="page-login">
|
||||
<el-form
|
||||
ref="form"
|
||||
:model="loginForm"
|
||||
:rules="rules"
|
||||
status-icon
|
||||
label-width="120px"
|
||||
class="demo-ruleForm"
|
||||
>
|
||||
<el-form-item label="" prop="username">
|
||||
<el-input v-model="loginForm.username" placeholder="用户名" autocomplete="off"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="" prop="password">
|
||||
<el-input v-model="loginForm.password" placeholder="登录密码" type="password" autocomplete="off"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button style="width: 100%" type="primary" @click="submitForm">登录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="login-wrapper d-flex">
|
||||
<div class="flex-1 left-picture">
|
||||
<svg class="logo-icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000">
|
||||
<path
|
||||
d="M549.766 97.925c19.974 4.609 50.703 35.339 62.995 55.312 35.339-13.828 36.875 41.484 46.094 55.312 23.047-16.901 46.094-35.339 46.094-55.312 0-21.51-30.729-35.339-30.729-41.484 0-12.292 38.411-12.292 41.484-12.292 50.703 0 106.016 39.948 106.016 96.797 0 27.656-9.219 41.484-33.802 70.677h32.266c21.51 0 27.656-3.073 55.312 7.682C923.125 294.591 960 348.367 960 389.852c0 3.073 0 9.219-1.536 13.828-4.609 3.073-12.292 3.073-16.901 4.609-15.365 1.536-32.266 6.146-47.63 7.682-47.63 0-50.703 0-53.776-1.536-1.536 1.536-1.536 0-1.536 4.609 0 3.073 7.682 44.557 10.755 67.604 9.219 56.849 12.292 115.234 19.974 173.62 1.536 7.682 7.682 15.365 9.219 23.047 3.073 13.828 6.146 27.656 6.146 39.948 0 153.646-245.833 208.958-321.119 208.958h-86.042c-115.234-9.219-248.906-52.24-301.145-138.281-4.609-7.682-18.437-39.948-18.437-50.703v-36.875c3.073-16.901 7.682-33.802 21.51-50.703v-84.505l12.292-132.135c-12.292 1.536-33.802 1.536-38.411 1.536-21.51 0-38.411-3.073-61.458-6.146-7.682-1.536-18.437-3.073-24.583-6.146-4.609-1.536-3.073-12.292-3.073-13.828 0-44.557 44.557-107.552 98.333-119.844 4.609-1.536 13.828-1.536 19.974-3.073l41.484-1.536c-13.828-10.755-36.875-46.094-36.875-59.922v-29.193c13.828-58.385 62.995-82.969 106.016-82.969 1.536 0 41.484 0 41.484 12.292 0 6.146-30.729 19.974-30.729 41.484 0 1.536 6.146 32.266 16.901 32.266 3.073 0-1.536-3.073 3.073-3.073 3.073 0 32.266 10.755 36.875 10.755h12.292l3.073-3.073c-19.974-16.901-30.729-36.875-36.875-53.776 12.292 7.682 18.438 9.219 29.193 9.219 27.656 0 46.094-15.365 78.359-29.193 24.583-10.755 52.24-13.828 78.359-18.437-12.292-9.219-24.583-16.901-36.875-23.047l38.411-1.536c7.68 1.537 15.362 4.61 23.044 6.146z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="login-form">
|
||||
<h1 class="title">天牛网盘</h1>
|
||||
<el-form
|
||||
ref="form"
|
||||
:model="loginForm"
|
||||
:rules="rules"
|
||||
status-icon
|
||||
size="large"
|
||||
@keyup.enter="submitForm"
|
||||
>
|
||||
<el-form-item label="" prop="username" class="input-item">
|
||||
<el-input v-model="loginForm.username" placeholder="用户名" autocomplete="off"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="" prop="password" class="input-item">
|
||||
<el-input v-model="loginForm.password" placeholder="登录密码" type="password" autocomplete="off"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button :loading="loginLoading" style="width: 100%" type="primary" @click="submitForm">登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<div class="forget-tips">
|
||||
<router-link class="reset-link" to="/forget">密码忘记了,点我重置密码!</router-link>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -31,11 +94,12 @@
|
||||
import {defineComponent, reactive, ref} from 'vue'
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import {useRouter} from 'vue-router'
|
||||
import {useStore} from "vuex";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Login',
|
||||
method:{
|
||||
gotoMain(){
|
||||
method: {
|
||||
gotoMain() {
|
||||
this.$router.push('/');
|
||||
}
|
||||
},
|
||||
@ -50,6 +114,7 @@ export default defineComponent({
|
||||
password: '',
|
||||
username: ''
|
||||
})
|
||||
|
||||
const checkPassword = (rule, value, callback) => {
|
||||
if (!value) callback(Error('请填写密码')) // 验证不通过
|
||||
else callback() // 验证通过
|
||||
@ -63,18 +128,31 @@ export default defineComponent({
|
||||
]
|
||||
})
|
||||
const router = useRouter(); // 获取路由对象
|
||||
const store = useStore();
|
||||
const loginLoading = ref(false)
|
||||
const submitForm = () => {
|
||||
form.value.validate((isValid) => {
|
||||
if(loginLoading.value) return;
|
||||
// 验证表单
|
||||
form.value.validate(async (isValid) => {
|
||||
if (isValid) {
|
||||
ElMessage.success('登录成功')
|
||||
router.replace('/')
|
||||
} else {
|
||||
ElMessage.info('验证不通过')
|
||||
// 设置loading未true
|
||||
loginLoading.value = true
|
||||
try {
|
||||
const _data = await store.dispatch('login', loginForm)
|
||||
// 登录成功后 需要保存token
|
||||
// localStorage.setItem('token',_data);
|
||||
ElMessage.success('登录成功')
|
||||
router.replace('/').then().catch();
|
||||
} catch (e) {
|
||||
ElMessage.error(e.message || '登录错误')
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
return {
|
||||
loginForm, submitForm, rules, form
|
||||
loginForm, submitForm, rules, form, loginLoading
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1,13 +1,26 @@
|
||||
import {createRouter, createWebHistory} from 'vue-router'
|
||||
import routes from "./routes";
|
||||
|
||||
import routes from "./routes";
|
||||
import store from "../service/store";
|
||||
|
||||
// 路由器实例对象
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
});
|
||||
router.beforeEach((to,from,next)=>{
|
||||
console.log(from.path ,'==>',to.path)
|
||||
next()
|
||||
//
|
||||
const loginPath = '/login', // 登录路径
|
||||
anonymousPaths = [loginPath, '/reg']; // 匿名可访问路径
|
||||
// 全局导航守卫
|
||||
router.beforeEach((to, from, next) => {
|
||||
const toPath = to.path; // 获取要展示得路径
|
||||
if (anonymousPaths.includes(toPath)) next() // 判断是否可匿名访问
|
||||
else {
|
||||
// 验证token
|
||||
if (!store.getters.userToken) next(loginPath) // 没有登录直接打开登录页面
|
||||
// if(!localStorage.getItem('token')) next(loginPath)
|
||||
else next();
|
||||
}
|
||||
})
|
||||
|
||||
export default router;
|
@ -1,3 +1,9 @@
|
||||
import store from "./store";
|
||||
import {
|
||||
ElMessage
|
||||
} from "element-plus";
|
||||
import router from "../router";
|
||||
|
||||
const API_PATH = "http://localhost:8080"
|
||||
|
||||
/**
|
||||
@ -8,6 +14,10 @@ const API_PATH = "http://localhost:8080"
|
||||
*/
|
||||
function request(api, method = 'GET', postData = {}, progressChange = null) {
|
||||
//return fetch(API_PATH + api).then(res => res.json());
|
||||
/**
|
||||
*
|
||||
* @type {RequestInit}
|
||||
*/
|
||||
let options = {
|
||||
method
|
||||
};
|
||||
@ -21,7 +31,7 @@ function request(api, method = 'GET', postData = {}, progressChange = null) {
|
||||
} else if (method.toUpperCase() == 'FILE') {
|
||||
options = {
|
||||
method: 'POST',
|
||||
body: postData // 参数
|
||||
body: postData, // 参数
|
||||
}
|
||||
} else {
|
||||
options = {
|
||||
@ -32,16 +42,35 @@ function request(api, method = 'GET', postData = {}, progressChange = null) {
|
||||
body: JSON.stringify(postData) // 参数
|
||||
}
|
||||
}
|
||||
let Authorization = store.getters.userToken
|
||||
if (Authorization) {
|
||||
options = {
|
||||
headers: {
|
||||
Authorization
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
/**
|
||||
*
|
||||
* @param {ApiResult} result
|
||||
*/
|
||||
const processResult = result => {
|
||||
const processResult = (result, a, b) => {
|
||||
if (result.code === 0) { // 判断响应码是否正常
|
||||
resolve(result.data)
|
||||
} else { // 有异常 直接抛出错误
|
||||
} else {
|
||||
const route = router.currentRoute.value;
|
||||
// 未登录或token无效
|
||||
if (result.code == 401) {
|
||||
store.commit('clearToken') // 清除token
|
||||
ElMessage.error('登录凭证验证失败,请重新登录');
|
||||
if (route.path != '/login') router.replace('/login').then(() => console.log('auth show login')).catch();
|
||||
reject(null)
|
||||
return;
|
||||
}
|
||||
// 其他异常 直接抛出错误
|
||||
reject(new Error(result.message))
|
||||
}
|
||||
}
|
||||
@ -49,6 +78,7 @@ function request(api, method = 'GET', postData = {}, progressChange = null) {
|
||||
.then(res => res.json())
|
||||
.then(processResult)
|
||||
.catch(e => {
|
||||
console.log(e)
|
||||
reject(e);
|
||||
})
|
||||
});
|
||||
@ -56,9 +86,11 @@ function request(api, method = 'GET', postData = {}, progressChange = null) {
|
||||
|
||||
export default {
|
||||
user: {
|
||||
login() {
|
||||
login(params) {
|
||||
return request('/api/user/login', 'POST', params);
|
||||
},
|
||||
signup() {
|
||||
signup(params) {
|
||||
return request('/api/user/reg', 'POST', params);
|
||||
}
|
||||
},
|
||||
folder: {
|
||||
|
53
web/src/service/store.js
Normal file
53
web/src/service/store.js
Normal file
@ -0,0 +1,53 @@
|
||||
import {createStore} from "vuex"
|
||||
import api from "./api";
|
||||
|
||||
const TOKEN_KEY = 'user_auth_token';
|
||||
const store = createStore({
|
||||
state: {
|
||||
/**
|
||||
* @type {UserInfo}
|
||||
*/
|
||||
loginUser: null,
|
||||
token: null
|
||||
},
|
||||
mutations: {
|
||||
// 此选项中的方法必须同步
|
||||
setLoginUserInfo(state, info) {
|
||||
state.loginUser = info;
|
||||
},
|
||||
setToken(state, token) {
|
||||
state.token = token;
|
||||
// sessionStorage.setItem(TOKEN_KEY,token);
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
},
|
||||
clearToken(state){
|
||||
state.token = null;
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
sessionStorage.removeItem(TOKEN_KEY)
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
login({commit}, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
api.user.login(data).then(
|
||||
/**
|
||||
*
|
||||
* @param {LoginUser} ret
|
||||
*/
|
||||
ret => {
|
||||
commit('setToken', ret.token);
|
||||
commit('setLoginUserInfo', ret.userInfo)
|
||||
resolve()
|
||||
}).catch(e => reject(e))
|
||||
});
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
userToken(state) {
|
||||
const token = state.token || sessionStorage.getItem(TOKEN_KEY) || localStorage.getItem(TOKEN_KEY);
|
||||
if(!state.token) state.token = token;
|
||||
return token
|
||||
}
|
||||
}
|
||||
})
|
||||
export default store
|
12
web/src/service/strings.js
Normal file
12
web/src/service/strings.js
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
*
|
||||
* @param {string} type
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isImage(type) {
|
||||
return type ? ['png', 'jpg', 'jpeg', 'gif', 'webp'].includes(type.toLowerCase()) : false;
|
||||
}
|
||||
|
||||
export default {
|
||||
isImage
|
||||
}
|
23
web/src/service/type.d.ts
vendored
23
web/src/service/type.d.ts
vendored
@ -35,3 +35,26 @@ declare type ShareInfo = {
|
||||
updateTime?: string,
|
||||
status: number
|
||||
}
|
||||
|
||||
declare type UserInfo = {
|
||||
account: string
|
||||
avatar: string
|
||||
createTime: string
|
||||
email: string
|
||||
id: number
|
||||
lastIp: string
|
||||
lastLogin: string
|
||||
nickname: string
|
||||
password: string
|
||||
salt: string
|
||||
sex: number
|
||||
status: number
|
||||
updateTime: string
|
||||
}
|
||||
declare type LoginUser = {
|
||||
account: string
|
||||
permissions: string[]
|
||||
roles: string[]
|
||||
token: string
|
||||
userInfo: UserInfo
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user