Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
L
lcj-btp-java-app
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
郭智朋
lcj-btp-java-app
Commits
e2584d4d
Commit
e2584d4d
authored
Feb 11, 2025
by
guozhipeng
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
登录接口改造
parent
5110af93
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
124 additions
and
62 deletions
+124
-62
Login.controller.js
app/main/webapp/controller/Login.controller.js
+5
-4
Login.view.xml
app/main/webapp/view/Login.view.xml
+2
-2
xs-app.json
app/router/xs-app.json
+2
-2
OperateAuthFilter.java
...stomer/lianchuangjie/common/filter/OperateAuthFilter.java
+11
-34
LoginController.java
...omer/lianchuangjie/master/controller/LoginController.java
+3
-2
LoginService.java
...a/customer/lianchuangjie/master/service/LoginService.java
+3
-1
LoginServiceImpl.java
...r/lianchuangjie/master/service/impl/LoginServiceImpl.java
+93
-0
application.yaml
srv/src/main/resources/application.yaml
+5
-17
No files found.
app/main/webapp/controller/Login.controller.js
View file @
e2584d4d
...
@@ -102,7 +102,7 @@ sap.ui.define([
...
@@ -102,7 +102,7 @@ sap.ui.define([
* 给输入框绑定回车事件,直接调用登录
* 给输入框绑定回车事件,直接调用登录
*/
*/
setKeyboardShortcuts
:
function
(){
setKeyboardShortcuts
:
function
(){
var
that
=
this
;
/*
var that = this;
this.getView().byId("txtUsername").onkeydown = function (evt){
this.getView().byId("txtUsername").onkeydown = function (evt){
if(evt.keyCode == 13){
if(evt.keyCode == 13){
that.onLogin();
that.onLogin();
...
@@ -113,7 +113,7 @@ sap.ui.define([
...
@@ -113,7 +113,7 @@ sap.ui.define([
if(evt.keyCode == 13){
if(evt.keyCode == 13){
that.onLogin();
that.onLogin();
}
}
}
}
*/
},
},
/**
/**
* 判断是否是手机浏览器
* 判断是否是手机浏览器
...
@@ -162,14 +162,15 @@ sap.ui.define([
...
@@ -162,14 +162,15 @@ sap.ui.define([
let
that
=
this
let
that
=
this
// 获取登录数据
// 获取登录数据
let
param
=
{
let
param
=
{};
/*let param = {
userLoginId: this.getView().byId("txtUsername").getValue(),
userLoginId: this.getView().byId("txtUsername").getValue(),
currentPassword: this.getView().byId("txtPassword").getValue()
currentPassword: this.getView().byId("txtPassword").getValue()
};
};
if(param.userLoginId.length === 0 || param.currentPassword.length === 0){
if(param.userLoginId.length === 0 || param.currentPassword.length === 0){
MessageBox.warning(this.i18n("Msg_input_username_password"));
MessageBox.warning(this.i18n("Msg_input_username_password"));
return;
return;
}
}
*/
console
.
log
(
"onLoginUrl="
,
that
.
baseUrl
+
"login/login"
);
console
.
log
(
"onLoginUrl="
,
that
.
baseUrl
+
"login/login"
);
that
.
getCsrfToken
(
csrfToken
=>
{
that
.
getCsrfToken
(
csrfToken
=>
{
console
.
log
(
'【获取到的 CSRF Token】 '
,
csrfToken
);
console
.
log
(
'【获取到的 CSRF Token】 '
,
csrfToken
);
...
...
app/main/webapp/view/Login.view.xml
View file @
e2584d4d
...
@@ -33,7 +33,7 @@
...
@@ -33,7 +33,7 @@
class=
"sapUiSmallMarginBottom"
class=
"sapUiSmallMarginBottom"
ariaHasPopup=
"Dialog"
visible=
"false"
/>
ariaHasPopup=
"Dialog"
visible=
"false"
/>
<Label
text=
"{i18n>Label_user_name}"
>
<
!--<
Label text="{i18n>Label_user_name}">
<layoutData>
<layoutData>
<l:GridData span="L4 M6 S4"/>
<l:GridData span="L4 M6 S4"/>
</layoutData>
</layoutData>
...
@@ -52,7 +52,7 @@
...
@@ -52,7 +52,7 @@
<layoutData>
<layoutData>
<l:GridData span="L4 M6 S8"/>
<l:GridData span="L4 M6 S8"/>
</layoutData>
</layoutData>
</Input>
</Input>
-->
<Label
text=
"{i18n>Label_language}"
>
<Label
text=
"{i18n>Label_language}"
>
<layoutData>
<layoutData>
<l:GridData
linebreak=
"true"
span=
"L4 M6 S4"
/>
<l:GridData
linebreak=
"true"
span=
"L4 M6 S4"
/>
...
...
app/router/xs-app.json
View file @
e2584d4d
...
@@ -3,8 +3,8 @@
...
@@ -3,8 +3,8 @@
"routes"
:
[
"routes"
:
[
{
{
"source"
:
"^/index.html(#.*)?$"
,
"source"
:
"^/index.html(#.*)?$"
,
"target"
:
"/index.html$1"
,
"target"
:
"/
webapp/
index.html$1"
,
"localDir"
:
"./main
/webapp
"
,
"localDir"
:
"./main"
,
"cacheControl"
:
"no-cache, no-store, must-revalidate"
"cacheControl"
:
"no-cache, no-store, must-revalidate"
},
},
{
{
...
...
srv/src/main/java/customer/lianchuangjie/common/filter/OperateAuthFilter.java
View file @
e2584d4d
...
@@ -64,32 +64,6 @@ public class OperateAuthFilter implements Filter {
...
@@ -64,32 +64,6 @@ public class OperateAuthFilter implements Filter {
String
uri
=
request
.
getRequestURI
();
String
uri
=
request
.
getRequestURI
();
String
ip
=
request
.
getRemoteAddr
();
String
ip
=
request
.
getRemoteAddr
();
log
.
info
(
"requestIP:{}, requestURI:{}, method:{}, appKey:{}"
,
ip
,
uri
,
request
.
getMethod
(),
appKey
);
log
.
info
(
"requestIP:{}, requestURI:{}, method:{}, appKey:{}"
,
ip
,
uri
,
request
.
getMethod
(),
appKey
);
// IP白名单
/*if (ipWhitelist) {
String language = request.getHeader("language");
if (ipWhitelistMap.isEmpty()) {
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType("text/html; charset=utf-8");
response.getWriter().write(MessageUtil.get("Msg_needVpn", language));
response.flushBuffer();
return;
} else {
boolean match = false;
for (String s : ipWhitelistMap.keySet()) {
if (IpAddressMatcherUtil.matches(ip, s)) {
match = true;
break;
}
}
if (!match) {
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType("text/html; charset=utf-8");
response.getWriter().write(MessageUtil.get("Msg_needVpn", language));
response.flushBuffer();
return;
}
}
}*/
String
username
=
null
;
String
username
=
null
;
Collection
<?
extends
GrantedAuthority
>
authorities
=
null
;
Collection
<?
extends
GrantedAuthority
>
authorities
=
null
;
...
@@ -195,25 +169,27 @@ public class OperateAuthFilter implements Filter {
...
@@ -195,25 +169,27 @@ public class OperateAuthFilter implements Filter {
return;
return;
}*/
}*/
// "/"是OData默认首页 禁止访问OData默认首页
// "/"是OData默认首页 禁止访问OData默认首页
/*if (request.getMethod().equals("GET") && ("/index.html".equals(uri)
)) {// "/index.html"跳转到"/main/webapp/index.html"
if
(
request
.
getMethod
().
equals
(
"GET"
)
&&
"/"
.
equals
(
uri
))
{
// "/index.html"跳转到"/main/webapp/index.html"
response
.
setHeader
(
"Cache-Control"
,
"no-cache, no-store, must-revalidate"
);
response
.
setHeader
(
"Cache-Control"
,
"no-cache, no-store, must-revalidate"
);
response
.
setHeader
(
"Pragma"
,
"no-cache"
);
response
.
setHeader
(
"Pragma"
,
"no-cache"
);
response
.
setHeader
(
"Expires"
,
"0"
);
response
.
setHeader
(
"Expires"
,
"0"
);
response
.
sendRedirect
(
"/main/webapp/index.html"
);
//重定向
response
.
sendRedirect
(
"/main/webapp/index.html"
);
//重定向
return
;
return
;
}
*/
}
/*if (request.getMethod().equals("GET") && ("/".equals(uri))) {// "/"是OData默认首页 禁止访问OData默认首页
if
(
request
.
getMethod
().
equals
(
"GET"
)
&&
"/index.html"
.
equals
(
uri
))
{
// "/index.html"跳转到"/main/webapp/index.html"
response
.
setHeader
(
"Cache-Control"
,
"no-cache, no-store, must-revalidate"
);
response
.
setHeader
(
"Cache-Control"
,
"no-cache, no-store, must-revalidate"
);
response
.
setHeader
(
"Pragma"
,
"no-cache"
);
response
.
setHeader
(
"Pragma"
,
"no-cache"
);
response
.
setHeader
(
"Expires"
,
"0"
);
response
.
setHeader
(
"Expires"
,
"0"
);
re
quest.getServletContext().getRequestDispatcher("/main/webapp/index.html").forward(request, response
);//重定向
re
sponse
.
sendRedirect
(
"/main/webapp/index.html#/menu"
);
//重定向
return
;
return
;
}
*/
}
//
boolean allowUri = Pattern.matches("(.*/login/.*|.*/odata/v4/.*)", uri);
boolean
allowUri
=
Pattern
.
matches
(
"(.*/login/.*|.*/odata/v4/.*)"
,
uri
);
//排除用户登录和非Post请求
//排除用户登录和非Post请求
/*
if (!allowUri && request.getMethod().equals("POST")) {
if
(!
allowUri
&&
request
.
getMethod
().
equals
(
"POST"
))
{
//校验请求头中appKey参数: appKey为空或其不存在于系统中,或状态未鉴权通过均拦截
//校验请求头中appKey参数: appKey为空或其不存在于系统中,或状态未鉴权通过均拦截
log
.
info
(
"[OperateAuthFilter.doFilter]排除用户登录和非Post请求"
);
if
(
StringUtils
.
isEmpty
(
appKey
))
{
if
(
StringUtils
.
isEmpty
(
appKey
))
{
log
.
info
(
"[OperateAuthFilter.doFilter]appKey isEmpty"
);
if
(!
CommonConstant
.
userAuthenticationMap
.
containsKey
(
appKey
)
if
(!
CommonConstant
.
userAuthenticationMap
.
containsKey
(
appKey
)
||
CommonConstant
.
userAuthenticationMap
.
get
(
appKey
).
getIsPassAuth
()
!=
Boolean
.
TRUE
)
{
||
CommonConstant
.
userAuthenticationMap
.
get
(
appKey
).
getIsPassAuth
()
!=
Boolean
.
TRUE
)
{
String
requestParamStr
=
request
.
getReader
().
lines
().
collect
(
Collectors
.
joining
(
System
.
lineSeparator
()));
String
requestParamStr
=
request
.
getReader
().
lines
().
collect
(
Collectors
.
joining
(
System
.
lineSeparator
()));
...
@@ -223,6 +199,7 @@ public class OperateAuthFilter implements Filter {
...
@@ -223,6 +199,7 @@ public class OperateAuthFilter implements Filter {
Result
wrapResult
=
OperateAuthUtil
.
verifyRequestPermission
(
requestParamStr
,
requestValueMap
.
get
(
"roleTypeId"
));
Result
wrapResult
=
OperateAuthUtil
.
verifyRequestPermission
(
requestParamStr
,
requestValueMap
.
get
(
"roleTypeId"
));
if
(!
ResponseConstant
.
SUCCESS_CODE
.
equals
(
wrapResult
.
getCode
()))
{
if
(!
ResponseConstant
.
SUCCESS_CODE
.
equals
(
wrapResult
.
getCode
()))
{
writeResult
(
response
,
wrapResult
);
writeResult
(
response
,
wrapResult
);
log
.
info
(
"[OperateAuthFilter.doFilter]用户角色鉴权校验失败"
);
return
;
return
;
}
}
...
@@ -232,7 +209,7 @@ public class OperateAuthFilter implements Filter {
...
@@ -232,7 +209,7 @@ public class OperateAuthFilter implements Filter {
request
=
new
AuthHttpServletRequest
(
request
,
biz
,
"appKey"
,
requestValueMap
.
get
(
"appKey"
));
request
=
new
AuthHttpServletRequest
(
request
,
biz
,
"appKey"
,
requestValueMap
.
get
(
"appKey"
));
}
}
}
}
}
*/
}
filterChain
.
doFilter
(
request
,
servletResponse
);
filterChain
.
doFilter
(
request
,
servletResponse
);
}
}
...
...
srv/src/main/java/customer/lianchuangjie/master/controller/LoginController.java
View file @
e2584d4d
...
@@ -50,7 +50,7 @@ public class LoginController {
...
@@ -50,7 +50,7 @@ public class LoginController {
@PostMapping
(
value
=
"/login"
)
@PostMapping
(
value
=
"/login"
)
public
Result
login
(
@RequestBody
JSONObject
jsonObject
,
HttpServletRequest
req
)
{
public
Result
login
(
@RequestBody
JSONObject
jsonObject
,
HttpServletRequest
req
)
{
String
language
=
req
.
getHeader
(
"language"
);
/*
String language = req.getHeader("language");
// return loginService.login(UserLogin);
// return loginService.login(UserLogin);
if (StringUtils.isEmpty(jsonObject.getString("userLoginId"))) {
if (StringUtils.isEmpty(jsonObject.getString("userLoginId"))) {
return Result.error("用户名不能为空");
return Result.error("用户名不能为空");
...
@@ -63,7 +63,8 @@ public class LoginController {
...
@@ -63,7 +63,8 @@ public class LoginController {
return Result.error(MessageUtil.get("user_login_passwordWeak", language));
return Result.error(MessageUtil.get("user_login_passwordWeak", language));
}
}
return
loginService
.
login
(
jsonObject
,
req
);
return loginService.login(jsonObject, req);*/
return
loginService
.
oauthLogin
(
jsonObject
,
req
);
}
}
@PostMapping
(
value
=
"/updatePasswordAll"
)
@PostMapping
(
value
=
"/updatePasswordAll"
)
...
...
srv/src/main/java/customer/lianchuangjie/master/service/LoginService.java
View file @
e2584d4d
...
@@ -3,10 +3,12 @@ package customer.lianchuangjie.master.service;
...
@@ -3,10 +3,12 @@ package customer.lianchuangjie.master.service;
import
com.alibaba.fastjson.JSONObject
;
import
com.alibaba.fastjson.JSONObject
;
import
cds.gen.db.login.UserLogin
;
import
customer.lianchuangjie.common.vo.Result
;
import
customer.lianchuangjie.common.vo.Result
;
import
jakarta.servlet.http.HttpServletRequest
;
import
jakarta.servlet.http.HttpServletRequest
;
public
interface
LoginService
{
public
interface
LoginService
{
public
Result
login
(
JSONObject
jsonObject
,
HttpServletRequest
req
);
public
Result
login
(
JSONObject
jsonObject
,
HttpServletRequest
req
);
//新的鉴权方式
public
Result
oauthLogin
(
JSONObject
jsonObject
,
HttpServletRequest
req
);
}
}
srv/src/main/java/customer/lianchuangjie/master/service/impl/LoginServiceImpl.java
View file @
e2584d4d
...
@@ -29,10 +29,14 @@ import org.slf4j.helpers.MessageFormatter;
...
@@ -29,10 +29,14 @@ import org.slf4j.helpers.MessageFormatter;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Qualifier
;
import
org.springframework.beans.factory.annotation.Qualifier
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.security.core.Authentication
;
import
org.springframework.security.core.GrantedAuthority
;
import
org.springframework.security.core.context.SecurityContextHolder
;
import
org.springframework.stereotype.Service
;
import
org.springframework.stereotype.Service
;
import
java.net.URI
;
import
java.net.URI
;
import
java.net.URL
;
import
java.net.URL
;
import
java.util.Collection
;
import
java.util.LinkedHashSet
;
import
java.util.LinkedHashSet
;
import
java.util.List
;
import
java.util.List
;
import
java.util.stream.Collectors
;
import
java.util.stream.Collectors
;
...
@@ -126,5 +130,94 @@ public class LoginServiceImpl implements LoginService {
...
@@ -126,5 +130,94 @@ public class LoginServiceImpl implements LoginService {
return
result
;
return
result
;
}
}
@Override
public
Result
oauthLogin
(
JSONObject
jsonObject
,
HttpServletRequest
req
)
{
Result
result
=
new
Result
<>();
String
userLoginId
=
null
;
String
username
=
null
;
//用户名 邮箱
String
partyId
=
null
;
try
{
//Collection<? extends GrantedAuthority> authorities = null;
Authentication
authentication
=
SecurityContextHolder
.
getContext
().
getAuthentication
();
if
(
authentication
!=
null
&&
authentication
.
isAuthenticated
())
{
username
=
authentication
.
getName
();
//登录用户 user/sap.default/zhipeng.guo@boscloud.cn
int
lastSlashIndex
=
username
.
lastIndexOf
(
'/'
);
// 找到最后一个斜杠的索引
if
(
lastSlashIndex
!=
-
1
&&
lastSlashIndex
<
username
.
length
()
-
1
)
{
username
=
username
.
substring
(
lastSlashIndex
+
1
);
// 提取斜杠后面的字符串
}
//authorities = authentication.getAuthorities();//角色集合[]
}
if
(
username
==
null
||
""
.
equals
(
username
))
{
log
.
error
(
MessageFormatter
.
format
(
"【oauth登录失败】{}"
,
username
).
getMessage
(),
"用户名不能为空!"
);
// 这是规范的错误打印,说明错误原因
return
Result
.
error
(
"oauth登录失败"
);
}
else
{
String
finalUsername
=
username
;
CqnSelect
cqnselect
=
Select
.
from
(
cds
.
gen
.
db
.
party
.
Person_
.
class
)
.
where
(
a
->
a
.
email
().
eq
(
finalUsername
));
List
<
cds
.
gen
.
db
.
party
.
Person
>
queryResult
=
cqnService
.
run
(
cqnselect
).
listOf
(
cds
.
gen
.
db
.
party
.
Person
.
class
);
if
(
queryResult
.
isEmpty
())
{
return
Result
.
error
(
MessageUtil
.
get
(
"user_login_userNotExist"
,
req
.
getHeader
(
"language"
)));
}
partyId
=
queryResult
.
get
(
0
).
getPartyId
();
}
String
finalUserLoginPartyId
=
partyId
;
CqnSelect
cqnselect
=
Select
.
from
(
UserLogin_
.
class
)
.
where
(
a
->
a
.
partyId
().
eq
(
finalUserLoginPartyId
).
and
(
a
.
enabled
().
eq
(
CommonConstant
.
Y
)));
List
<
UserLogin
>
queryResult
=
cqnService
.
run
(
cqnselect
).
listOf
(
UserLogin
.
class
);
if
(
queryResult
.
isEmpty
())
{
return
Result
.
error
(
MessageUtil
.
get
(
"user_login_userNotExist"
,
req
.
getHeader
(
"language"
)));
}
userLoginId
=
queryResult
.
get
(
0
).
getUserLoginId
();
// 打印日志
log
.
info
(
"[LoginServiceImpl.oauthLogin] userLoginId {}"
,
userLoginId
);
log
.
info
(
"[LoginServiceImpl.oauthLogin] partyId {}"
,
partyId
);
log
.
info
(
"[LoginServiceImpl.oauthLogin] username {}"
,
username
);
log
.
info
(
"[LoginServiceImpl.oauthLogin] authorization {}"
,
req
.
getHeader
(
"authorization"
));
log
.
info
(
"[LoginServiceImpl.oauthLogin] cookie {}"
,
req
.
getHeader
(
"cookie"
));
//返回前端用户数据
//查询可以登录的角色
List
<
PartyRoleTypeView
>
partyRoleTypeViewList
=
partyRoleTypeViewDao
.
selectListLogin
(
partyId
);
String
url
=
"/odata/v4/partyService/Party?$expand=PartyGroup,PartyType,Person,PartyRole,PartyClassification&$filter=(partyId eq '"
+
partyId
+
"')"
;
log
.
debug
(
url
);
url
=
"http://localhost:"
+
httpPort
+
url
;
URL
url1
=
new
URL
(
url
);
URI
uri
=
new
URI
(
url1
.
getProtocol
(),
null
,
url1
.
getHost
(),
url1
.
getPort
(),
url1
.
getPath
(),
url1
.
getQuery
(),
null
);
HttpClient
client
=
HttpClients
.
createDefault
();
// 创建Get请求
HttpGet
httpGet
=
new
HttpGet
(
uri
);
//添加鉴权 authorization Basic c2FiaW5lOnBhc3Nfc2FiaW5l
httpGet
.
setHeader
(
"authorization"
,
req
.
getHeader
(
"authorization"
));
JSONObject
jsonObject1
=
null
;
HttpResponse
res
=
client
.
execute
(
httpGet
);
// 返回json格式:
jsonObject1
=
JSONObject
.
parseObject
(
EntityUtils
.
toString
(
res
.
getEntity
()));
if
(
res
.
getStatusLine
().
getStatusCode
()
==
200
)
{
//log.debug(jsonObject1.get("value").toString());
result
.
setSuccess
(
true
);
List
list
=
(
List
)
jsonObject1
.
get
(
"value"
);
PageVo
pageVo
=
new
PageVo
();
//登录成功,设置用户鉴权参数
UserAuthentication
userAuthentication
=
OperateAuthUtil
.
setUserAppParam
(
userLoginId
,
partyId
,
new
LinkedHashSet
<>(
partyRoleTypeViewList
.
stream
().
map
(
d
->
d
.
getRoleTypeId
()).
collect
(
Collectors
.
toList
())));
((
JSONObject
)
list
.
get
(
0
)).
put
(
"userAuthentication"
,
userAuthentication
);
((
JSONObject
)
list
.
get
(
0
)).
put
(
"PartyRole"
,
partyRoleTypeViewList
);
pageVo
.
setRecords
(
list
);
result
.
setResult
(
pageVo
);
}
else
{
return
Result
.
error
(
jsonObject1
.
getString
(
"error"
));
}
}
catch
(
Exception
e
)
{
// [A]
log
.
error
(
e
.
getMessage
(),
e
);
// 这是简单的错误打印,推荐使用 “[B]” 方式
// [B]
log
.
error
(
MessageFormatter
.
format
(
"【oauth登录失败】{}"
,
userLoginId
).
getMessage
(),
e
);
// 这是规范的错误打印,说明错误原因
return
Result
.
error
(
"oauth登录失败"
);
}
return
result
;
}
}
}
srv/src/main/resources/application.yaml
View file @
e2584d4d
...
@@ -37,26 +37,14 @@ cds:
...
@@ -37,26 +37,14 @@ cds:
security
:
security
:
mock
:
mock
:
users
:
users
:
-
name
:
klaus
-
name
:
zhipeng.guo@boscloud.cn
password
:
pass_klaus
password
:
Gzp@123456
additional
:
firstName
:
Klaus
lastName
:
Sussard
email
:
Klaus.Sussard@mail.com
-
name
:
mia
password
:
pass_mia
additional
:
firstName
:
Mia
lastName
:
Bonnellac
email
:
Mia.Bonnellac@mail.com
-
name
:
sabine
password
:
pass_sabine
roles
:
roles
:
-
Administrators
-
Administrators
additional
:
additional
:
firstName
:
Sabine
firstName
:
guo
lastName
:
Autumnpike
lastName
:
zhipeng
email
:
Sabine.Autumnpike@mail.com
email
:
zhipeng.guo@boscloud.cn
---
---
management
:
management
:
endpoint
:
endpoint
:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment