【问题标题】:Express/Knex/Sql query with dynamic interpolation not working具有动态插值的 Express/Knex/Sql 查询不起作用
【发布时间】:2020-04-05 20:54:26
【问题描述】:

我有一个 Express/Postgres 后端,它使用以下路线返回基于过去 X 天累积的积分的排行榜。例如,当我在下面的查询中将“points_horizo​​n”硬编码为 7 时,它会返回基于过去 7 天的排行榜。但是,我希望能够为每个组指定属性points_horizon,以调整排行榜包含的天数。但是,下面的插值不起作用,返回错误:ReferenceError: points_horizon is not defined

// returns total number of acts per user over the last 'points_horizon' days
router.get('/leaderboard/:group_id', async (req, res) => {
  let results = await knex.raw('
SELECT memberships.users_id, users.username, avatar_url, COUNT(acts.users_id) 
FROM memberships 
JOIN groups ON memberships.groups_id = groups.id 
JOIN users ON memberships.users_id = users.id 
LEFT JOIN acts ON acts.users_id = users.id 
AND acts.created_at >= (CURRENT_DATE - ' + points_horizon + ') 
WHERE memberships.groups_id = ' + req.params.group_id + ' 
GROUP BY memberships.users_id, users.username, avatar_url 
ORDER BY COUNT(acts.id) DESC');
  console.log('This is the reports/leaderboard query response', results.rows)
  res.json(results.rows);
});

要添加更多详细信息,以下是 points_horizo​​n 的设置方式:

这是排行榜组件:

<template>
  <div class="table-responsive mt-2">
    <table class="ui celled table">
      <thead>
        <tr><th colspan="4">Leaderboard for {{ currentGroup.name }}</th></tr>
        <tr>
          <th>Username</th>
          <th>Last Action</th>
          <th>Date</th>
          <th>Score</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(leader) in leaderboard" :key="leader.users_id">
          <td>
            <h4 class="ui image header">
              <img v-if="leader.avatar_url" :src="leader.avatar_url"  class="ui mini rounded image">
              <img v-else :src="'https://robohash.org/'+ leader.username" class="ui mini rounded image"/>
              <router-link :to="`/users/${leader.users_id}`" class="content">
                {{leader.username}}
              </router-link>
            </h4>
          </td>
          <td>{{ lastUserAct.deed }}</td>
          <td></td>
          <!-- <td>{{ lastAct(leader.id).deed }}</td>
          <td>{{ moment(lastAct(leader.id).created_at).strftime("%A, %d %b %Y %l:%M %p") }}</td> -->
          <td>{{leader.count}}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
import moment from 'moment-strftime'
import _ from 'lodash'
import ReportsService from '@/services/ReportsService'
import ActsService from '@/services/ActsService'
export default {
  name: "Leaderboard",
  data() {
    return {
      lastUserAct: {}
    }
  },
  computed: {
    leaderboard () {
      return this.$store.getters.leaderboard;
    },
    currentGroup () {
      return this.$store.getters.currentGroup;
    }
    // ,
    // lastAct (userId) {
    //   return _.orderBy(this.actsByUser(userId), 'created_at')[0];
    // }
  },
  mounted () {
    this.getLeaderboard();
  },
  methods: {
    getLeaderboard: async function () {
      console.log('in LeaderBoard, this is currentGroup: ', this.$store.getters.currentGroup.name)
      this.$store.dispatch("updateLeaderboard", this.currentGroup);
    },
    moment: function (datetime) {
      return moment(datetime);
    }
    ,
    async lastActByUser (leader_id) {
      console.log('in Leaderboard, getting last act for user')
      const response = await ActsService.fetchLastActByUser ({
        userId: leader_id
      });
      this.lastUserAct = response.data
      console.log('in Leaderboard, lastAct response: ', response.data)
    }
  }
};
</script> 

这里是 store.js:

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import PostsService from './services/PostsService'
import ReportsService from './services/ReportsService'
...

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    ...
    leaderboard: [],
    currentGroup: {},
    lastAct: '',
    ...
  },
  mutations: {
    ...
    setLeaderboard(state, leaderboard) {
      state.leaderboard = leaderboard
    },
    setCurrentGroup(state, group) {
      state.currentGroup = group
    },...
  },
  actions: {
    ...
    getUserGroups({ commit }) {
      GroupsService.getGroups()
        .then(resp => {
          console.log('in store getUserGroups, this is usergroups: ', resp);
            commit('setCurrentGroup', resp.data[0]);
        });
    },
    updateLeaderboard({ commit }, group) {
      ReportsService.getLeaderboard(group.id)
        .then(resp => {
          commit('setLeaderboard', resp);
        });
    },...
  },
  getters: {
    ...
    leaderboard: state => {
      return state.leaderboard;
    },
    currentGroup: state => {
      return state.currentGroup;
    },

  }
})

这里是调用 api 的 ReportsService:

import axios from 'axios'

export default {
  async getLeaderboard (group_id) {
    let response = await axios.get('reports/leaderboard/' + group_id) 
    console.log('In ReportsService, leaderboard response: ', response.data.id);
    if (response.status === 200) {
      return response.data;
    }
  }
}

我想问题是如何将组传递到快速溃败 s.t.我可以在查询中使用它。

一些更改,但仍然无法检索组属性:

我将 store.js 中的相关动作调整为:

updateLeaderboard({ commit }, group) {
  ReportsService.getLeaderboard(group)
    .then(resp => {
      commit('setLeaderboard', resp);
    });
},

将 ReportsService 更改为:

import axios from 'axios'

export default {
  async getLeaderboard (group) {
    let response = await axios.get('reports/leaderboard/' + group.id, {
      params: {
        group: group
      }
    }) 
    console.log('In ReportsService, leaderboard response: ', response.data.id);
    if (response.status === 200) {
      return response.data;
    }
  }
}

在服务器端路由:

// returns total number of acts per user over the last 'points_horizon' days
router.get('/leaderboard/:group_id', async (req, res) => {
  console.log('this is req group object: ', req.query.group)
  let results = await knex.raw('SELECT memberships.users_id, users.username, avatar_url, COUNT(acts.users_id) FROM memberships JOIN groups ON memberships.groups_id = groups.id JOIN users ON memberships.users_id = users.id LEFT JOIN acts ON acts.users_id = users.id AND acts.created_at >= (CURRENT_DATE - 13) WHERE memberships.groups_id = ' + req.params.group_id + ' GROUP BY memberships.users_id, users.username, avatar_url ORDER BY COUNT(acts.id) DESC');
  console.log('This is the reports/leaderboard query response', results.rows)
  res.json(results.rows);
});

在console.log中,req.query.group返回:

this is req group object:  {"id":2,"name":"Tuesday Group","description":"We meet every other Tuesday in person!","created_at":null,"updated_at":null,"owners_id":null,"max_members":10,"private_group":false,"address":null,"latitude":null,"longitude":null,"points_horizon":14}

但是,req.query.group.idreq.query.group["id"] 返回 undefined

req.query.group.constructor.name 返回String

最后更新: JSON.parse(req.query.group).points_horizon 返回正确的值。但是,我不明白为什么 req.query.group 不返回 json,也不知道这是否是正确的方法......

【问题讨论】:

  • 似乎 points_horizon 变量没有在 javascript 中设置——或者,你能显示一个更大的代码 sn-p 来显示 points_horizon 的设置位置/方式吗?或者,您的意思是使用req.params.points_horizon 而不是简单的points_horizon
  • points_horizo​​n 是组模型的一个属性。我还尝试了返回 ReferenceError: groups is not defined 的 groups.points_horizo​​n

标签: sql postgresql express knex.js


【解决方案1】:

由于没有足够的数据来给出更好的答案,这里的问题是您的变量 points_horizon 不存在(正如评论指出的那样)。

您使用 knex 的方式也容易受到 SQL 注入攻击。当您将文字值传递给查询时,您应该使用? 参数绑定,以便数据库驱动程序可以处理转义变量。有关 knex 如何处理 SQL 注入攻击保护的更多信息,请参见此处:

Does Knex.js prevent sql injection?

【讨论】:

  • 感谢此关于 sql 注入的说明。我会回去解决这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-07
  • 1970-01-01
  • 2021-07-04
相关资源
最近更新 更多