增加外部API接口

This commit is contained in:
weidong 2024-07-02 14:37:20 +08:00
parent 0b78668c22
commit 0ae563942f
13 changed files with 449 additions and 30 deletions

View File

@ -0,0 +1,22 @@
package com.imdroid.secapi.dto;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "ApiKey")
public class ApiKey {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
Integer tenantid;
String tenantname;
private String apikey;
private String secret;
}

View File

@ -0,0 +1,10 @@
package com.imdroid.secapi.dto;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ApiKeyMapper extends BaseMapper<ApiKey> {
}

View File

@ -0,0 +1,104 @@
package com.imdroid.beidou.controller;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.imdroid.beidou.common.HttpResult;
import com.imdroid.common.util.AppUtils;
import com.imdroid.secapi.dto.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
import java.util.List;
@Controller
public class ApiKeyController extends BasicController {
@Autowired
ApiKeyMapper apiKeyMapper;
@Autowired
TenantMapper tenantMapper;
@Autowired
OpLogManager opLogManager;
/**** 推送页面 *****/
@RequestMapping("/sys/apikey")
public String listApiKey(Model m, HttpSession session) {
initModel(m, session);
return "/page/api_key";
}
@RequestMapping("/sys/apikey/add")
public String addApiKey(Model m, HttpSession session){
initModel(m, session);
QueryWrapper<Tenant> queryWrapper = new QueryWrapper<>();
Integer tenantId = getTenantId(session);
if (tenantId != null && tenantId != Tenant.SAAS_PROVIDER_ID) {
queryWrapper.eq("id", tenantId);
}
List<Tenant> tenants = tenantMapper.selectList(queryWrapper);
m.addAttribute("tenant_list", tenants);
String apiKey = AppUtils.getAppId();
String apiSecret = AppUtils.getAppSecret(apiKey);
m.addAttribute("api_key", apiKey);
m.addAttribute("api_secret", apiSecret);
return "/page/table/api_key_add";
}
/**** 推送数据 *****/
@RequestMapping("/sys/apikey/list")
@ResponseBody
public JSONObject list(HttpSession session, int page, int limit) {
Page<ApiKey> pageable = new Page<>(page, limit);
QueryWrapper<ApiKey> queryWrapper = new QueryWrapper<>();
Integer tenantId = getTenantId(session);
if (tenantId != null && tenantId != Tenant.SAAS_PROVIDER_ID) {
queryWrapper.eq("tenantid", tenantId);
}
IPage<ApiKey> cs = apiKeyMapper.selectPage(pageable, queryWrapper);
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", 0);
jsonObject.put("msg", "");
jsonObject.put("count", cs.getTotal());
jsonObject.put("data", cs.getRecords());
return jsonObject;
}
@PostMapping("/sys/apikey/update")
@ResponseBody
public String update(HttpSession session, @RequestBody JSONObject object) throws Exception {
// 从请求参数中创建对象
ApiKey apiKey = JSONObject.toJavaObject(object,ApiKey.class);
Tenant tenant = tenantMapper.selectById(apiKey.getTenantid());
int num = 0;
if(tenant!=null) {
apiKey.setTenantname(tenant.getName());
num = apiKeyMapper.insert(apiKey);
}
if (num == 0) {
return HttpResult.failed();
} else {
return HttpResult.ok();
}
}
@PostMapping("/sys/apikey/delete")
@ResponseBody
public String delete(HttpSession session, @RequestParam Integer id) throws Exception {
int num = apiKeyMapper.deleteById(id);
opLogManager.addLog(getLoginUser(session),getTenantId(session),
OpLogManager.OP_TYPE_DEL,
OpLogManager.OP_OBJ_SYS,
"api key deleted");
if (num == 0) {
return HttpResult.failed();
} else return HttpResult.ok();
}
}

View File

@ -303,3 +303,12 @@ CREATE TABLE IF NOT EXISTS `OperationLog` (
`content` varchar(256) DEFAULT NULL, `content` varchar(256) DEFAULT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `ApiKey` (
`id` bigint(20) AUTO_INCREMENT,
`tenantid` int DEFAULT 0,
`apikey` varchar(64) NOT NULL,
`secret` varchar(256) NOT NULL,
`tenantname` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

View File

@ -162,6 +162,12 @@
"href": "sys/oplog", "href": "sys/oplog",
"icon": "fa fa-book", "icon": "fa fa-book",
"target": "_self" "target": "_self"
},
{
"title": "API",
"href": "sys/apikey",
"icon": "fa fa-key",
"target": "_self"
} }
] ]
} }

View File

@ -0,0 +1,110 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Api Key</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="../lib/layui-v2.6.3/css/layui.css" media="all">
<link rel="stylesheet" href="../css/public.css" media="all">
</head>
<body>
<div class="layuimini-container">
<div class="layuimini-main">
<table class="layui-hide" id="currentTableId" lay-filter="currentTableFilter"></table>
<script type="text/html" id="currentTableBar">
<a class="layui-btn layui-btn-xs layui-btn-danger data-count-delete" lay-event="delete">删除</a>
</script>
<script type="text/html" id="toolbarTop">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-normal layui-btn-sm data-add-btn" lay-event="add">生成</button>
</div>
</script>
</div>
</div>
<script src="../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script th:inline="none">
layui.use(['form', 'table'], function () {
var $ = layui.$,
form = layui.form,
table = layui.table;
/**
* 初始化表单,要加上,不然刷新部分组件可能会不加载
*/
form.render();
table.render({
elem: '#currentTableId',
url: '/sys/apikey/list',
toolbar: '#toolbarTop',
cols: [[
{field: 'tenantname', title: '所属组织'},
{field: 'apikey', title: 'Api Key'},
{field: 'secret', title: 'Api Secret'},
{title: '操作', toolbar: '#currentTableBar', align: "center", minWidth: 120}
]],
limits: [10, 20, 50, 100, 150],
limit: 10,
page: true,
skin: 'line'
});
/**
* toolbar事件监听
*/
table.on('toolbar(currentTableFilter)', function (obj) {
if (obj.event === 'add') { // 监听添加操作
var index = layer.open({
title: '新增',
type: 2,
shade: 0.2,
maxmin:true,
shadeClose: true,
offset: 'rb',
anim: 2,
area: ['50%', '100%'],
content: '/sys/apikey/add'
});
$(window).on("resize", function () {
layer.full(index);
});
}
});
table.on('tool(currentTableFilter)', function (obj) {
var data = obj.data;
if (obj.event === 'delete') {
layer.confirm('确定删除'+data.name+"?", function(index){
$.ajax({
type:"POST",
url:"/sys/apikey/delete",
data:{
'id':data.id
},
success: function (data) {
//data是cotroller相应处理函数的返回值
table.reload('currentTableId');
},
error: function () {
console.log("ajax error");
}
});
layer.close(index);
});
}
});
});
function onBaseParaUpdated(){
layui.table.reload('currentTableId');
}
</script>
</body>
</html>

View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>API Key</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="../../lib/layui-v2.6.3/css/layui.css" media="all">
<link rel="stylesheet" href="../../css/public.css" media="all">
<style>
body {
background-color: #ffffff;
}
</style>
</head>
<body>
<div class="layui-form layuimini-form">
<input type="hidden" name="id" id="id">
<div class="layui-form-item">
<label class="layui-form-label required">API Key</label>
<div class="layui-input-block">
<input type="text" name="apikey" id="apikey" th:value="${api_key}" class="layui-input" readonly>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">API Secret</label>
<div class="layui-input-block">
<input type="text" name="secret" id="secret" th:value="${api_secret}" class="layui-input" readonly>
</div>
</div>
<div class="layui-form-item" name="div_tenant" id="div_tenant">
<div class="layui-input-group">
<label class="layui-form-label">所属企业</label>
<div class="layui-input-inline">
<select name="tenantid" id="tenantid" lay-filter="tenant_id">
<option th:each="item : ${tenant_list}" th:text="${item.name}" th:value="${item.id}"></option>
</select>
</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="saveBtn">确认保存</button>
</div>
</div>
</div>
<script src="../../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script>
layui.use(['form'], function () {
var form = layui.form,
$ = layui.$;
var iframeIndex = parent.layer.getFrameIndex(window.name);
//监听提交
form.on('submit(saveBtn)', function (data) {
$.ajax({
type:"POST",
url:"/sys/apikey/update",
contentType: "application/json;charset=UTF-8",
data: JSON.stringify(data.field),
success: function (result) {
parent.onBaseParaUpdated();
parent.layer.close(iframeIndex);
},
error: function () {
console.log("ajax error");
parent.layer.close(iframeIndex);
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>组织</title> <title>用户</title>
<meta name="renderer" content="webkit"> <meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

View File

@ -0,0 +1,76 @@
package com.imdroid.common.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.UUID;
public class AppUtils {
//生成 app_secret 密钥
private final static String SERVER_NAME = "mazhq_abc123";
private final static String[] chars = new String[]{"a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
"t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z"};
/**
* @Description: <p>
* 短8位UUID思想其实借鉴微博短域名的生成方式但是其重复概率过高而且每次生成4个需要随即选取一个
* 本算法利用62个可打印字符通过随机生成32位UUID由于UUID都为十六进制所以将UUID分成8组每4个为一组然后通过模62操作结果作为索引取出字符
* 这样重复率大大降低
* 经测试在生成一千万个数据也没有出现重复完全满足大部分需求
* </p>
* @author mazhq
* @date 2019/8/27 16:16
*/
public static String getAppId() {
StringBuffer shortBuffer = new StringBuffer();
String uuid = UUID.randomUUID().toString().replace("-", "");
for (int i = 0; i < 8; i++) {
String str = uuid.substring(i * 4, i * 4 + 4);
int x = Integer.parseInt(str, 16);
shortBuffer.append(chars[x % 0x3E]);
}
return shortBuffer.toString();
}
/**
* <p>
* 通过appId和内置关键词生成APP Secret
* </P>
* @author mazhq
* @date 2019/8/27 16:32
*/
public static String getAppSecret(String appId) {
try {
String[] array = new String[]{appId, SERVER_NAME};
StringBuffer sb = new StringBuffer();
// 字符串排序
Arrays.sort(array);
for (int i = 0; i < array.length; i++) {
sb.append(array[i]);
}
String str = sb.toString();
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(str.getBytes());
byte[] digest = md.digest();
StringBuffer hexstr = new StringBuffer();
String shaHex = "";
for (int i = 0; i < digest.length; i++) {
shaHex = Integer.toHexString(digest[i] & 0xFF);
if (shaHex.length() < 2) {
hexstr.append(0);
}
hexstr.append(shaHex);
}
return hexstr.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
}

View File

@ -7,8 +7,8 @@ import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.exceptions.JWTVerificationException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.imdroid.secapi.dto.User; import com.imdroid.secapi.dto.ApiKey;
import com.imdroid.secapi.dto.UserMapper; import com.imdroid.secapi.dto.ApiKeyMapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
@ -20,7 +20,7 @@ import javax.servlet.http.HttpServletResponse;
@Component @Component
public class JwtInterceptor implements HandlerInterceptor { public class JwtInterceptor implements HandlerInterceptor {
@Autowired @Autowired
private UserMapper userMapper; private ApiKeyMapper apiMapper;
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization"); String token = request.getHeader("Authorization");
@ -34,23 +34,23 @@ public class JwtInterceptor implements HandlerInterceptor {
return false; return false;
} }
// 获取 token 中的userId // 获取 token 中的userId
String userId; String apiKey;
try { try {
userId = JWT.decode(token).getAudience().get(0); apiKey = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) { } catch (JWTDecodeException j) {
response.setStatus(401); response.setStatus(401);
return false; return false;
} }
//根据token中的userid查询数据库 //根据token中的userid查询数据库
QueryWrapper<User> queryWrapper = new QueryWrapper<>(); QueryWrapper<ApiKey> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name",userId); queryWrapper.eq("apikey",apiKey);
User user = userMapper.selectOne(queryWrapper); ApiKey api = apiMapper.selectOne(queryWrapper);
if (user == null) { if (api == null) {
response.setStatus(401); response.setStatus(401);
return false; return false;
} }
// 用户密码加签验证 token // 用户密码加签验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build(); JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(api.getSecret())).build();
try{ try{
jwtVerifier.verify(token); // 验证token jwtVerifier.verify(token); // 验证token
} catch (JWTVerificationException e) { } catch (JWTVerificationException e) {
@ -58,7 +58,7 @@ public class JwtInterceptor implements HandlerInterceptor {
return false; return false;
} }
request.setAttribute("user",user); request.setAttribute("tenantId",api.getTenantid());
return true; return true;
} }
} }

View File

@ -24,9 +24,9 @@ public class DeviceApi {
@GetMapping(value = "/gnss/device" ) @GetMapping(value = "/gnss/device" )
public String query(HttpServletRequest request, String projectName) { public String query(HttpServletRequest request, String projectName) {
User user = (User) request.getAttribute("user"); Integer tenantId = (Integer) request.getAttribute("tenantId");
List<GnssStatusJoin> deviceList = deviceMapper.queryDeployedByProject(user.getTenantid(),projectName); List<GnssStatusJoin> deviceList = deviceMapper.queryDeployedByProject(tenantId,projectName);
DeviceQueryResponse deviceQueryResponse = new DeviceQueryResponse(); DeviceQueryResponse deviceQueryResponse = new DeviceQueryResponse();
deviceQueryResponse.OK(); deviceQueryResponse.OK();
for(GnssStatusJoin device:deviceList){ for(GnssStatusJoin device:deviceList){

View File

@ -38,26 +38,26 @@ public class GnssDataApi {
queryWrapper.isNotNull("rpose"); queryWrapper.isNotNull("rpose");
if(!StrUtil.isBlank(beginDate)){ if(!StrUtil.isBlank(beginDate)){
queryWrapper.ge("createtime",beginDate); queryWrapper.ge("t.createtime",beginDate);
} }
if(!StrUtil.isBlank(endDate)){ if(!StrUtil.isBlank(endDate)){
queryWrapper.ge("createtime",endDate); queryWrapper.ge("t.createtime",endDate);
} }
if(StrUtil.isBlank(sortType) || sortType.equals("asc")){ if(StrUtil.isBlank(sortType) || sortType.equals("asc")){
queryWrapper.orderByAsc("createtime"); queryWrapper.orderByAsc("t.createtime");
} }
else{ else{
queryWrapper.orderByDesc("createtime"); queryWrapper.orderByDesc("t.createtime");
} }
if(pageSize!=null){ if(pageSize!=null){
if(pageSize > 10000) pageSize=10000; if(pageSize > 1000) pageSize=1000;
queryWrapper.last("limit "+pageSize); queryWrapper.last("limit "+pageSize);
} }
else{ else{
queryWrapper.last("limit 500"); queryWrapper.last("limit 100");
} }
List<GnssCalcData> dataList = dataMapper.selectList(queryWrapper); List<GnssCalcData> dataList = dataMapper.selectList(queryWrapper);
@ -73,7 +73,7 @@ public class GnssDataApi {
gnssData.setCreateTime(data.getCreatetime().format(formatter)); gnssData.setCreateTime(data.getCreatetime().format(formatter));
gnssData.setRpose(data.getRpose()); gnssData.setRpose(data.getRpose());
gnssData.setRposn(data.getRposn()); gnssData.setRposn(data.getRposn());
gnssData.setRposd(data.getRposd()); gnssData.setRposu(data.getRposd());
gnssResponse.getData().add(gnssData); gnssResponse.getData().add(gnssData);
lastRecordTime = recordTime.plusMinutes(30); lastRecordTime = recordTime.plusMinutes(30);
} }

View File

@ -1,7 +1,6 @@
package com.imdroid.sec_exapi.controller; package com.imdroid.sec_exapi.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.imdroid.common.util.BCryptPasswordEncoderUtil;
import com.imdroid.common.util.GsonUtil; import com.imdroid.common.util.GsonUtil;
import com.imdroid.sec_exapi.auth.TokenUtils; import com.imdroid.sec_exapi.auth.TokenUtils;
import com.imdroid.sec_exapi.entity.LoginResponse; import com.imdroid.sec_exapi.entity.LoginResponse;
@ -21,34 +20,35 @@ public class UserApi {
@Autowired @Autowired
private TenantMapper tenantMapper; private TenantMapper tenantMapper;
@Autowired @Autowired
UserMapper userMapper; ApiKeyMapper apiMapper;
@Autowired @Autowired
OpLogManager opLogManager; OpLogManager opLogManager;
@Autowired @Autowired
GnssDeviceMapper deviceMapper; GnssDeviceMapper deviceMapper;
private final BCryptPasswordEncoderUtil bCryptPasswordEncoderUtil=new BCryptPasswordEncoderUtil();
final Logger logger = LoggerFactory.getLogger(UserApi.class); final Logger logger = LoggerFactory.getLogger(UserApi.class);
// 登录 // 登录
@PostMapping(value = "/login" ) @PostMapping(value = "/login" )
public String login(String apiKey, String apiSecret, HttpServletResponse response) { public String login(String apiKey, String apiSecret, HttpServletResponse response) {
User user = userMapper.queryByName(apiKey); QueryWrapper<ApiKey> queryWrapper1 = new QueryWrapper<>();
queryWrapper1.eq("apikey", apiKey);
ApiKey api = apiMapper.selectOne(queryWrapper1);
LoginResponse loginResponse = new LoginResponse(); LoginResponse loginResponse = new LoginResponse();
if (user == null || !bCryptPasswordEncoderUtil.matches(apiSecret, user.getPassword())) { if (api == null || !api.getSecret().equals(apiSecret)) {
loginResponse.failed(); loginResponse.failed();
} }
else { else {
opLogManager.addLog(user.getName(), user.getTenantid(), opLogManager.addLog(api.getApikey(), api.getTenantid(),
OpLogManager.OP_TYPE_LOGIN, OpLogManager.OP_TYPE_LOGIN,
OpLogManager.OP_OBJ_USER, OpLogManager.OP_OBJ_USER,
"api login"); "api login");
loginResponse.OK(); loginResponse.OK();
String token = TokenUtils.getToken(apiKey, user.getPassword()); String token = TokenUtils.getToken(apiKey, apiSecret);
loginResponse.setToken(token); loginResponse.setToken(token);
//查询项目 //查询项目
Tenant tenant = tenantMapper.selectById(user.getTenantid()); Tenant tenant = tenantMapper.selectById(api.getTenantid());
QueryWrapper<GnssDevice> queryWrapper = new QueryWrapper<>(); QueryWrapper<GnssDevice> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("tenantid", tenant.getId()); queryWrapper.eq("tenantid", tenant.getId());
queryWrapper.eq("opmode", GnssDevice.OP_MODE_USE); queryWrapper.eq("opmode", GnssDevice.OP_MODE_USE);