Commit 3371d41c authored by panyf's avatar panyf
Browse files

[CP]完善JWT鉴权,实现方案从多页面改为单页面,使用dialog进行登录操作

parent c897835e
......@@ -49,7 +49,24 @@
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
<version>0.12.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.1</version>
<scope>runtime</scope>
</dependency>
</dependencies>
......
src/main/frontend/public/favicon.ico

4.19 KB | W: | H:

src/main/frontend/public/favicon.ico

66.1 KB | W: | H:

src/main/frontend/public/favicon.ico
src/main/frontend/public/favicon.ico
src/main/frontend/public/favicon.ico
src/main/frontend/public/favicon.ico
  • 2-up
  • Swipe
  • Onion skin
......@@ -5,7 +5,7 @@
<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>
<title>CompileProxy</title>
</head>
<body>
<noscript>
......
<template>
<div id="app" class="flex flex-col items-center justify-center h-screen">
<div class="upload-container">
<el-upload
ref="uploadRef"
:action="uploadUrl"
:auto-upload="false"
:on-change="handleFileChange"
:file-list="fileList"
accept=".cc"
:multiple="false"
>
<template #trigger>
<el-button size="small" type="primary">选取文件</el-button>
<!-- 登录对话框 -->
<el-dialog :visible.sync="loginDialogVisible" title="登录">
<template v-slot:default>
<el-form @submit.prevent="handleLogin">
<el-form-item label="用户名">
<el-input v-model="loginForm.username"></el-input>
</el-form-item>
<el-form-item label="密码">
<el-input type="password" v-model="loginForm.password"></el-input>
</el-form-item>
<!-- 修改:移除 native-type 属性,使用 @click 触发登录方法 -->
<el-button type="primary" @click="handleLogin">登录</el-button>
<el-alert
v-if="loginMessage"
:title="loginMessage"
:type="loginMessageType"
show-icon
class="mt-4"
></el-alert>
</el-form>
</template>
</el-dialog>
<div class="button-container">
<el-button
style="margin-left: 10px;"
size="small"
type="success"
:disabled="isUploading"
@click="uploadFile"
type="info"
@click="loginDialogVisible = true"
>
上传并编译
登录
</el-button>
</el-upload>
<el-upload
ref="uploadRef"
:action="uploadUrl"
:auto-upload="false"
:on-change="handleFileChange"
:file-list="fileList"
accept=".cc"
:multiple="false"
:headers="{'Authorization': `Bearer ${token}`}"
>
<template #trigger>
<el-button size="small" type="primary">选取文件</el-button>
</template>
<el-button
style="margin-left: 10px;"
size="small"
type="success"
:disabled="isUploading ||!token"
@click="uploadFile"
>
上传并编译
</el-button>
</el-upload>
</div>
<!-- 动态进度条 -->
<el-progress
......@@ -31,7 +63,7 @@
:status="progressStatus"
class="mt-4"
:stroke-width="10"
:show-info="false"
:show-info="false"
></el-progress>
<!-- 成功提示 -->
......@@ -78,13 +110,21 @@ export default {
downloadUrl: null,
downloadFileName: 'radarcal',
errorMessage: '',
showErrorMessage: false, // 控制错误信息栏的显示
isUploading: false, // 是否正在上传和编译
progressPercentage: 0, // 进度条百分比
progressStatus: '', // 进度条状态(success/error)
isSuccess: false, // 是否编译成功
isDownloadAvailable: false, // 是否显示下载按钮
progressInterval: null // 用于存储进度条更新的定时器
showErrorMessage: false,
isUploading: false,
progressPercentage: 0,
progressStatus: '',
isSuccess: false,
isDownloadAvailable: false,
progressInterval: null,
loginDialogVisible: false,
loginForm: {
username: '',
password: ''
},
token: null,
loginMessage: '',
loginMessageType: ''
};
},
methods: {
......@@ -108,14 +148,14 @@ export default {
this.progressPercentage = 0;
this.progressStatus = '';
// 设置进度条定时器,100 秒内均匀增长到 100%
// 设置进度条定时器,180 秒内均匀增长到 100%
this.progressInterval = setInterval(() => {
if (this.progressPercentage < 100) {
this.progressPercentage += 1; // 每秒增加 1%
this.progressPercentage += 1;
} else {
clearInterval(this.progressInterval); // 停止定时器
clearInterval(this.progressInterval);
}
}, 1000); // 每秒更新一次进度条
}, 1800);
try {
const formData = new FormData();
......@@ -123,33 +163,36 @@ export default {
const response = await fetch(this.uploadUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`
},
body: formData
});
clearInterval(this.progressInterval); // 停止进度条定时器
clearInterval(this.progressInterval);
if (response.ok) {
const blob = await response.blob();
this.downloadUrl = window.URL.createObjectURL(blob);
this.isDownloadAvailable = true; // 显示下载按钮
this.isSuccess = true; // 标记编译成功
this.progressPercentage = 100; // 进度条完成
this.progressStatus = 'success'; // 进度条状态为成功
this.$message.success('编译成功!'); // 提示编译成功
this.isDownloadAvailable = true;
this.isSuccess = true;
this.progressPercentage = 100;
this.progressStatus ='success';
this.$message.success('编译成功!');
} else {
const errorText = await response.text();
this.errorMessage = errorText;
this.showErrorMessage = true; // 显示错误信息栏
this.progressStatus = 'error'; // 进度条状态为失败
this.showErrorMessage = true;
this.progressStatus = 'error';
}
} catch (error) {
clearInterval(this.progressInterval); // 停止进度条定时器
clearInterval(this.progressInterval);
console.error('发生错误:', error);
this.errorMessage = '发生错误,请重试';
this.showErrorMessage = true; // 显示错误信息栏
this.progressStatus = 'error'; // 进度条状态为失败
this.showErrorMessage = true;
this.progressStatus = 'error';
} finally {
this.isUploading = false; // 隐藏进度条
this.isUploading = false;
}
},
downloadFile() {
......@@ -161,6 +204,39 @@ export default {
link.click();
document.body.removeChild(link);
}
},
async handleLogin() {
try {
console.log('登录信息:', this.loginForm);
const formData = new URLSearchParams();
formData.append('username', this.loginForm.username);
formData.append('password', this.loginForm.password);
const response = await fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formData
});
if (response.ok) {
const token = await response.text();
this.token = token;
this.loginMessage = '获取 token 成功';
this.$message.success('获取 token 成功');
this.loginMessageType ='success';
this.loginDialogVisible = false;
} else {
const errorText = await response.text();
this.loginMessage = errorText;
this.loginMessageType = 'error';
}
} catch (error) {
console.error('登录请求出错:', error);
this.loginMessage = '登录请求出错,请重试';
this.loginMessageType = 'error';
}
}
}
};
......@@ -172,4 +248,10 @@ export default {
flex-direction: column;
align-items: center;
}
.button-container {
display: flex;
flex-direction: row;
gap: 20px;
margin-bottom: 20px;
}
</style>
\ No newline at end of file
......@@ -35,11 +35,8 @@ public class FileUploadController {
@PostMapping("/login")
public ResponseEntity<String> login(@RequestParam("username") String username, @RequestParam("password") String password) {
System.out.println("username: " + username + " password: " + password);
if (USER_NAME.equals(username) && USER_PASSWORD.equals(password)) {
String token = new JwtUtil().generateToken(username);
//test
System.out.println("token: " + token + " username: " + username + " password: " + password + "extractname: " + jwtUtil.extractUsername(token));
return ResponseEntity.ok(new JwtUtil.AuthResponse(token).getToken());
} else {
......@@ -52,10 +49,9 @@ public class FileUploadController {
@RequestParam("file") MultipartFile file) {
try {
String token = authorizationHeader.replace("Bearer ", "");
System.out.println("token: " + token + " username: " + "extractname: " + jwtUtil.extractUsername(token));
String username = jwtUtil.extractUsername(token);
if(jwtUtil.validateToken(token, username)){
if(!jwtUtil.validateToken(token, username)){
System.out.println("Token is valid");
return ResponseEntity.status(401).body("token错误,请刷新页面重新登录");
}
......@@ -112,7 +108,6 @@ public class FileUploadController {
private PublishResult publishProject(Path projectDir) throws IOException, InterruptedException {
// 定义发布目录
Path publishDir = projectDir.resolve("build");
System.out.println("publishDir: " + publishDir);
// 执行 rm -rf build && mkdir build
ProcessBuilder rmMkdirBuilder = new ProcessBuilder("bash", "-c", "rm -rf build && mkdir build");
......@@ -140,6 +135,7 @@ public class FileUploadController {
// 获取错误流并读取错误信息
try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(cmakeMakeProcess.getErrorStream()))) {
String errorLog = errorReader.lines().collect(Collectors.joining("\n"));
System.out.println("CMake or Make failed: " + errorLog);
return new PublishResult(false, "CMake or Make failed: " + " Error: " + errorLog, publishDir);
}
}
......
......@@ -7,7 +7,7 @@ import java.util.Date;
@Component
public class JwtUtil {
private final String SECRET_KEY = "ZEDzedZeDZEdzEDzeDtechTechTEchTEChTECHsecretSECRET";
private final String SECRET_KEY = "ZEDzedZeDZEdzEDzeDtechTechTEchTEChTECHsecretSECRET";
public String generateToken(String username) {
return Jwts.builder()
......@@ -19,7 +19,12 @@ public class JwtUtil {
}
public String extractUsername(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject();
return Jwts.parser()
.setSigningKey(SECRET_KEY) // 使用 setSigningKey 而不是 verifyWith
.build()
.parseClaimsJws(token) // 使用 parseClaimsJws 而不是 parseSignedClaims
.getBody()
.getSubject();
}
public boolean validateToken(String token, String username) {
......@@ -32,10 +37,23 @@ public class JwtUtil {
}
private boolean isTokenExpired(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getExpiration().before(new Date());
try {
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
.getBody();
Date expirationDate = claims.getExpiration();
Date currentDate = new Date();
return expirationDate.before(currentDate);
} catch (JwtException | IllegalArgumentException e) {
// 如果解析失败或 Token 无效,则认为 Token 已过期
return true;
}
}
public class AuthRequest {
public static class AuthRequest {
private String username;
private String password;
......@@ -57,7 +75,7 @@ public class JwtUtil {
}
}
static class AuthResponse {
public static class AuthResponse {
private String token;
public AuthResponse(String token) {
......@@ -69,4 +87,4 @@ public class JwtUtil {
return token;
}
}
}
}
\ No newline at end of file
#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50}.upload-container[data-v-9955520a]{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh}
\ No newline at end of file
.upload-container[data-v-3d5ae34e]{display:flex;flex-direction:column;align-items:center}h3[data-v-ebbc34ee]{margin:40px 0 0}ul[data-v-ebbc34ee]{list-style-type:none;padding:0}li[data-v-ebbc34ee]{display:inline-block;margin:0 10px}a[data-v-ebbc34ee]{color:#42b983}
\ No newline at end of file
.upload-container[data-v-1072ea42]{display:flex;flex-direction:column;align-items:center}.button-container[data-v-1072ea42]{display:flex;flex-direction:row;gap:20px;margin-bottom:20px}h3[data-v-ebbc34ee]{margin:40px 0 0}ul[data-v-ebbc34ee]{list-style-type:none;padding:0}li[data-v-ebbc34ee]{display:inline-block;margin:0 10px}a[data-v-ebbc34ee]{color:#42b983}
\ No newline at end of file
src/main/resources/static/favicon.ico

4.19 KB | W: | H:

src/main/resources/static/favicon.ico

66.1 KB | W: | H:

src/main/resources/static/favicon.ico
src/main/resources/static/favicon.ico
src/main/resources/static/favicon.ico
src/main/resources/static/favicon.ico
  • 2-up
  • Swipe
  • Onion skin
<!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"><link rel="icon" href="/favicon.ico"><title>frontend</title><script defer="defer" src="/js/chunk-vendors.620ce23d.js"></script><script defer="defer" src="/js/app.4cf479e6.js"></script><link href="/css/chunk-vendors.10dd4e95.css" rel="stylesheet"><link href="/css/app.9c706792.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
\ No newline at end of file
<!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"><link rel="icon" href="/favicon.ico"><title>CompileProxy</title><script defer="defer" src="/js/chunk-vendors.620ce23d.js"></script><script defer="defer" src="/js/app.2a660157.js"></script><link href="/css/chunk-vendors.10dd4e95.css" rel="stylesheet"><link href="/css/app.b89b1abd.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
(function(){"use strict";var e={6653:function(e,t,s){var r=s(5471),o=function(){var e=this,t=e._self._c;return t("div",{attrs:{id:"app"}},[t("router-view")],1)},n=[],i={data(){return{}},methods:{},mounted(){}},a=i,l=s(1656),u=(0,l.A)(a,o,n,!1,null,null,null),c=u.exports,d=s(173),g=function(){var e=this,t=e._self._c;return t("div",{staticClass:"upload-container"},[t("el-dialog",{attrs:{visible:e.loginDialogVisible,title:"登录"},on:{"update:visible":function(t){e.loginDialogVisible=t}}},[t("el-form",{on:{submit:function(t){return t.preventDefault(),e.handleLogin.apply(null,arguments)}}},[t("el-form-item",{attrs:{label:"用户名"}},[t("el-input",{model:{value:e.loginForm.username,callback:function(t){e.$set(e.loginForm,"username",t)},expression:"loginForm.username"}})],1),t("el-form-item",{attrs:{label:"密码"}},[t("el-input",{attrs:{type:"password"},model:{value:e.loginForm.password,callback:function(t){e.$set(e.loginForm,"password",t)},expression:"loginForm.password"}})],1),t("el-button",{attrs:{type:"primary","native-type":"submit"}},[e._v("登录")])],1)],1),e.isLoggedIn?t("el-upload",{ref:"uploadRef",attrs:{action:e.uploadUrl,"auto-upload":!1,"on-change":e.handleFileChange,"file-list":e.fileList,accept:".cc",multiple:!1},scopedSlots:e._u([{key:"trigger",fn:function(){return[t("el-button",{attrs:{size:"small",type:"primary"}},[e._v("选取文件")])]},proxy:!0}],null,!1,2040363866)},[t("el-button",{staticStyle:{"margin-left":"10px"},attrs:{size:"small",type:"success",disabled:e.isUploading},on:{click:e.uploadFile}},[e._v(" 上传并编译 ")])],1):e._e(),e.isLoggedIn&&e.isUploading?t("el-progress",{staticClass:"mt-4",attrs:{percentage:e.progressPercentage,status:e.progressStatus,"stroke-width":10,"show-info":!1}}):e._e(),e.isLoggedIn&&e.isSuccess?t("el-alert",{staticClass:"mt-4",attrs:{title:"编译成功!",type:"success","show-icon":""}}):e._e(),e.isLoggedIn&&e.showErrorMessage?t("el-alert",{staticClass:"mt-4",attrs:{title:"发布错误信息",type:"error",closable:!1}},[e._v(" "+e._s(e.errorMessage)+" ")]):e._e(),e.isLoggedIn&&e.isDownloadAvailable?t("el-button",{staticClass:"mt-4",attrs:{size:"small",type:"primary"},on:{click:e.downloadFile}},[e._v(" 下载标定程序 ")]):e._e()],1)},p=[],h=(s(4603),s(7566),s(8721),s(4335)),f={data(){return{loginDialogVisible:!0,loginForm:{username:"",password:""},isLoggedIn:!1,uploadUrl:"/upload",fileList:[],selectedFile:null,downloadUrl:null,downloadFileName:"radarcal",errorMessage:"",showErrorMessage:!1,isUploading:!1,progressPercentage:0,progressStatus:"",isSuccess:!1,isDownloadAvailable:!1,progressInterval:null}},methods:{handleLogin(){h.A.post("/login",this.loginForm).then((e=>{const t=e.data;localStorage.setItem("token",t),this.isLoggedIn=!0,this.loginDialogVisible=!1,this.$message.success("登录成功")})).catch((e=>{this.$message.error("登录失败,请检查用户名和密码")}))},handleFileChange(e){this.selectedFile=e.raw,this.fileList=[e]},async uploadFile(){if(this.selectedFile){this.errorMessage="",this.showErrorMessage=!1,this.isSuccess=!1,this.isUploading=!0,this.progressPercentage=0,this.progressStatus="",this.progressInterval=setInterval((()=>{this.progressPercentage<100?this.progressPercentage+=1:clearInterval(this.progressInterval)}),1200);try{const e=new FormData;e.append("file",this.selectedFile);const t=await fetch(this.uploadUrl,{method:"POST",headers:{Authorization:`Bearer ${localStorage.getItem("token")}`},body:e});if(clearInterval(this.progressInterval),t.ok){const e=await t.blob();this.downloadUrl=window.URL.createObjectURL(e),this.isDownloadAvailable=!0,this.isSuccess=!0,this.progressPercentage=100,this.progressStatus="success",this.$message.success("编译成功!")}else{const e=await t.text();this.errorMessage=e,this.showErrorMessage=!0,this.progressStatus="error",401===t.status&&(this.$message.error("登录已过期,请重新登录"),this.isLoggedIn=!1,this.loginDialogVisible=!0)}}catch(e){clearInterval(this.progressInterval),console.error("发生错误:",e),this.errorMessage="发生错误,请重试",this.showErrorMessage=!0,this.progressStatus="error",e.response&&401===e.response.status&&(this.$message.error("登录已过期,请重新登录"),this.isLoggedIn=!1,this.loginDialogVisible=!0)}finally{this.isUploading=!1}}else this.$message.error("请选择一个文件")},downloadFile(){if(this.downloadUrl){const e=document.createElement("a");e.href=this.downloadUrl,e.download=this.downloadFileName,document.body.appendChild(e),e.click(),document.body.removeChild(e)}}}},m=f,v=(0,l.A)(m,g,p,!1,null,"9955520a",null),b=v.exports;r["default"].use(d.Ay);const w=[{path:"/",name:"root",redirect:"/upload"},{path:"/upload",name:"upload",component:b}],y=new d.Ay({mode:"history",base:"/",routes:w});var F=y,I=s(1052),S=s.n(I),_=s(9952);r["default"].config.productionTip=!1,r["default"].use(S()),r["default"].use(S(),{locale:_["default"]}),new r["default"]({router:F,render:e=>e(c)}).$mount("#app")}},t={};function s(r){var o=t[r];if(void 0!==o)return o.exports;var n=t[r]={id:r,loaded:!1,exports:{}};return e[r].call(n.exports,n,n.exports,s),n.loaded=!0,n.exports}s.m=e,function(){s.amdO={}}(),function(){var e=[];s.O=function(t,r,o,n){if(!r){var i=1/0;for(c=0;c<e.length;c++){r=e[c][0],o=e[c][1],n=e[c][2];for(var a=!0,l=0;l<r.length;l++)(!1&n||i>=n)&&Object.keys(s.O).every((function(e){return s.O[e](r[l])}))?r.splice(l--,1):(a=!1,n<i&&(i=n));if(a){e.splice(c--,1);var u=o();void 0!==u&&(t=u)}}return t}n=n||0;for(var c=e.length;c>0&&e[c-1][2]>n;c--)e[c]=e[c-1];e[c]=[r,o,n]}}(),function(){s.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return s.d(t,{a:t}),t}}(),function(){s.d=function(e,t){for(var r in t)s.o(t,r)&&!s.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}}(),function(){s.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"===typeof window)return window}}()}(),function(){s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)}}(),function(){s.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}}(),function(){s.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e}}(),function(){var e={524:0};s.O.j=function(t){return 0===e[t]};var t=function(t,r){var o,n,i=r[0],a=r[1],l=r[2],u=0;if(i.some((function(t){return 0!==e[t]}))){for(o in a)s.o(a,o)&&(s.m[o]=a[o]);if(l)var c=l(s)}for(t&&t(r);u<i.length;u++)n=i[u],s.o(e,n)&&e[n]&&e[n][0](),e[n]=0;return s.O(c)},r=self["webpackChunkfrontend"]=self["webpackChunkfrontend"]||[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))}();var r=s.O(void 0,[504],(function(){return s(6653)}));r=s.O(r)})();
//# sourceMappingURL=app.cd3a0313.js.map
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment