предисловие
В нашем процессе разработки JavaWeb есть более или менее сервисные методы или http-запросы, которые представляют собой всего несколько строк операторов SQL, и нам всегда приходится многократно писать уровень контроллера, сервисный уровень и уровень dao. Итак, я предпринял смелую попытку инкапсулировать общедоступный метод для этого типа метода без написания кода Java и написания нескольких строк инструкции sql для создания различных интерфейсов и методов.
дизайн стола
id | description | SQL | creator | creattime | updatetime |
---|---|---|---|---|---|
notices | Получить список уведомлений | select * from notices where reciever ={userNo} | admin | 2018-07-06 14:07:48 | 2018-07-06 14:07:53 |
мы выбираем
-
id
имя метода запрашивается для URL -
description
описать -
sql
для конкретного оператора sql - основатель
- Время создания, время модификации
первое издание
URL-запрос
пробный http://a.com/common/sqls/имя метода
Примечание. Имя метода — это идентификатор в таблице.
Участие не ограничено, здесьcontroller
слой будет переданservice
Слои, конечно, если вы хотите ограничить, вы также можете сделать белый список записей.
Уровень контроллера:
@RequestMapping(value = "/sql/{id}")
public ResultObject getRules(@PathVariable(value = "id") String id) {
ResultObject resultObject = new ResultObject();
Map<String, Object> params=getRequestParams();
validateParams(params, "token");
User user = loginService.findByToken(params.get("token").toString());
params.put("userNo",user.getUserNo());
List<Map<String,Object>> mapList=commonService.querySql(id,params);
resultObject.setData(mapList);
return resultObject;
}
Уровень контроллера в основном преобразует все полученные переменные в paramsMap, затем проверяет токен пользователя и получает пользовательское «Нет» через токен. Когда маркер отсутствует или маркер не может получить номер пользователя, создается исключение. Получите номер пользователя, поместите значение пользователя No в карту и, наконец, передайте имя метода (идентификатор) и карту на уровень службы.
Общий сервис:
/**
* 通过id找到具体sql语句
* @param id
* @return sql
*/
public String getSqlById(String id) {
return commonDao.getSqlById(id);
}
/**
* 返回通用sql执行结果
* @param id
* @param params
* @return list
*/
public List<Map<String, Object>> querySql(String id,Map<String, Object> params) {
String sql=getSqlById(id);
for (Map.Entry<String, Object> stringObjectEntry : params.entrySet()) {
sql=sql.replace("{"+stringObjectEntry.getKey()+"}","'"+(String)stringObjectEntry.getValue()+"'");
}
return commonDao.querySql(sql);
}
В первом варианте мы сначала заменяем sql, полученный методом id путем обхода всех параметров в карте, а затем выполняем его.
интерфейс:
public interface CommonDao {
String getSqlById(String id);
List<Map<String,Object>> querySql(String sql);
}
картограф:
версия мибатиса
<?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 namespace="*.basic.dao.CommonDao" >
<select id="getSqlById" resultType="string">
SELECT sql from m_sql WHERE id =#{value}
</select>
<select id="querySql" resultType="map">
${value}
</select>
</mapper>
получить параметры запроса:
public Map<String, Object> getRequestParams() {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
try {
request.setCharacterEncoding("UTF-8");
} catch (UnsupportedEncodingException var5) {
var5.printStackTrace();
}
Map<String, Object> params = new HashMap();
Enumeration names = request.getParameterNames();
while(names.hasMoreElements()) {
String name = (String)names.nextElement();
params.put(name, request.getParameter(name));
}
return params;
}
Хорошо, вот и мы, наша первая версия вышла, давайте сначала попробуем эффект!
Внутренний вызов
Когда код Java вызывается внутри, сначала создайте экземплярCommonService
, а затем позвонитеcommonService.querySql(id,params)
Просто вернитесь, будь то обычный вызов или инкапсуляция и повторное предоставление интерфейса rpc, нет никаких проблем.
Добавить запрос
Когда мы хотим добавить интерфейс для получения друзей пользователя, нам нужно всего лишь добавить запись в базу данных
id | description | SQL | creator | creattime | updatetime |
---|---|---|---|---|---|
friends | Получить список друзей | select * from freindss where userNo ={userNo} | admin | 2018-07-06 14:07:48 | 2018-07-06 14:07:53 |
перечислить
http://localhost:8080/*/common/sql/friends?token=cc4771aebb444d6c928a61ba5fe1153e
Исходящие параметры:
{"данные":[{"id":"1","имя":"Чжан Сан"}] ,"код":200,"сообщение":"успех"}
такой получитьfriends
Интерфейс просто отличный.Конечно, реальный спрос на SQL может быть очень сложным, но это не повлияет на выполнение нашего проекта.
второе издание
Хотя первая версия ок, но тут явно фатальный баг, то есть будет инжект, поэтому в этой версии надо решить проблему с инжектом.
идея первая
Входные параметры фильтра:
public List<Map<String, Object>> querySql(String id,Map<String, Object> params) {
String sql=getSqlById(id);
if (params.entrySet().size()>5) {
throw new CommonException("参数太多了,请删除一些");
}
for (Map.Entry<String, Object> stringObjectEntry : params.entrySet()) {
if (checksql((String)stringObjectEntry.getValue())) {
throw new CommonException("不安全的请求!");
}
sql=sql.replace("{"+stringObjectEntry.getKey()+"}","'"+(String)stringObjectEntry.getValue()+"'");
}
if (checksqlSecond(sql)) {
throw new CommonException("sql参数不合法!不能包含update、delete等");
}
return commonDao.querySql(sql);
}
private boolean checksql(String sql) {
if (sql.length()>50) {
return true;
}
if (!sql.equals(transactSQLInjection(sql))) {
return true;
}
if (sqlValidate(sql)) {
return true;
}
return false;
}
private boolean checksqlSecond(String sql) {
String temp_sql=sql.toLowerCase();
if (temp_sql.contains("delete")||temp_sql.contains("update")||temp_sql.contains("truncate")||temp_sql.contains("drop")) {
return true;
}
return false;
}
private String transactSQLInjection(String str)
{
return str.replaceAll(".*([';]+|(--)+).*", " ");
}
private static boolean sqlValidate(String str) {
str = str.toLowerCase();
String badStr = "'|and|exec|execute|insert|select|delete|update|count|drop|*|%|chr|mid|master|truncate|" +
"char|declare|sitename|net user|xp_cmdshell|;|or|-|+|,|like'|and|exec|execute|insert|create|drop|" +
"table|from|grant|use|group_concat|column_name|" +
"information_schema.columns|table_schema|union|where|select|delete|update|order|by|count|*|" +
"chr|mid|master|truncate|char|declare|or|;|-|--|+|,|like|//|/|%|#";//过滤掉的sql关键字,可以手动添加
String[] badStrs = badStr.split("\\|");
for (int i = 0; i < badStrs.length; i++) {
if (str.indexOf(badStrs[i]) >= 0) {
return true;
}
}
return false;
}
Мы используем обычный фильтр для фильтрации чувствительных символов. Чтобы слишком много входных параметров не влияли на наше обычное сопоставление, мы ограничиваем все 5 входных параметров и ограничиваем значение каждого параметра не более чем 50.
Если вы это сделаете, вас тут же оскорбят.Почему бы вам не использовать предварительно скомпилированный код для хорошей воды? Затем продолжаем исследовать
идея вторая
Предварительно скомпилированные параметры:
String patt = "\\{.+?}";
String querySql=sql.replaceAll(patt,"?");
Pattern r = Pattern.compile(patt);
Matcher m = r.matcher(sql);
List<String> list= new ArrayList<String>();
while(m.find()){
list.add(m.group());
}
try {
PreparedStatement preparedStatement = conn.prepareStatement(querySql);
for (int i = 0; i < list.size(); i++) {
preparedStatement.setString(i+1,params.get(list.get(i).substring(1,list.get(i).length()-1));
}
preparedStatement.executeUpdate(sql_update);
}catch(Exception e){
//e.printStackTrace();
logger.error(e.message());
}
сначала заменить на обычный
select * from notices where reciever={userNo} and isRead={isRead}
заменить
select * from notices where reciever=? and isRead=?
Затем добавьте {userNo} и {isRead} в список и, наконец, пройдитесь по списку и поместите элементы спискаuserNo
,isRead
переданное значениеpreparedStatement
.
Решение второй идеи может прекрасно решить проблему внедрения sql.Конечно, есть и другие решения, такие как использованиеmybatis
конструктор sql; используйте другие предварительно скомпилированные фреймворки sql и т. д.
Третье издание:
Теперь наша проблема с безопасностью также решена, давайте добавим некоторые общедоступные методы, такие как идентификатор пользователя в карте параметров, но не имя пользователя, и нам нужно отображать имя пользователя. Если вы используете sql-ассоциацию, различные ассоциации делают sql все более и более сложным. Здесь мы инкапсулируем некоторые общедоступные методы, такие как идентификатор пользователя в имя, идентификатор группы в имя группы.
Модернизация стола
Добавьте два поля метода ввода и метода вывода.
id | description | SQL | inmethod | outmethod | creator |
---|---|---|---|---|---|
notices | Получить список уведомлений | select * from notices where reciever =#{userNo} | usertoken2id | userid2name,groupid2name | admin |
Здесь мы поддерживаем метод с разделителями-запятыми, метод идентификации входных параметров и добавляем его к параметрам, код выглядит следующим образом:
String[] inMethodsplit = inMethod.split(",");
for (String s : inMethodsplit) {
switch (s){
case "usertoken2id":
params.add("userName",usertoken2id(params.get("userid")));
break;
case "xxxx":
params.add("xx",xxmethod(params.get("userxx")));
break;
}
}
Вы можете поддерживать внутренние общедоступные методы своей компании в коммутаторе, чтобы уменьшить объем написания sql.
Что касается участия, я думаю, все это понимают, поэтому я не буду вводить его здесь.
Суммировать
На данный момент написан фреймворк для программирования sql, напишите раздел sql, напишите несколько публичных методов (по желанию), можно выполнитьhttp接口
или普通java方法
, очень ли это удобно, и есть ли желание попробовать?
Данная статья является новой попыткой разработки, а также своего рода ориентированнойsql
Исследование программирования. (На самом деле, я не знаю, могу ли я назвать это sql-ориентированным программированием, поэтому я просто называю это так.) Если у вас есть какие-либо новые идеи и идеи, пожалуйста, оставьте сообщение.
Не забывайте следовать за мной!