Compare commits
4 Commits
Author | SHA1 | Date |
---|---|---|
|
e4be9007f1 | |
|
16957e2ee6 | |
|
5e4aac8a97 | |
|
4f31caf354 |
|
@ -0,0 +1,10 @@
|
|||
# Getting Started
|
||||
|
||||
### Reference Documentation
|
||||
|
||||
For further reference, please consider the following sections:
|
||||
|
||||
* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html)
|
||||
* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.7.16/maven-plugin/reference/html/)
|
||||
* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.7.16/maven-plugin/reference/html/#build-image)
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.7.16</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>VueDemoV20</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>VueDemoV2.0</name>
|
||||
<description>VueDemoV2.0</description>
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!--jsonwebtoken 生成token的库 -->
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>3.8.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>3.5.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.12</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,14 @@
|
|||
package com.example.vuedemov20;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.example.vuedemov20;
|
||||
|
||||
public class Product {
|
||||
private String state;
|
||||
private String approver;
|
||||
private String result;
|
||||
//补全getset方法
|
||||
public String getState() {
|
||||
return state;
|
||||
|
||||
}
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
public String getApprover() {
|
||||
return approver;
|
||||
|
||||
}
|
||||
public void setApprover(String approver) {
|
||||
this.approver = approver;
|
||||
}
|
||||
public String getResult() {
|
||||
return result;
|
||||
}
|
||||
public void setResult(String result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.example.vuedemov20.Token;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
//入口拦截,设置哪些接口需要拦截或不拦截(保护后端接口 防止未经授权的访问)
|
||||
@Configuration
|
||||
public class IntercepterConfig implements WebMvcConfigurer {
|
||||
private final TokenInterceptor tokenInterceptor;
|
||||
|
||||
// 构造方法
|
||||
public IntercepterConfig(TokenInterceptor tokenInterceptor) {
|
||||
this.tokenInterceptor = tokenInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
//excludePathPatterns用来配置不需要拦截的路径
|
||||
List<String> excludePath = new ArrayList<>();//List用来保存所有不需要拦截的路径
|
||||
excludePath.add("/register"); //注册
|
||||
excludePath.add("/login"); //登录
|
||||
|
||||
|
||||
|
||||
registry.addInterceptor(tokenInterceptor)//添加名为tokenInterceptor的拦截器
|
||||
.addPathPatterns("/**") //指定拦截所有路径
|
||||
.excludePathPatterns(excludePath);//排除不需要拦截的路径
|
||||
WebMvcConfigurer.super.addInterceptors(registry);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.example.vuedemov20.Token;
|
||||
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
|
||||
@Service
|
||||
public class RedisUtil {
|
||||
@Resource
|
||||
private RedisTemplate<String, String> stringRedisTemplate;//这是一个使用redis的API,可以直接用StringRedisTemplate
|
||||
|
||||
public void addTokens(String username, String token) {//存入token
|
||||
ValueOperations valueOperations = stringRedisTemplate.opsForValue();
|
||||
valueOperations.set(username, token);
|
||||
}
|
||||
public String getTokens(String username) {//获取token
|
||||
return stringRedisTemplate.opsForValue().get(username);
|
||||
}
|
||||
|
||||
public void delTokens(String username) {//删除token在前端已经进行
|
||||
stringRedisTemplate.delete(username);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.example.vuedemov20.Token;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.JWTVerifier;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
|
||||
import java.util.Date;
|
||||
public class TokenGenerate {
|
||||
|
||||
private static final long EXPIRE_TIME= 15*60*1000;
|
||||
private static final String TOKEN_SECRET="tokenqkj"; //密钥盐
|
||||
public String generateToken(String username){
|
||||
String token = null;
|
||||
try{
|
||||
Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME);
|
||||
token = JWT.create()
|
||||
.withIssuer("auth0")
|
||||
.withClaim("username", username)
|
||||
.withExpiresAt(expiresAt)
|
||||
.sign(Algorithm.HMAC256(TOKEN_SECRET));
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
/**
|
||||
* 签名验证
|
||||
*/
|
||||
public static boolean verify(String token){
|
||||
try {
|
||||
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build();
|
||||
DecodedJWT jwt = verifier.verify(token);
|
||||
System.out.println("认证通过:");
|
||||
System.out.println("issuer: " + jwt.getIssuer());
|
||||
System.out.println("username: " + jwt.getClaim("username").asString());
|
||||
System.out.println("过期时间: " + jwt.getExpiresAt());
|
||||
return true;
|
||||
} catch (Exception e){
|
||||
System.out.println("没通过");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package com.example.vuedemov20.Token;
|
||||
|
||||
|
||||
import com.example.vuedemov20.vueControll;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
//token拦截器,对拦截下的接口检查其的token是否符合只有
|
||||
// 在提供一个有效的token时才能通过验证,否则给出认证失败的响应。
|
||||
@Component
|
||||
public class TokenInterceptor implements HandlerInterceptor {
|
||||
@Resource
|
||||
RedisUtil redisUtil;
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception{
|
||||
//Axios 发起跨域请求前,浏览器也会首先发起 OPTIONS 预检请求。检查服务器是否允许跨域访问。
|
||||
if(request.getMethod().equals("OPTIONS")){
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
System.out.println("允许跨域访问");
|
||||
return true;
|
||||
}
|
||||
|
||||
response.setCharacterEncoding("utf-8");
|
||||
String token = redisUtil.getTokens(vueControll.Username);
|
||||
if(token != null){
|
||||
boolean result = TokenGenerate.verify(token);
|
||||
if(result){
|
||||
System.out.println("通过拦截器");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
PrintWriter out = null;
|
||||
response.getWriter().write("认证失败,错误码:50000");
|
||||
return false;//原为false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package com.example.vuedemov20;
|
||||
|
||||
import com.example.vuedemov20.Token.RedisUtil;
|
||||
import com.example.vuedemov20.Token.TokenGenerate;
|
||||
import mysql.*;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin //加上CrossOrigin可解决跨域问题
|
||||
public class vueControll {
|
||||
@Resource
|
||||
RedisUtil redisUtil;
|
||||
String token;
|
||||
public static String Username;
|
||||
@RequestMapping("/login")
|
||||
public String tologin(@RequestParam("username") String username,@RequestParam("password") String password) throws SQLException {
|
||||
ResultSet resultRegister = CheckRegister.MyAccount(username);
|
||||
Username = username;
|
||||
if(resultRegister.next()) {
|
||||
if(username.equals(resultRegister.getString("username"))&&password.equals(resultRegister.getString("password"))) {
|
||||
token = new TokenGenerate().generateToken(username);
|
||||
redisUtil.addTokens(username,token);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
return "false";
|
||||
}
|
||||
|
||||
@RequestMapping("/register")
|
||||
public String toRegister(@RequestParam("username") String username,@RequestParam("password") String password) throws SQLException {
|
||||
ResultSet resultRegister = CheckRegister.MyAccount(username);
|
||||
//还没有注册过
|
||||
if(resultRegister.next()){
|
||||
resultRegister.getString("username").equals(username);
|
||||
return "false";
|
||||
}
|
||||
AddRegister.add(username, password);
|
||||
return "true";
|
||||
|
||||
}
|
||||
|
||||
@PostMapping("/product")
|
||||
public ResponseEntity<List<Product>> getProduct(@RequestBody Product product) throws SQLException {
|
||||
//提取数据到List
|
||||
ResultSet resultProduct = CheckProduct.MyAccount();
|
||||
// 封装结果集到Product列表
|
||||
List<Product> productList = new ArrayList<>();
|
||||
while (resultProduct.next()) {
|
||||
Product p = new Product();
|
||||
p.setState(resultProduct.getString("state"));
|
||||
p.setApprover(resultProduct.getString("approver"));
|
||||
p.setResult(resultProduct.getString("result"));
|
||||
productList.add(p);
|
||||
}
|
||||
return ResponseEntity.ok(productList);
|
||||
}
|
||||
|
||||
@PostMapping("/addProduct")
|
||||
public ResponseEntity<List<Product>> addProduct(@RequestBody Product product) throws SQLException {
|
||||
//存入数据库
|
||||
AddProduct.add(product.getState(),product.getApprover(),product.getResult());
|
||||
//进行整体数据传输
|
||||
ResultSet resultProduct = CheckProduct.MyAccount();
|
||||
// 封装结果集到Product列表
|
||||
List<Product> productList = new ArrayList<>();
|
||||
while (resultProduct.next()) {
|
||||
Product p = new Product();
|
||||
p.setState(resultProduct.getString("state"));
|
||||
p.setApprover(resultProduct.getString("approver"));
|
||||
p.setResult(resultProduct.getString("result"));
|
||||
productList.add(p);
|
||||
}
|
||||
return ResponseEntity.ok(productList);
|
||||
}
|
||||
|
||||
@PostMapping("/deleteProduct")
|
||||
public ResponseEntity<List<Product>> deleteProduct(@RequestBody Product product) throws Exception {
|
||||
DeleteProduct.delete(product.getState(), product.getApprover(), product.getResult());
|
||||
//进行整体数据传输
|
||||
ResultSet resultProduct = CheckProduct.MyAccount();
|
||||
// 封装结果集到Product列表
|
||||
List<Product> productList = new ArrayList<>();
|
||||
while (resultProduct.next()) {
|
||||
Product p = new Product();
|
||||
p.setState(resultProduct.getString("state"));
|
||||
p.setApprover(resultProduct.getString("approver"));
|
||||
p.setResult(resultProduct.getString("result"));
|
||||
productList.add(p);
|
||||
}
|
||||
return ResponseEntity.ok(productList);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package mysql;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class AddProduct {
|
||||
public static void add(String state,String approver,String result) throws SQLException {
|
||||
String sql ="INSERT INTO product(state,approver,result) VALUES (?,?,?) ";
|
||||
PreparedStatement preparedStatement;
|
||||
preparedStatement = JDBC.getConnection().prepareStatement(sql);
|
||||
preparedStatement.setString(1,state);
|
||||
preparedStatement.setString(2,approver);
|
||||
preparedStatement.setString(3,result);
|
||||
|
||||
|
||||
preparedStatement.executeLargeUpdate();//插入数据
|
||||
preparedStatement.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package mysql;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class AddRegister {
|
||||
public static void add(String username,String password) throws SQLException {
|
||||
String sql ="INSERT INTO register(username,password) VALUES (?,?) ";
|
||||
PreparedStatement preparedStatement;
|
||||
preparedStatement = JDBC.getConnection().prepareStatement(sql);
|
||||
preparedStatement.setString(1,username);
|
||||
preparedStatement.setString(2,password);
|
||||
|
||||
|
||||
preparedStatement.executeLargeUpdate();//插入数据
|
||||
preparedStatement.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package mysql;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class CheckProduct {
|
||||
public static ResultSet MyAccount() throws SQLException {
|
||||
String sql = "SELECT* FROM product";
|
||||
ResultSet resultSet;
|
||||
PreparedStatement preparedStatement;
|
||||
preparedStatement = JDBC.getConnection().prepareStatement(sql);
|
||||
resultSet = preparedStatement.executeQuery();//查询数据
|
||||
return resultSet;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package mysql;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class CheckRegister {
|
||||
public static ResultSet MyAccount(String username) throws SQLException {
|
||||
String sql = "SELECT* FROM register where username = ?";
|
||||
ResultSet resultSet;
|
||||
PreparedStatement preparedStatement;
|
||||
preparedStatement = JDBC.getConnection().prepareStatement(sql);
|
||||
preparedStatement.setString(1, username);
|
||||
resultSet = preparedStatement.executeQuery();//查询数据
|
||||
return resultSet;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package mysql;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
|
||||
public class DeleteProduct {
|
||||
public static void delete(String state,String approver,String result ) throws Exception{
|
||||
String sql = "delete from product where state=? AND approver = ? AND result = ?";
|
||||
PreparedStatement preparedStatement;
|
||||
preparedStatement = JDBC.getConnection().prepareStatement(sql);
|
||||
|
||||
preparedStatement.setString(1, state);
|
||||
preparedStatement.setString(2, approver);
|
||||
preparedStatement.setString(3, result);
|
||||
|
||||
preparedStatement.executeUpdate();//插入数据
|
||||
preparedStatement.close();
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package mysql;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class JDBC {
|
||||
// static {
|
||||
// try {
|
||||
// Class.forName("com.mysql.cj.jdbc.Driver");
|
||||
// } catch (ClassNotFoundException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
// }
|
||||
private static String url="jdbc:mysql://127.0.0.1:3306/vue";
|
||||
private static String username="root";
|
||||
private static String password="123456";
|
||||
|
||||
public static Connection getConnection() {
|
||||
Connection connection;
|
||||
try {
|
||||
connection= DriverManager.getConnection(url,username,password);
|
||||
return connection;
|
||||
} catch (SQLException e) {
|
||||
System.out.println("数据库未连接");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package mysql;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RegisterConfig {
|
||||
private String username;
|
||||
private String password;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
server.port=8081
|
||||
|
||||
spring.datasource.url="jdbc:mysql://127.0.0.1:3306/vue"
|
||||
spring.datasource.username="root"
|
||||
spring.datasource.password="123456"
|
||||
|
||||
db.url=jdbc:mysql://127.0.0.1:3306/vue
|
||||
db.user=root
|
||||
db.password=123456
|
||||
db.driver=com.mysql.jdbc.Driver
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<!--
|
||||
mapper为映射的根节点,用来管理DAO接口
|
||||
namespace指定DAO接口的完整类名,表示mapper配置文件管理哪个DAO接口(包.接口名)
|
||||
mybatis会依据这个接口动态创建一个实现类去实现这个接口,而这个实现类是一个Mapper对象
|
||||
-->
|
||||
<mapper namespace="lrs.user">
|
||||
<!--
|
||||
id = "接口中的方法名"
|
||||
#{} :表示占位符,等价于 ? 这是mybatis框架的语法
|
||||
parameterType = "接口中传入方法的参数类型"
|
||||
resultType = "返回实体类对象:包.类名" 处理结果集 自动封装
|
||||
注意:sql语句后不要出现";"号
|
||||
-->
|
||||
<!-- 查询 根据id查询用户信息
|
||||
select标签用于查询的标签
|
||||
id:标签的唯一标识
|
||||
resultType:定义返回的类型 把sql查询的结果封装到哪个实体类中
|
||||
#{} :表示占位符,等价于 ? 这是mybatis框架的语法
|
||||
-->
|
||||
<!-- 例子 -->
|
||||
<select id="findById" resultType="User">
|
||||
select * from register where username= #{username}
|
||||
</select>
|
||||
<insert id="add" parameterType="User">
|
||||
insert into register values(#{username},#{password})
|
||||
</insert>
|
||||
</mapper>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
<configuration>
|
||||
<typeAliases>
|
||||
<package name="com.example.mybatistest"/>
|
||||
</typeAliases>
|
||||
<environments default="development">
|
||||
<environment id="development">
|
||||
<transactionManager type="JDBC"/>
|
||||
<dataSource type="POOLED">
|
||||
<property name="driver" value="com.mysql.jdbc.Driver"/>
|
||||
<property name="url" value="jdbc:mysql://127.0.0.1:3306/vue"/>
|
||||
<property name="username" value="root"/>
|
||||
<property name="password" value="123456"/>
|
||||
</dataSource>
|
||||
</environment>
|
||||
</environments>
|
||||
<!--注册mapper配置文件(mapper文件路径配置)
|
||||
url:网络上的映射文件
|
||||
注意:映射配置文件位置要和映射器位置一样,如:映射器在com.mycode.dao里,
|
||||
那么配置文件就应该在resources的com/mycode/dao目录下,否则会报
|
||||
Could not find resource com.mycode.dao.UserMapper.xml类似错误
|
||||
-->
|
||||
<mappers>
|
||||
<!--下面编写mapper映射文件↓↓↓↓↓ 参考格式:<mapper resource="dao/UserMapper.xml"/> -->
|
||||
<mapper resource="mapper/UserMapper.xml"></mapper>
|
||||
</mappers>
|
||||
</configuration>
|
|
@ -0,0 +1,13 @@
|
|||
package com.example.vuedemov20;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class ApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
|
@ -0,0 +1,17 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
'extends': [
|
||||
'plugin:vue/essential',
|
||||
'eslint:recommended'
|
||||
],
|
||||
parserOptions: {
|
||||
parser: '@babel/eslint-parser'
|
||||
},
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
|
@ -0,0 +1 @@
|
|||
shamefully-hoist=true
|
|
@ -0,0 +1,24 @@
|
|||
# vue-demo
|
||||
|
||||
## Project setup
|
||||
```
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
pnpm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "vue-demo",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.5.0",
|
||||
"core-js": "^3.8.3",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"vue": "^2.6.14",
|
||||
"vue-router": "^3.5.1",
|
||||
"vuex": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.16",
|
||||
"@babel/eslint-parser": "^7.12.16",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-plugin-router": "~5.0.0",
|
||||
"@vue/cli-plugin-vuex": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"sass": "^1.32.7",
|
||||
"sass-loader": "^12.0.0",
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,5 @@
|
|||
body, div, dl,dt,dd,ul,ol,li,h2,h3,h4,h5,h6,pre,form,fieldset,legend,input,textarea{
|
||||
margin: 0;padding:0;}
|
||||
body{text-align: center;}
|
||||
a{ text-decoration: none;}
|
||||
li{ list-style: none;}
|
|
@ -0,0 +1,33 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import axios from 'axios'
|
||||
import "./css/common.css"
|
||||
|
||||
Vue.prototype.$http = axios
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
import ElementUI from 'element-ui';
|
||||
import 'element-ui/lib/theme-chalk/index.css';
|
||||
Vue.use(ElementUI);
|
||||
|
||||
// 添加请求拦截器,在请求头中加token
|
||||
//使用 axios 的请求拦截器来实现在请求头中自动带上 token 的功能:
|
||||
axios.interceptors.request.use(
|
||||
config => {
|
||||
if (localStorage.getItem('token')) {
|
||||
config.headers.token = localStorage.getItem('token');
|
||||
}
|
||||
return config;
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
|
@ -0,0 +1,74 @@
|
|||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import Home from "../views/Home.vue"
|
||||
import Login from "../views/Login.vue"
|
||||
import Product from "../views/Product.vue"
|
||||
import BlogEdit from "../views/BlogEdit.vue"
|
||||
import jwt_decode from "jwt-decode";
|
||||
import store from "../store/index.js"
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Home",
|
||||
component: Home
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
name: "Login",
|
||||
component: Login
|
||||
},
|
||||
{
|
||||
path: "/product",
|
||||
name: "Product",
|
||||
component: Product
|
||||
},
|
||||
{
|
||||
path: "/blogedit",
|
||||
name: "BlogEdit",
|
||||
component: BlogEdit
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
mode:"history",
|
||||
routes
|
||||
})
|
||||
|
||||
//只有在跳转页面时才会执行
|
||||
router.beforeEach((to, from, next) => {
|
||||
|
||||
//to 将要访问的路径
|
||||
//from 代表从哪个路径而来
|
||||
//next() 代表放行 next('xxx') 强制放行的xxx的路径
|
||||
if(to.path==='/login'){
|
||||
next();
|
||||
}else{
|
||||
const tokenStr = window.localStorage.getItem('token');
|
||||
|
||||
console.log(tokenStr);
|
||||
if(!tokenStr){
|
||||
alert("请你先登陆")
|
||||
return next('/login')
|
||||
}
|
||||
const token = jwt_decode(tokenStr);
|
||||
const currentTime = Date.now() / 1000;
|
||||
if (currentTime > token.exp) {
|
||||
store.commit("delToken"); //删除token
|
||||
console.log("已超时");
|
||||
alert("登陆过期,请你重新登陆")
|
||||
return next('/login')
|
||||
}
|
||||
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
export default router
|
|
@ -0,0 +1,25 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
Vue.use(Vuex);
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state: {
|
||||
//定义一个state存储token信息
|
||||
token: localStorage.getItem('token') ? localStorage.getItem('token') : ''
|
||||
},
|
||||
mutations: {
|
||||
//登录后通过setToken存入token token保存在state和localStorage中,放入请求头中
|
||||
setToken (state,token) {
|
||||
state.token =token;
|
||||
localStorage.setItem("token",token.token); //存储token
|
||||
},
|
||||
//登出后通过delToken清除token
|
||||
delToken (state) {
|
||||
state.token = '';
|
||||
localStorage.removeItem("token"); //删除token
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default store;
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<template>
|
||||
<div class="m_content">
|
||||
<h3>欢迎来到xbx的博客</h3>
|
||||
<div class="maction">
|
||||
<span><el-link type="primary" href="/#">主页</el-link></span>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<span><el-link type="success" href="/#">发表博客</el-link></span>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="label">标题</div>
|
||||
<el-input placeholder="请输入内容" clearable >
|
||||
</el-input>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.m_content {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.maction {
|
||||
margin: 10px 0px;
|
||||
}
|
||||
/* 使标题和对话框同行 */
|
||||
.block {
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
|
||||
<div class="m_content">
|
||||
<h3>欢迎来到xbx的博客</h3>
|
||||
<div class="maction">
|
||||
<span><el-link type="primary" href="/#">主页</el-link></span>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<span><el-link type="success" href="/#">发表博客</el-link></span>
|
||||
</div>
|
||||
<div class="block">
|
||||
<el-timeline>
|
||||
<el-timeline-item timestamp="2018/4/12" placement="top">
|
||||
<el-card>
|
||||
<h4>更新 Github 模板</h4>
|
||||
<p>王小虎 提交于 2018/4/12 20:46</p>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
<el-timeline-item timestamp="2018/4/3" placement="top">
|
||||
<el-card>
|
||||
<h4>更新 Github 模板</h4>
|
||||
<p>王小虎 提交于 2018/4/3 20:46</p>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
<el-timeline-item timestamp="2018/4/2" placement="top">
|
||||
<el-card>
|
||||
<a href="#">更新 Github 模板</a>
|
||||
<p>王小虎 提交于 2018/4/2 20:46</p>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "HomePage"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.m_content {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.maction {
|
||||
margin: 10px 0px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,171 @@
|
|||
<template>
|
||||
<div class="login">
|
||||
<el-card class="box-card">
|
||||
<div class="clearfix">
|
||||
<span>大数据专业实验室</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-tabs stretch>
|
||||
<el-tab-pane label="登陆">
|
||||
<el-form :model="loginForm" status-icon :rules="rules" label-width="80px" ref="loginForm">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input type="text" v-model="loginForm.username"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input type="password" v-model="loginForm.password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitLoginForm()">登陆</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="注册">
|
||||
<el-form :model="registerForm" status-icon :rules="rules" label-width="80px" ref="loginForm">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input type="text" v-model="registerForm.username"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input type="password" v-model="registerForm.password"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="确认密码" prop="configurePassword">
|
||||
<el-input type="password" v-model="registerForm.configurePassword"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitRegisterForm()">注册</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapMutations } from 'vuex';//引入vuex下的方法
|
||||
|
||||
export default {
|
||||
data() {
|
||||
var validateUsername = (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error("请输入用户名"));
|
||||
} else if (value.length < 4) {
|
||||
callback(new Error("长度不够"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
var validatePassword = (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error("请输入密码"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
var validateConfigurePassword = (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error("请输入密码")); //callback的作用是显示那个图标的
|
||||
} else if (value !== this.registerForm.password) {
|
||||
callback(new Error("两次密码不一致"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loginForm: {
|
||||
username: "",
|
||||
password: "",
|
||||
},
|
||||
registerForm: {
|
||||
username: "",
|
||||
password: "",
|
||||
configurePassword: ""
|
||||
},
|
||||
|
||||
|
||||
|
||||
rules: {
|
||||
username: [
|
||||
{
|
||||
validator: validateUsername,
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
validator: validatePassword,
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
configurePasswnpmord: [
|
||||
{
|
||||
validator: validateConfigurePassword,
|
||||
trigger: "blur",
|
||||
}
|
||||
]
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
//映射mapMutations 辅助函数映射 Vuex mutations
|
||||
// - 让组件可以直接调用 Vuex 中的 mutations 方法
|
||||
...mapMutations(['setToken']),
|
||||
|
||||
submitLoginForm() {
|
||||
let self = this //在回调函数中调用this会创建与this对象的新绑定,而不是正则函数表达式中的 Vue 对象。
|
||||
var url = "http://localhost:8081/login?username=" + this.loginForm.username + "&password=" + this.loginForm.password
|
||||
axios.get(url).then(function (response) { //匿名回调函数,导致回调函数中的 this 不再指向 Vue 实例
|
||||
var jsonObject = response.data;
|
||||
var jsonString = JSON.stringify(jsonObject)
|
||||
if (jsonString !== "false") {
|
||||
window.localStorage.setItem("token", jsonString);
|
||||
self.setToken({ token: jsonString });
|
||||
self.$router.push('/product')
|
||||
} else {
|
||||
alert("账号或密码错误!")
|
||||
}
|
||||
})
|
||||
},
|
||||
submitRegisterForm() {
|
||||
var url = "http://127.0.0.1:8081/register?username=" + this.registerForm.username + "&password=" + this.registerForm.password
|
||||
axios.get(url).then(function (response) {
|
||||
var jsonObject = response.data;
|
||||
var jsonString = JSON.stringify(jsonObject)
|
||||
if (jsonString === "true") {
|
||||
alert("注册成功!")
|
||||
} else {
|
||||
alert("该用户已注册!")
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
|
||||
name: "LoginPage",
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.login {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.box-card {
|
||||
width: 500px;
|
||||
margin: 100px auto;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,196 @@
|
|||
<template>
|
||||
<div class="">
|
||||
<div class="inline">
|
||||
<p>审批状态:</p>
|
||||
<el-select v-model="value" clearable placeholder="请选择">
|
||||
<el-option v-for="item in stateOptions" :key="item.value" :label="item.label"
|
||||
:value="item.value"></el-option>
|
||||
</el-select>
|
||||
<!-- 中间来个空格隔开 -->
|
||||
<div style="width: 20px"></div>
|
||||
<p>采购类别:</p>
|
||||
<el-select v-model="categoryValue" clearable placeholder="请选择">
|
||||
<el-option v-for="item in categoryOptions" :key="item.categoryValue" :label="item.label"
|
||||
:value="item.categoryValue"></el-option>
|
||||
</el-select>
|
||||
<div style="width: 20px"></div>
|
||||
<el-button type="primary">查询</el-button>
|
||||
<div style="width: 20px"></div>
|
||||
|
||||
<el-button type="primary" @click="createWord()">生成表格</el-button>
|
||||
</div>
|
||||
<!-- 设置按钮在最左边 type属性会改变按钮的样式,表示不同的意义。-->
|
||||
<div class="left">
|
||||
<el-button type="primary" @click="increase()">新增</el-button>
|
||||
<el-dialog title="新增" :visible.sync="dialogFormVisible">
|
||||
<el-form :model="form">
|
||||
<el-form-item label="审批状态">
|
||||
<el-input v-model="form.state" autocomplete="off" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批人">
|
||||
<el-input v-model="form.approver" autocomplete="off" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核结果">
|
||||
<el-input v-model="form.result" autocomplete="off" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div>
|
||||
<el-button @click="dialogFormVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="sureIncrease()">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
<br>
|
||||
<!-- 缩小表格以适应屏幕 -->
|
||||
<div style="width: 100%; overflow-x: auto" class="table">
|
||||
<el-table :data="tableData" style="width: 100%" border>
|
||||
<el-table-column type="selection" width="55"></el-table-column>
|
||||
<el-table-column prop="state" label="审批状态" width="100"></el-table-column>
|
||||
<el-table-column prop="approver" label="审批人" width="100"></el-table-column>
|
||||
<el-table-column prop="result" label="审核结果" width="100"></el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-button @click="handleClick(scope.row)" type="primary" size=20px>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: [Array, String]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const formData = {
|
||||
state: this.form.state,
|
||||
approver: this.form.approver,
|
||||
result: this.form.result,
|
||||
};
|
||||
axios.post('http://127.0.0.1:8081/product', formData).then((response) => {
|
||||
this.tableData = response.data;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
handleClick(row) {
|
||||
axios.post("http://127.0.0.1:8081/deleteProduct", row).then((res) => {
|
||||
const productList = res.data;
|
||||
this.tableData = productList;
|
||||
this.$message.success('删除产品成功');
|
||||
console.log(res.data);
|
||||
|
||||
});
|
||||
},
|
||||
//新增功能
|
||||
|
||||
increase() {
|
||||
this.dialogFormVisible = true;
|
||||
},
|
||||
sureIncrease() {
|
||||
const formData = {
|
||||
state: this.form.state,
|
||||
approver: this.form.approver,
|
||||
result: this.form.result,
|
||||
};
|
||||
var url = "http://127.0.0.1:8081/addProduct"
|
||||
axios.post(url, formData).then((res) => {
|
||||
const productList = res.data;
|
||||
this.tableData = productList;
|
||||
this.$message.success('新增产品成功');
|
||||
console.log(res.data);
|
||||
});
|
||||
this.dialogFormVisible = false;
|
||||
},
|
||||
createWord(){
|
||||
// var url = "http://localhost:8081/createWord"
|
||||
// axios.post(url, this.tableData).then((res) => {
|
||||
// this.$message.success('生成表格成功');
|
||||
// console.log(res.data);
|
||||
// });
|
||||
this.$message.success('功能已下线');
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
data() {
|
||||
return {
|
||||
stateOptions: [
|
||||
{
|
||||
value: "选项1",
|
||||
label: "待审批",
|
||||
},
|
||||
{
|
||||
value: "选项2",
|
||||
label: "已通过",
|
||||
},
|
||||
{
|
||||
value: "选项3",
|
||||
label: "未通过",
|
||||
},
|
||||
],
|
||||
value: "",
|
||||
categoryOptions: [
|
||||
{
|
||||
categoryValue: "选项1",
|
||||
label: "办公用具",
|
||||
},
|
||||
{
|
||||
categoryValue: "选项2",
|
||||
label: "生产材料",
|
||||
},
|
||||
{
|
||||
categoryValue: "选项3",
|
||||
label: "部门活动",
|
||||
},
|
||||
],
|
||||
categoryValue: "",
|
||||
tableData: [],
|
||||
|
||||
dialogFormVisible: false,
|
||||
form: {
|
||||
state: "",
|
||||
approver: "",
|
||||
result: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
name: "ProductPage",
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 使整个项目居中显示 */
|
||||
.parent {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 将left类中的按钮设置在审批状态下方 */
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
margin-left: 350px;
|
||||
}
|
||||
|
||||
.table {
|
||||
margin-left: 350px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,4 @@
|
|||
const { defineConfig } = require('@vue/cli-service')
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true
|
||||
})
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,13 @@
|
|||
package com.example.vuedemov20;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.example.vuedemov20;
|
||||
|
||||
public class Product {
|
||||
private String state;
|
||||
private String approver;
|
||||
private String result;
|
||||
//补全getset方法
|
||||
public String getState() {
|
||||
return state;
|
||||
|
||||
}
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
public String getApprover() {
|
||||
return approver;
|
||||
|
||||
}
|
||||
public void setApprover(String approver) {
|
||||
this.approver = approver;
|
||||
}
|
||||
public String getResult() {
|
||||
return result;
|
||||
}
|
||||
public void setResult(String result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.example.vuedemov20.Token;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
//入口拦截,设置哪些接口需要拦截或不拦截(保护后端接口 防止未经授权的访问)
|
||||
@Configuration
|
||||
public class IntercepterConfig implements WebMvcConfigurer {
|
||||
private final TokenInterceptor tokenInterceptor;
|
||||
|
||||
// 构造方法
|
||||
public IntercepterConfig(TokenInterceptor tokenInterceptor) {
|
||||
this.tokenInterceptor = tokenInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
//excludePathPatterns用来配置不需要拦截的路径
|
||||
List<String> excludePath = new ArrayList<>();//List用来保存所有不需要拦截的路径
|
||||
excludePath.add("/register"); //注册
|
||||
excludePath.add("/login"); //登录
|
||||
|
||||
|
||||
|
||||
registry.addInterceptor(tokenInterceptor)//添加名为tokenInterceptor的拦截器
|
||||
.addPathPatterns("/**") //指定拦截所有路径
|
||||
.excludePathPatterns(excludePath);//排除不需要拦截的路径
|
||||
WebMvcConfigurer.super.addInterceptors(registry);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.example.vuedemov20.Token;
|
||||
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
|
||||
@Service
|
||||
public class RedisUtil {
|
||||
@Resource
|
||||
private RedisTemplate<String, String> stringRedisTemplate;//这是一个使用redis的API,可以直接用StringRedisTemplate
|
||||
|
||||
public void addTokens(String username, String token) {//存入token
|
||||
ValueOperations valueOperations = stringRedisTemplate.opsForValue();
|
||||
valueOperations.set(username, token);
|
||||
}
|
||||
public String getTokens(String username) {//获取token
|
||||
return stringRedisTemplate.opsForValue().get(username);
|
||||
}
|
||||
|
||||
public void delTokens(String username) {//删除token在前端已经进行
|
||||
stringRedisTemplate.delete(username);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.example.vuedemov20.Token;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.JWTVerifier;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
|
||||
import java.util.Date;
|
||||
public class TokenGenerate {
|
||||
|
||||
private static final long EXPIRE_TIME= 15*60*1000;
|
||||
private static final String TOKEN_SECRET="tokenqkj"; //密钥盐
|
||||
public String generateToken(String username){
|
||||
String token = null;
|
||||
try{
|
||||
Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME);
|
||||
token = JWT.create()
|
||||
.withIssuer("auth0")
|
||||
.withClaim("username", username)
|
||||
.withExpiresAt(expiresAt)
|
||||
.sign(Algorithm.HMAC256(TOKEN_SECRET));
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
/**
|
||||
* 签名验证
|
||||
*/
|
||||
public static boolean verify(String token){
|
||||
try {
|
||||
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build();
|
||||
DecodedJWT jwt = verifier.verify(token);
|
||||
System.out.println("认证通过:");
|
||||
System.out.println("issuer: " + jwt.getIssuer());
|
||||
System.out.println("username: " + jwt.getClaim("username").asString());
|
||||
System.out.println("过期时间: " + jwt.getExpiresAt());
|
||||
return true;
|
||||
} catch (Exception e){
|
||||
System.out.println("没通过");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package com.example.vuedemov20.Token;
|
||||
|
||||
|
||||
import com.example.vuedemov20.vueControll;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
//token拦截器,对拦截下的接口检查其的token是否符合只有
|
||||
// 在提供一个有效的token时才能通过验证,否则给出认证失败的响应。
|
||||
@Component
|
||||
public class TokenInterceptor implements HandlerInterceptor {
|
||||
@Resource
|
||||
RedisUtil redisUtil;
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception{
|
||||
//Axios 发起跨域请求前,浏览器也会首先发起 OPTIONS 预检请求。检查服务器是否允许跨域访问。
|
||||
if(request.getMethod().equals("OPTIONS")){
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
System.out.println("允许跨域访问");
|
||||
return true;
|
||||
}
|
||||
|
||||
response.setCharacterEncoding("utf-8");
|
||||
String token = redisUtil.getTokens(vueControll.Username);
|
||||
if(token != null){
|
||||
boolean result = TokenGenerate.verify(token);
|
||||
if(result){
|
||||
System.out.println("通过拦截器");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
PrintWriter out = null;
|
||||
response.getWriter().write("认证失败,错误码:50000");
|
||||
return false;//原为false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package com.example.vuedemov20;
|
||||
|
||||
import com.example.vuedemov20.Token.RedisUtil;
|
||||
import com.example.vuedemov20.Token.TokenGenerate;
|
||||
import mysql.*;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin //加上CrossOrigin可解决跨域问题
|
||||
public class vueControll {
|
||||
@Resource
|
||||
RedisUtil redisUtil;
|
||||
String token;
|
||||
public static String Username;
|
||||
@RequestMapping("/login")
|
||||
public String tologin(@RequestParam("username") String username,@RequestParam("password") String password) throws SQLException {
|
||||
ResultSet resultRegister = CheckRegister.MyAccount(username);
|
||||
Username = username;
|
||||
if(resultRegister.next()) {
|
||||
if(username.equals(resultRegister.getString("username"))&&password.equals(resultRegister.getString("password"))) {
|
||||
token = new TokenGenerate().generateToken(username);
|
||||
redisUtil.addTokens(username,token);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
return "false";
|
||||
}
|
||||
|
||||
@RequestMapping("/register")
|
||||
public String toRegister(@RequestParam("username") String username,@RequestParam("password") String password) throws SQLException {
|
||||
ResultSet resultRegister = CheckRegister.MyAccount(username);
|
||||
//还没有注册过
|
||||
if(resultRegister.next()){
|
||||
resultRegister.getString("username").equals(username);
|
||||
return "false";
|
||||
}
|
||||
AddRegister.add(username, password);
|
||||
return "true";
|
||||
|
||||
}
|
||||
|
||||
@PostMapping("/product")
|
||||
public ResponseEntity<List<Product>> getProduct(@RequestBody Product product) throws SQLException {
|
||||
//提取数据到List
|
||||
ResultSet resultProduct = CheckProduct.MyAccount();
|
||||
// 封装结果集到Product列表
|
||||
List<Product> productList = new ArrayList<>();
|
||||
while (resultProduct.next()) {
|
||||
Product p = new Product();
|
||||
p.setState(resultProduct.getString("state"));
|
||||
p.setApprover(resultProduct.getString("approver"));
|
||||
p.setResult(resultProduct.getString("result"));
|
||||
productList.add(p);
|
||||
}
|
||||
return ResponseEntity.ok(productList);
|
||||
}
|
||||
|
||||
@PostMapping("/addProduct")
|
||||
public ResponseEntity<List<Product>> addProduct(@RequestBody Product product) throws SQLException {
|
||||
//存入数据库
|
||||
AddProduct.add(product.getState(),product.getApprover(),product.getResult());
|
||||
//进行整体数据传输
|
||||
ResultSet resultProduct = CheckProduct.MyAccount();
|
||||
// 封装结果集到Product列表
|
||||
List<Product> productList = new ArrayList<>();
|
||||
while (resultProduct.next()) {
|
||||
Product p = new Product();
|
||||
p.setState(resultProduct.getString("state"));
|
||||
p.setApprover(resultProduct.getString("approver"));
|
||||
p.setResult(resultProduct.getString("result"));
|
||||
productList.add(p);
|
||||
}
|
||||
return ResponseEntity.ok(productList);
|
||||
}
|
||||
|
||||
@PostMapping("/deleteProduct")
|
||||
public ResponseEntity<List<Product>> deleteProduct(@RequestBody Product product) throws Exception {
|
||||
DeleteProduct.delete(product.getState(), product.getApprover(), product.getResult());
|
||||
//进行整体数据传输
|
||||
ResultSet resultProduct = CheckProduct.MyAccount();
|
||||
// 封装结果集到Product列表
|
||||
List<Product> productList = new ArrayList<>();
|
||||
while (resultProduct.next()) {
|
||||
Product p = new Product();
|
||||
p.setState(resultProduct.getString("state"));
|
||||
p.setApprover(resultProduct.getString("approver"));
|
||||
p.setResult(resultProduct.getString("result"));
|
||||
productList.add(p);
|
||||
}
|
||||
return ResponseEntity.ok(productList);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package mysql;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class AddProduct {
|
||||
public static void add(String state,String approver,String result) throws SQLException {
|
||||
String sql ="INSERT INTO product(state,approver,result) VALUES (?,?,?) ";
|
||||
PreparedStatement preparedStatement;
|
||||
preparedStatement = JDBC.getConnection().prepareStatement(sql);
|
||||
preparedStatement.setString(1,state);
|
||||
preparedStatement.setString(2,approver);
|
||||
preparedStatement.setString(3,result);
|
||||
|
||||
|
||||
preparedStatement.executeLargeUpdate();//插入数据
|
||||
preparedStatement.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package mysql;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class AddRegister {
|
||||
public static void add(String username,String password) throws SQLException {
|
||||
String sql ="INSERT INTO register(username,password) VALUES (?,?) ";
|
||||
PreparedStatement preparedStatement;
|
||||
preparedStatement = JDBC.getConnection().prepareStatement(sql);
|
||||
preparedStatement.setString(1,username);
|
||||
preparedStatement.setString(2,password);
|
||||
|
||||
|
||||
preparedStatement.executeLargeUpdate();//插入数据
|
||||
preparedStatement.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package mysql;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class CheckProduct {
|
||||
public static ResultSet MyAccount() throws SQLException {
|
||||
String sql = "SELECT* FROM product";
|
||||
ResultSet resultSet;
|
||||
PreparedStatement preparedStatement;
|
||||
preparedStatement = JDBC.getConnection().prepareStatement(sql);
|
||||
resultSet = preparedStatement.executeQuery();//查询数据
|
||||
return resultSet;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package mysql;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class CheckRegister {
|
||||
public static ResultSet MyAccount(String username) throws SQLException {
|
||||
String sql = "SELECT* FROM register where username = ?";
|
||||
ResultSet resultSet;
|
||||
PreparedStatement preparedStatement;
|
||||
preparedStatement = JDBC.getConnection().prepareStatement(sql);
|
||||
preparedStatement.setString(1, username);
|
||||
resultSet = preparedStatement.executeQuery();//查询数据
|
||||
return resultSet;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package mysql;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
|
||||
public class DeleteProduct {
|
||||
public static void delete(String state,String approver,String result ) throws Exception{
|
||||
String sql = "delete from product where state=? AND approver = ? AND result = ?";
|
||||
PreparedStatement preparedStatement;
|
||||
preparedStatement = JDBC.getConnection().prepareStatement(sql);
|
||||
|
||||
preparedStatement.setString(1, state);
|
||||
preparedStatement.setString(2, approver);
|
||||
preparedStatement.setString(3, result);
|
||||
|
||||
preparedStatement.executeUpdate();//插入数据
|
||||
preparedStatement.close();
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package mysql;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class JDBC {
|
||||
// static {
|
||||
// try {
|
||||
// Class.forName("com.mysql.cj.jdbc.Driver");
|
||||
// } catch (ClassNotFoundException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
// }
|
||||
private static String url="jdbc:mysql://127.0.0.1:3306/vue";
|
||||
private static String username="root";
|
||||
private static String password="123456";
|
||||
|
||||
public static Connection getConnection() {
|
||||
Connection connection;
|
||||
try {
|
||||
connection= DriverManager.getConnection(url,username,password);
|
||||
return connection;
|
||||
} catch (SQLException e) {
|
||||
System.out.println("数据库未连接");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package mysql;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RegisterConfig {
|
||||
private String username;
|
||||
private String password;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
server.port=8081
|
||||
|
||||
spring.datasource.url="jdbc:mysql://127.0.0.1:3306/vue"
|
||||
spring.datasource.username="root"
|
||||
spring.datasource.password="123456"
|
||||
|
||||
db.url=jdbc:mysql://127.0.0.1:3306/vue
|
||||
db.user=root
|
||||
db.password=123456
|
||||
db.driver=com.mysql.jdbc.Driver
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<!--
|
||||
mapper为映射的根节点,用来管理DAO接口
|
||||
namespace指定DAO接口的完整类名,表示mapper配置文件管理哪个DAO接口(包.接口名)
|
||||
mybatis会依据这个接口动态创建一个实现类去实现这个接口,而这个实现类是一个Mapper对象
|
||||
-->
|
||||
<mapper namespace="lrs.user">
|
||||
<!--
|
||||
id = "接口中的方法名"
|
||||
#{} :表示占位符,等价于 ? 这是mybatis框架的语法
|
||||
parameterType = "接口中传入方法的参数类型"
|
||||
resultType = "返回实体类对象:包.类名" 处理结果集 自动封装
|
||||
注意:sql语句后不要出现";"号
|
||||
-->
|
||||
<!-- 查询 根据id查询用户信息
|
||||
select标签用于查询的标签
|
||||
id:标签的唯一标识
|
||||
resultType:定义返回的类型 把sql查询的结果封装到哪个实体类中
|
||||
#{} :表示占位符,等价于 ? 这是mybatis框架的语法
|
||||
-->
|
||||
<!-- 例子 -->
|
||||
<select id="findById" resultType="User">
|
||||
select * from register where username= #{username}
|
||||
</select>
|
||||
<insert id="add" parameterType="User">
|
||||
insert into register values(#{username},#{password})
|
||||
</insert>
|
||||
</mapper>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
<configuration>
|
||||
<typeAliases>
|
||||
<package name="com.example.mybatistest"/>
|
||||
</typeAliases>
|
||||
<environments default="development">
|
||||
<environment id="development">
|
||||
<transactionManager type="JDBC"/>
|
||||
<dataSource type="POOLED">
|
||||
<property name="driver" value="com.mysql.jdbc.Driver"/>
|
||||
<property name="url" value="jdbc:mysql://127.0.0.1:3306/vue"/>
|
||||
<property name="username" value="root"/>
|
||||
<property name="password" value="123456"/>
|
||||
</dataSource>
|
||||
</environment>
|
||||
</environments>
|
||||
<!--注册mapper配置文件(mapper文件路径配置)
|
||||
url:网络上的映射文件
|
||||
注意:映射配置文件位置要和映射器位置一样,如:映射器在com.mycode.dao里,
|
||||
那么配置文件就应该在resources的com/mycode/dao目录下,否则会报
|
||||
Could not find resource com.mycode.dao.UserMapper.xml类似错误
|
||||
-->
|
||||
<mappers>
|
||||
<!--下面编写mapper映射文件↓↓↓↓↓ 参考格式:<mapper resource="dao/UserMapper.xml"/> -->
|
||||
<mapper resource="mapper/UserMapper.xml"></mapper>
|
||||
</mappers>
|
||||
</configuration>
|
|
@ -0,0 +1,13 @@
|
|||
package com.example.vuedemov20;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class ApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue