feat: integrate frontend resources (#6)
This commit is contained in:
parent
82feab3f1a
commit
77fcf3011a
|
@ -1,3 +1,8 @@
|
|||
FROM gradle:7.3-jdk11 as build
|
||||
WORKDIR /app
|
||||
ADD . /app
|
||||
RUN gradle api:build
|
||||
|
||||
FROM openjdk:11.0.13-jre
|
||||
ARG service_name_folder
|
||||
WORKDIR /app
|
44
README.md
44
README.md
|
@ -1,13 +1,11 @@
|
|||
# Databasir
|
||||
## 规划
|
||||
项目目前还属于 MVP (可行性验证)阶段,功能、文档等尚未完备,功能也处于随时调整的阶段
|
||||
项目目前还属于 MVP (可行性验证)阶段,功能处于随时调整的阶段
|
||||
|
||||
以下功能尚在开发中
|
||||
|
||||
- [x] 定时文档同步
|
||||
- [ ] 表字段协同注释
|
||||
- [ ] 操作审计日志
|
||||
- [ ] 容器化部署
|
||||
|
||||
## 简介
|
||||
|
||||
|
@ -18,13 +16,49 @@
|
|||
3. 精细化:团队成员可以协同为文档做更精细化的注释
|
||||
4. 扁平化:权限管理扁平,减少冗余流程,价值最大化
|
||||
|
||||
## 部署 TODO
|
||||
## 部署
|
||||
|
||||
Databasir 采用了前后端分离的模式进行开发和部署,前端和后端需要独立部署
|
||||
Databasir 采用了前后端分离的模式进行开发和部署,前端和后端可以独立部署,也可以采用只部署已整合前端资源的后端应用
|
||||
|
||||
- 后端应用: https://github.com/vran-dev/databasir
|
||||
- 前端应用: https://github.com/vran-dev/databasir-frontend
|
||||
|
||||
### JAR 模式部署
|
||||
|
||||
注意:
|
||||
|
||||
1. 使用 JAR 模式部署需要系统环境有 Java 环境,最低版本为 Java11。
|
||||
2. 应用使用 MYSQL 作为数据存储,所以也需要准备好数据库。
|
||||
|
||||
部署:
|
||||
1. 在 [Github RELEASE](https://github.com/vran-dev/databasir/releases) 页面下载最新版应用 Databasir.jar (你也可以选择克隆项目后自行构建)
|
||||
2. 将 Databasir.jar 上传到服务器
|
||||
3. 在 Databasir.jar 所在目录创建 config 目录,并在目录下创建 `application.properties` 配置,配置中配置 MYSQL 的用户名、密码和连接
|
||||
|
||||
```properties
|
||||
# 端口号,默认8080
|
||||
server.port=8080
|
||||
# 数据库用户名
|
||||
databasir.datasource.username=root
|
||||
# 数据库密码
|
||||
databasir.datasource.password=123456
|
||||
# 数据库地址
|
||||
databasir.datasource.url=127.0.0.1:3306
|
||||
```
|
||||
|
||||
4. 通过 java -jar Databasir.jar 启动应用即可
|
||||
|
||||
应用启动后会默认创建 Databasir 管理员用户
|
||||
|
||||
- 用户名:databasir
|
||||
- 密码:databasir
|
||||
|
||||
通过该账号登录应用既可以进行管理
|
||||
|
||||
### Docker 部署
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
## 展示
|
||||
|
||||
|
|
|
@ -31,13 +31,3 @@ dependencies {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Docker
|
||||
*/
|
||||
task copyDockerfile(type: Copy) {
|
||||
from("Dockerfile")
|
||||
into("build/libs")
|
||||
}
|
||||
|
||||
bootJar.finalizedBy copyDockerfile
|
||||
assemble.finalizedBy copyDockerfile
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.databasir.api;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class IndexController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String index() {
|
||||
return "index.html";
|
||||
}
|
||||
|
||||
}
|
|
@ -43,8 +43,8 @@ public class LoginController {
|
|||
try {
|
||||
return JsonData.ok(loginService.refreshAccessTokens(request));
|
||||
} catch (DatabasirException e) {
|
||||
if (Objects.equals(e.getErrCode(), DomainErrors.ACCESS_TOKEN_REFRESH_INVALID.getErrCode())) {
|
||||
throw new InvalidTokenException(DomainErrors.ACCESS_TOKEN_REFRESH_INVALID);
|
||||
if (Objects.equals(e.getErrCode(), DomainErrors.INVALID_REFRESH_TOKEN_OPERATION.getErrCode())) {
|
||||
throw new InvalidTokenException(DomainErrors.INVALID_REFRESH_TOKEN_OPERATION);
|
||||
}
|
||||
if (Objects.equals(e.getErrCode(), DomainErrors.REFRESH_TOKEN_EXPIRED.getErrCode())) {
|
||||
throw new InvalidTokenException(DomainErrors.REFRESH_TOKEN_EXPIRED);
|
||||
|
|
|
@ -45,6 +45,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/login", Routes.Login.REFRESH_ACCESS_TOKEN).permitAll()
|
||||
.antMatchers("/", "/*.html", "/js/**", "/css/**", "/img/**", "/*.ico").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.exceptionHandling().authenticationEntryPoint(databasirAuthenticationEntryPoint);
|
||||
|
|
|
@ -7,6 +7,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
|||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
@ -46,7 +47,14 @@ public class DatabasirJwtTokenFilter extends OncePerRequestFilter {
|
|||
}
|
||||
|
||||
String username = jwtTokens.getUsername(token);
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
UserDetails userDetails = null;
|
||||
try {
|
||||
userDetails = userDetailsService.loadUserByUsername(username);
|
||||
} catch (UsernameNotFoundException e) {
|
||||
logger.warn("username not found after token verified: " + e.getMessage());
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
.left-menu:not(.el-menu--collapse){height:100vh}.el-aside{display:block;position:fixed;left:0;bottom:0;top:0;width:200px}.databasir-main-header{display:flex;justify-content:space-between;align-items:center;position:fixed;top:0;right:0;left:220px;padding:30px;background:#fff;z-index:100;border-color:#eee;border-width:0 0 1px 0;border-style:solid}.databasir-main{margin-left:200px;margin-top:80px;--el-main-padding:0px 20px 20px 20px}.databasir-main-content{max-width:95%;--el-main-padding:0px 20px 20px 20px}
|
|
@ -0,0 +1 @@
|
|||
.el-row{margin-top:33px}
|
|
@ -0,0 +1 @@
|
|||
.login-main{margin:0 auto;margin-top:200px}.login-input{border-width:0 0 1px 0;border-style:solid;width:100%;min-height:33px}.login-input::-moz-placeholder{color:hsla(0,0%,70.6%,.808)}.login-input:-ms-input-placeholder{color:hsla(0,0%,70.6%,.808)}.login-input::placeholder{color:hsla(0,0%,70.6%,.808)}.login-input:focus{outline:none;border-color:#000}.login-card{max-width:600px;min-width:500px;border-color:#000}
|
|
@ -0,0 +1 @@
|
|||
.card-header{display:flex;justify-content:space-between;align-items:center}.el-row{margin-bottom:20px}.el-row:last-child{margin-bottom:0}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1 @@
|
|||
<!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>databasir-frontend</title><link href="/css/chunk-0e34b2c6.06814884.css" rel="prefetch"><link href="/css/chunk-588dbed6.e51aa148.css" rel="prefetch"><link href="/css/chunk-7efe8be4.00ac37b1.css" rel="prefetch"><link href="/js/chunk-0e34b2c6.7af33675.js" rel="prefetch"><link href="/js/chunk-2d0a47bb.baec3bc7.js" rel="prefetch"><link href="/js/chunk-2d0cc811.c5d1ef9e.js" rel="prefetch"><link href="/js/chunk-48cebeac.162363c9.js" rel="prefetch"><link href="/js/chunk-588dbed6.ba7725b2.js" rel="prefetch"><link href="/js/chunk-7efe8be4.815f1aa1.js" rel="prefetch"><link href="/js/chunk-9622a6d8.d116da54.js" rel="prefetch"><link href="/js/chunk-abb10c56.c12963e3.js" rel="prefetch"><link href="/js/chunk-fffb1b64.1ffb9f27.js" rel="prefetch"><link href="/css/app.fc57c576.css" rel="preload" as="style"><link href="/css/chunk-vendors.d4aa889d.css" rel="preload" as="style"><link href="/js/app.d3ac1eb1.js" rel="preload" as="script"><link href="/js/chunk-vendors.42fcab1c.js" rel="preload" as="script"><link href="/css/chunk-vendors.d4aa889d.css" rel="stylesheet"><link href="/css/app.fc57c576.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but databasir-frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/js/chunk-vendors.42fcab1c.js"></script><script src="/js/app.d3ac1eb1.js"></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,2 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0cc811"],{"4de0":function(e,t,r){"use strict";r.r(t);var n=r("7a23"),o=Object(n["createTextVNode"])(" : "),u=Object(n["createTextVNode"])("保存");function l(e,t,r,l,a,c){var s=Object(n["resolveComponent"])("el-input"),i=Object(n["resolveComponent"])("el-form-item"),d=Object(n["resolveComponent"])("el-col"),m=Object(n["resolveComponent"])("el-button"),f=Object(n["resolveComponent"])("el-form"),p=Object(n["resolveComponent"])("el-card"),b=Object(n["resolveComponent"])("el-main"),j=Object(n["resolveComponent"])("el-container");return Object(n["openBlock"])(),Object(n["createBlock"])(j,null,{default:Object(n["withCtx"])((function(){return[Object(n["createVNode"])(b,null,{default:Object(n["withCtx"])((function(){return[Object(n["createVNode"])(p,null,{default:Object(n["withCtx"])((function(){return[Object(n["createVNode"])(f,{model:a.form,"label-position":"top",rules:a.formRule,ref:"formRef",style:{"max-width":"900px"}},{default:Object(n["withCtx"])((function(){return[Object(n["createVNode"])(i,{label:"邮箱账号",prop:"username"},{default:Object(n["withCtx"])((function(){return[Object(n["createVNode"])(s,{modelValue:a.form.username,"onUpdate:modelValue":t[0]||(t[0]=function(e){return a.form.username=e})},null,8,["modelValue"])]})),_:1}),Object(n["createVNode"])(i,{label:"邮箱密码",prop:"password"},{default:Object(n["withCtx"])((function(){return[Object(n["createVNode"])(s,{modelValue:a.form.password,"onUpdate:modelValue":t[1]||(t[1]=function(e){return a.form.password=e}),type:"password",placeholder:"请输入密码","show-password":""},null,8,["modelValue"])]})),_:1}),Object(n["createVNode"])(i,{label:"SMTP",prop:"smtpHost"},{default:Object(n["withCtx"])((function(){return[Object(n["createVNode"])(d,{span:12},{default:Object(n["withCtx"])((function(){return[Object(n["createVNode"])(s,{modelValue:a.form.smtpHost,"onUpdate:modelValue":t[2]||(t[2]=function(e){return a.form.smtpHost=e}),placeholder:"SMTP Host"},null,8,["modelValue"])]})),_:1}),Object(n["createVNode"])(d,{span:1,style:{"text-align":"center"}},{default:Object(n["withCtx"])((function(){return[o]})),_:1}),Object(n["createVNode"])(d,{span:6},{default:Object(n["withCtx"])((function(){return[Object(n["createVNode"])(s,{modelValue:a.form.smtpPort,"onUpdate:modelValue":t[3]||(t[3]=function(e){return a.form.smtpPort=e}),placeholder:"SMTP Port"},null,8,["modelValue"])]})),_:1})]})),_:1}),Object(n["createVNode"])(i,{style:{"margin-top":"38px"}},{default:Object(n["withCtx"])((function(){return[Object(n["createVNode"])(m,{type:"primary",onClick:t[4]||(t[4]=function(e){return c.onSubmit("formRef")})},{default:Object(n["withCtx"])((function(){return[u]})),_:1})]})),_:1})]})),_:1},8,["model","rules"])]})),_:1})]})),_:1})]})),_:1})}var a=r("1da1"),c=(r("96cf"),r("1c1e")),s="/api/v1.0/settings",i=function(){return c["a"].get(s+"/sys_email")},d=function(e){return c["a"].post(s+"/sys_email",e)},m={data:function(){return{form:{smtpHost:null,smtpPort:null,username:null,password:null},formRule:{username:[this.requiredInputValidRule("请输入邮箱账号"),{type:"email",message:"邮箱格式不正确",trigger:"blur"}],password:[this.requiredInputValidRule("请输入邮箱密码")],smtpHost:[this.requiredInputValidRule("请输入 SMTP 地址")],smtpPort:[this.requiredInputValidRule("请输入 SMTP 端口"),{min:1,max:65535,message:"端口有效值为 1~65535",trigger:"blur"}]}}},mounted:function(){this.fetchSysMail()},methods:{requiredInputValidRule:function(e){return{required:!0,message:e,trigger:"blur"}},fetchSysMail:function(){var e=this;return Object(a["a"])(regeneratorRuntime.mark((function t(){var r;return regeneratorRuntime.wrap((function(t){while(1)switch(t.prev=t.next){case 0:return t.next=2,i().then((function(e){return e.data}));case 2:r=t.sent,r&&(e.form=r);case 4:case"end":return t.stop()}}),t)})))()},onSubmit:function(){var e=this;this.$refs.formRef.validate((function(t){return t?(d(e.form).then((function(t){t.errCode||e.$message.success("更新成功")})),!0):(e.$message.error("请完善表单相关信息!"),!1)}))}}},f=r("6b0d"),p=r.n(f);const b=p()(m,[["render",l]]);t["default"]=b}}]);
|
||||
//# sourceMappingURL=chunk-2d0cc811.c5d1ef9e.js.map
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,2 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-588dbed6"],{"4dd4":function(e,t,n){},a1af:function(e,t,n){"use strict";n("4dd4")},a55b:function(e,t,n){"use strict";n.r(t);var r=n("7a23"),o={class:"login-card"},c=Object(r["createElementVNode"])("h1",null,"Databasir",-1),a=Object(r["createTextVNode"])(" 登录 "),u=Object(r["createTextVNode"])(" 忘记密码? ");function l(e,t,n,l,i,d){var s=Object(r["resolveComponent"])("el-header"),f=Object(r["resolveComponent"])("el-link"),b=Object(r["resolveComponent"])("el-divider"),m=Object(r["resolveComponent"])("el-form-item"),p=Object(r["resolveComponent"])("el-button"),j=Object(r["resolveComponent"])("el-space"),O=Object(r["resolveComponent"])("el-form"),h=Object(r["resolveComponent"])("el-main"),w=Object(r["resolveComponent"])("el-footer"),C=Object(r["resolveComponent"])("el-container");return Object(r["openBlock"])(),Object(r["createBlock"])(C,null,{default:Object(r["withCtx"])((function(){return[Object(r["createVNode"])(s),Object(r["createVNode"])(h,{class:"login-main"},{default:Object(r["withCtx"])((function(){return[Object(r["createElementVNode"])("div",o,[Object(r["createVNode"])(O,{ref:"formRef",rules:i.formRule,model:i.form,style:{border:"none"}},{default:Object(r["withCtx"])((function(){return[Object(r["createVNode"])(m,null,{default:Object(r["withCtx"])((function(){return[Object(r["createVNode"])(b,{"content-position":"left"},{default:Object(r["withCtx"])((function(){return[Object(r["createVNode"])(f,{href:"https://github.com/vran-dev/databasir",target:"_blank",underline:!1,type:"info"},{default:Object(r["withCtx"])((function(){return[c]})),_:1})]})),_:1})]})),_:1}),Object(r["createVNode"])(m,{prop:"username"},{default:Object(r["withCtx"])((function(){return[Object(r["withDirectives"])(Object(r["createElementVNode"])("input",{type:"text",class:"login-input",placeholder:"用户名或邮箱","onUpdate:modelValue":t[0]||(t[0]=function(e){return i.form.username=e})},null,512),[[r["vModelText"],i.form.username]])]})),_:1}),Object(r["createVNode"])(m,{prop:"password"},{default:Object(r["withCtx"])((function(){return[Object(r["withDirectives"])(Object(r["createElementVNode"])("input",{type:"password",class:"login-input",placeholder:"密码","onUpdate:modelValue":t[1]||(t[1]=function(e){return i.form.password=e})},null,512),[[r["vModelText"],i.form.password]])]})),_:1}),Object(r["createVNode"])(m,null,{default:Object(r["withCtx"])((function(){return[Object(r["createVNode"])(j,{size:32},{default:Object(r["withCtx"])((function(){return[Object(r["createVNode"])(p,{style:{width:"120px","margin-top":"10px"},color:"#000",onClick:t[2]||(t[2]=function(e){return d.onLogin("formRef")}),plain:"",round:""},{default:Object(r["withCtx"])((function(){return[a]})),_:1}),Object(r["createVNode"])(f,{href:"#",target:"_blank",underline:!1,type:"info"},{default:Object(r["withCtx"])((function(){return[u]})),_:1})]})),_:1})]})),_:1})]})),_:1},8,["rules","model"])])]})),_:1}),Object(r["createVNode"])(w,null,{default:Object(r["withCtx"])((function(){return[Object(r["createVNode"])(j)]})),_:1})]})),_:1})}var i=n("b0af"),d=n("5f87"),s={data:function(){return{form:{username:null,password:null},formRule:{username:[{required:!0,message:"请输入用户名或邮箱",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]}}},methods:{toIndexPage:function(){this.$router.push({path:"/groups"})},onLogin:function(){var e=this;this.$refs.formRef.validate((function(t){t&&Object(i["a"])(e.form).then((function(t){t.errCode||(d["b"].saveUserLoginData(t.data),e.$store.commit("userUpdate",{nickname:t.data.nickname,username:t.data.username,email:t.data.email}),e.toIndexPage())}))}))}}},f=(n("a1af"),n("6b0d")),b=n.n(f);const m=b()(s,[["render",l]]);t["default"]=m}}]);
|
||||
//# sourceMappingURL=chunk-588dbed6.ba7725b2.js.map
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -9,7 +9,7 @@ import lombok.RequiredArgsConstructor;
|
|||
@Getter
|
||||
public enum DomainErrors implements DatabasirErrors {
|
||||
REFRESH_TOKEN_EXPIRED("X_0001", "refresh token expired"),
|
||||
ACCESS_TOKEN_REFRESH_INVALID("X_0002", "invalid refresh token operation"),
|
||||
INVALID_REFRESH_TOKEN_OPERATION("X_0002", "invalid refresh token operation"),
|
||||
|
||||
NOT_SUPPORT_DATABASE_TYPE("A_10000", "不支持的数据库类型, 请检查项目配置"),
|
||||
PROJECT_NOT_FOUND("A_10001", "项目不存在"),
|
||||
|
|
|
@ -32,7 +32,7 @@ public class LoginService {
|
|||
|
||||
public AccessTokenRefreshResponse refreshAccessTokens(AccessTokenRefreshRequest request) {
|
||||
LoginPojo login = loginDao.selectByRefreshToken(request.getRefreshToken())
|
||||
.orElseThrow(DomainErrors.ACCESS_TOKEN_REFRESH_INVALID::exception);
|
||||
.orElseThrow(DomainErrors.INVALID_REFRESH_TOKEN_OPERATION::exception);
|
||||
// refresh-token 已过期
|
||||
if (login.getRefreshTokenExpireAt().isBefore(LocalDateTime.now())) {
|
||||
throw DomainErrors.REFRESH_TOKEN_EXPIRED.exception();
|
||||
|
@ -41,9 +41,15 @@ public class LoginService {
|
|||
if (login.getAccessTokenExpireAt().isAfter(LocalDateTime.now())) {
|
||||
log.warn("invalid access token refresh operation: request = {}, login = {}", request, login);
|
||||
loginDao.deleteByUserId(login.getUserId());
|
||||
throw DomainErrors.ACCESS_TOKEN_REFRESH_INVALID.exception();
|
||||
throw DomainErrors.INVALID_REFRESH_TOKEN_OPERATION.exception();
|
||||
}
|
||||
UserPojo user = userDao.selectById(login.getUserId());
|
||||
|
||||
// refresh-token 对应的用户已被删除
|
||||
UserPojo user = userDao.selectOptionalById(login.getUserId())
|
||||
.orElseThrow(() -> {
|
||||
log.warn("user not exists but refresh token exists for " + login.getRefreshToken());
|
||||
return DomainErrors.INVALID_REFRESH_TOKEN_OPERATION.exception();
|
||||
});
|
||||
String accessToken = jwtTokens.accessToken(user.getEmail());
|
||||
LocalDateTime accessTokenExpireAt = jwtTokens.expireAt(accessToken);
|
||||
loginDao.updateAccessToken(accessToken, accessTokenExpireAt, user.getId());
|
||||
|
|
|
@ -48,7 +48,7 @@ public class SystemService {
|
|||
return pojo;
|
||||
});
|
||||
|
||||
String email = "admin@databasir.com";
|
||||
String email = "N/A";
|
||||
String username = "databasir";
|
||||
Optional<UserPojo> userOpt = userDao.selectByEmail(email);
|
||||
if (!userOpt.isPresent()) {
|
||||
|
|
Loading…
Reference in New Issue