首页 > 解决方案 > Java Spring Boot 和 React Native 应用程序同时向 api 发送 id 和 object

问题描述

我有一个用于任务管理的移动应用程序。我使用 jhipster 和 ignite-cli 创建了应用程序,但是当我尝试从前端执行 put 请求时,我给出了一个错误,因为 put 方法需要 id 和对象,但只有对象从前端转到 api。

api.js

// a library to wrap and simplify api calls
import apisauce from 'apisauce'
import CookieManager from '@react-native-community/cookies'

import AppConfig from '../../config/app-config'

// our "constructor"
const create = (baseURL = AppConfig.apiUrl) => {
  // ------
  // STEP 1
  // ------
  //
  // Create and configure an apisauce-based api object.
  //
  const api = apisauce.create({
    // base URL is read from the "constructor"
    baseURL,
    // here are some default headers
    headers: {
      'Cache-Control': 'no-cache',
    },
    // 10 second timeout...
    timeout: 10000,
  })
  api.addAsyncRequestTransform((request) => async (request) => {
    const cookies = await CookieManager.get(baseURL)
    if (cookies['XSRF-TOKEN'] && cookies['XSRF-TOKEN'].value) {
      request.headers['X-XSRF-TOKEN'] = cookies['XSRF-TOKEN'].value
    }
    return request
  })

  // ------
  // STEP 2
  // ------
  //
  // Define some functions that call the api.  The goal is to provide
  // a thin wrapper of the api layer providing nicer feeling functions
  // rather than "get", "post" and friends.
  //
  // I generally don't like wrapping the output at this level because
  // sometimes specific actions need to be take on `403` or `401`, etc.
  //
  // Since we can't hide from that, we embrace it by getting out of the
  // way at this level.
  //
  const login = (userAuth) =>
    api.post('api/authentication', userAuth, {
      headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json, text/plain, */*' },
    })
  const logout = () => api.post('api/logout')
  const register = (user) => api.post('api/register', user)
  const forgotPassword = (data) =>
    api.post('api/account/reset-password/init', data, {
      headers: { 'Content-Type': 'text/plain', Accept: 'application/json, text/plain, */*' },
    })

  const getAccount = () => api.get('api/account')
  const updateAccount = (account) => api.post('api/account', account)
  const changePassword = (currentPassword, newPassword) =>
    api.post(
      'api/account/change-password',
      { currentPassword, newPassword },
      { headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*' } },
    )

  const getUser = (userId) => api.get('api/users/' + userId)
  const getUsers = (options) => api.get('api/users', options)
  const createUser = (user) => api.post('api/users', user)
  const updateUser = (user) => api.put('api/users', user)
  const deleteUser = (userId) => api.delete('api/users/' + userId)

  const getTask = (taskId) => api.get('api/tasks/' + taskId)
  const getTasks = (options) => api.get('api/tasks', options)
  const createTask = (task) => api.post('api/tasks', task)
  const updateTask = (task) => api.put('api/tasks', task)
  const deleteTask = (taskId) => api.delete('api/tasks/' + taskId)
  const searchTasks = (query) => api.get('api/_search/tasks', { query: query })
  // ignite-jhipster-api-method-needle

  // ------
  // STEP 3
  // ------
  //
  // Return back a collection of functions that we would consider our
  // interface.  Most of the time it'll be just the list of all the
  // methods in step 2.
  //
  // Notice we're not returning back the `api` created in step 1?  That's
  // because it is scoped privately.  This is one way to create truly
  // private scoped goodies in JavaScript.
  //
  return {
    // a list of the API functions from step 2
    createUser,
    updateUser,
    getUsers,
    getUser,
    deleteUser,

    createTask,
    updateTask,
    getTasks,
    getTask,
    deleteTask,
    searchTasks,
    // ignite-jhipster-api-export-needle
    logout,
    login,
    register,
    forgotPassword,
    getAccount,
    updateAccount,
    changePassword,
  }
}

// let's return back our create method as the default.
export default {
  create,
}

任务实体编辑screen.js

import React, { createRef, useRef } from 'react'
import { ActivityIndicator, Alert, Text, TouchableHighlight, View } from 'react-native'
import { connect } from 'react-redux'
import TaskActions from './tasks.reducer'
import UserActions from '../../../shared/reducers/user.reducer'
import { Navigation } from 'react-native-navigation'
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'
import { taskEntityDetailScreen } from '../../../navigation/layouts'

import t from 'tcomb-form-native'

import styles from './tasks-entity-edit-screen-style'

let Form = t.form.Form

class TaskEntityEditScreen extends React.Component {
  constructor(props) {
    super(props)
    Navigation.events().bindComponent(this)
    this.state = {
      formModel: t.struct({
        id: t.maybe(t.Number),
        task: t.String,
        deadline: t.Date,
        userId: this.getUsers(),
      }),
      formValue: { id: null },
      formOptions: {
        fields: {
          id: {
            hidden: true,
          },
          userId: {
            testID: 'userIdInput',
            label: 'User',
          },
          task: {
            returnKeyType: 'next',
            onSubmitEditing: () => this.form.getComponent('deadline').refs.input.focus(),
            testID: 'taskInput',
          },
          deadline: {
            mode: 'date',
            testID: 'deadlineInput',
          },
        },
      },
      task: {},
      isNewEntity: true,
    }
    if (props.data && props.data.entityId) {
      this.state.isNewEntity = false
      this.props.getTask(props.data.entityId)
    }
    this.props.getAllUsers()

    this.submitForm = this.submitForm.bind(this)
    this.formChange = this.formChange.bind(this)
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.task !== prevState.task && !prevState.isNewEntity) {
      return { formValue: entityToFormValue(nextProps.task), task: nextProps.task }
    }
    return null
  }

  componentDidUpdate(prevProps) {
    if (prevProps.updating && !this.props.updating) {
      if (this.props.error) {
        Alert.alert('Error', 'Something went wrong updating the entity', [{ text: 'OK' }])
      } else {
        const entityId = this.props.task.id
        this.props.reset()
        this.props.getTask(entityId)
        const alertOptions = [{ text: 'OK' }]
        if (!this.state.formValue.id) {
          alertOptions.push({
            text: 'View',
            onPress: taskEntityDetailScreen.bind(this, { entityId }),
          })
        }
        Navigation.pop(this.props.componentId)
        Alert.alert('Success', 'Entity saved successfully', alertOptions)
      }
    }
  }

  getUsers = () => {
    const users = {}
    this.props.users.forEach((user) => {
      users[user.id] = user.login ? user.login.toString() : user.id.toString()
    })
    return t.maybe(t.enums(users))
  }
  submitForm() {
    // call getValue() to get the values of the form
    const task = this.form.getValue()

    if (task) {
      // if validation fails, value will be null
      this.props.updateTask(formValueToEntity(task))
    }
  }

  formChange(newValue) {
    this.setState({
      formValue: newValue,
    })
  }

  render() {
    if (this.props.fetching) {
      return (
        <View style={styles.loading}>
          <ActivityIndicator size="large" />
        </View>
      )
    }
    return (
      <View style={styles.container}>
        <KeyboardAwareScrollView keyboardShouldPersistTaps={'handled'} testID="taskEditScrollView">
          <Form
            ref={(c) => {
              this.form = c
            }}
            type={this.state.formModel}
            options={this.state.formOptions}
            value={this.state.formValue}
            onChange={this.formChange}
          />
          <TouchableHighlight style={styles.button} onPress={this.submitForm} underlayColor="#99d9f4" testID="submitButton">
            <Text style={styles.buttonText}>Save</Text>
          </TouchableHighlight>
        </KeyboardAwareScrollView>
      </View>
    )
  }
}
// convenience methods for customizing the mapping of the entity to/from the form value
const entityToFormValue = (value) => {
  if (!value) {
    return {}
  }
  return {
    id: value.id || null,
    task: value.task || null,
    deadline: value.deadline || null,
    userId: value.user && value.user.id ? value.user.id : null,
  }
}

const formValueToEntity = (value) => {
  const entity = {
    id: value.id || null,
    task: value.task || null,
    deadline: value.deadline || null,
  }
  if (value.userId) {
    entity.user = { id: value.userId }
  }
  return entity
}

const mapStateToProps = (state) => {
  return {
    users: state.users.users || [],
    task: state.tasks.task,
    fetching: state.tasks.fetchingOne,
    updating: state.tasks.updating,
    error: state.tasks.errorUpdating,
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    getAllUsers: (options) => dispatch(UserActions.userAllRequest(options)),
    getTask: (id) => dispatch(TaskActions.taskRequest(id)),
    getAllTasks: (options) => dispatch(TaskActions.taskAllRequest(options)),
    updateTask: (task) => dispatch(TaskActions.taskUpdateRequest(task)),
    reset: () => dispatch(TaskActions.taskReset()),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(TaskEntityEditScreen)

task.reducer.js

import { createReducer, createActions } from 'reduxsauce'
import Immutable from 'seamless-immutable'
import { loadMoreDataWhenScrolled } from '../../../shared/util/pagination-utils'
import { parseHeaderForLinks } from '../../../shared/util/url-utils'

/* ------------- Types and Action Creators ------------- */

const { Types, Creators } = createActions({
  taskRequest: ['taskId'],
  taskAllRequest: ['options'],
  taskUpdateRequest: ['task'],
  taskSearchRequest: ['query'],
  taskDeleteRequest: ['taskId'],

  taskSuccess: ['task'],
  taskAllSuccess: ['tasks', 'headers'],
  taskUpdateSuccess: ['task'],
  taskSearchSuccess: ['tasks'],
  taskDeleteSuccess: [],

  taskFailure: ['error'],
  taskAllFailure: ['error'],
  taskUpdateFailure: ['error'],
  taskSearchFailure: ['error'],
  taskDeleteFailure: ['error'],

  taskReset: [],
})

export const TaskTypes = Types
export default Creators

/* ------------- Initial State ------------- */

export const INITIAL_STATE = Immutable({
  fetchingOne: null,
  fetchingAll: null,
  updating: null,
  searching: null,
  deleting: null,
  task: null,
  tasks: [],
  errorOne: null,
  errorAll: null,
  errorUpdating: null,
  errorSearching: null,
  errorDeleting: null,
  links: { next: 0 },
  totalItems: 0,
})

/* ------------- Reducers ------------- */

// request the data from an api
export const request = (state) =>
  state.merge({
    fetchingOne: true,
    errorOne: false,
    task: null,
  })

// request the data from an api
export const allRequest = (state) =>
  state.merge({
    fetchingAll: true,
    errorAll: false,
  })

// request to update from an api
export const updateRequest = (state) =>
  state.merge({
    updating: true,
  })
// request to search from an api
export const searchRequest = (state) =>
  state.merge({
    searching: true,
  })
// request to delete from an api
export const deleteRequest = (state) =>
  state.merge({
    deleting: true,
  })

// successful api lookup for single entity
export const success = (state, action) => {
  const { task } = action
  return state.merge({
    fetchingOne: false,
    errorOne: null,
    task,
  })
}
// successful api lookup for all entities
export const allSuccess = (state, action) => {
  const { tasks, headers } = action
  const links = parseHeaderForLinks(headers.link)
  return state.merge({
    fetchingAll: false,
    errorAll: null,
    links,
    totalItems: parseInt(headers['x-total-count'], 10),
    tasks: loadMoreDataWhenScrolled(state.tasks, tasks, links),
  })
}
// successful api update
export const updateSuccess = (state, action) => {
  const { task } = action
  return state.merge({
    updating: false,
    errorUpdating: null,
    task,
  })
}
// successful api search
export const searchSuccess = (state, action) => {
  const { tasks } = action
  return state.merge({
    searching: false,
    errorSearching: null,
    tasks,
  })
}
// successful api delete
export const deleteSuccess = (state) => {
  return state.merge({
    deleting: false,
    errorDeleting: null,
    task: null,
  })
}

// Something went wrong fetching a single entity.
export const failure = (state, action) => {
  const { error } = action
  return state.merge({
    fetchingOne: false,
    errorOne: error,
    task: null,
  })
}
// Something went wrong fetching all entities.
export const allFailure = (state, action) => {
  const { error } = action
  return state.merge({
    fetchingAll: false,
    errorAll: error,
    tasks: [],
  })
}
// Something went wrong updating.
export const updateFailure = (state, action) => {
  const { error } = action
  return state.merge({
    updating: false,
    errorUpdating: error,
    task: state.task,
  })
}
// Something went wrong deleting.
export const deleteFailure = (state, action) => {
  const { error } = action
  return state.merge({
    deleting: false,
    errorDeleting: error,
    task: state.task,
  })
}
// Something went wrong searching the entities.
export const searchFailure = (state, action) => {
  const { error } = action
  return state.merge({
    searching: false,
    errorSearching: error,
    tasks: [],
  })
}

export const reset = (state) => INITIAL_STATE

/* ------------- Hookup Reducers To Types ------------- */

export const reducer = createReducer(INITIAL_STATE, {
  [Types.TASK_REQUEST]: request,
  [Types.TASK_ALL_REQUEST]: allRequest,
  [Types.TASK_UPDATE_REQUEST]: updateRequest,
  [Types.TASK_SEARCH_REQUEST]: searchRequest,
  [Types.TASK_DELETE_REQUEST]: deleteRequest,

  [Types.TASK_SUCCESS]: success,
  [Types.TASK_ALL_SUCCESS]: allSuccess,
  [Types.TASK_UPDATE_SUCCESS]: updateSuccess,
  [Types.TASK_SEARCH_SUCCESS]: searchSuccess,
  [Types.TASK_DELETE_SUCCESS]: deleteSuccess,

  [Types.TASK_FAILURE]: failure,
  [Types.TASK_ALL_FAILURE]: allFailure,
  [Types.TASK_UPDATE_FAILURE]: updateFailure,
  [Types.TASK_SEARCH_FAILURE]: searchFailure,
  [Types.TASK_DELETE_FAILURE]: deleteFailure,
  [Types.TASK_RESET]: reset,
})

任务.sagas.js

import { call, put } from 'redux-saga/effects'
import { callApi } from '../../../shared/sagas/call-api.saga'
import TaskActions from './tasks.reducer'

export function* getTask(api, action) {
  const { taskId } = action
  // make the call to the api
  const apiCall = call(api.getTask, taskId)
  const response = yield call(callApi, apiCall)

  // success?
  if (response.ok) {
    response.data = mapDateFields(response.data)
    yield put(TaskActions.taskSuccess(response.data))
  } else {
    yield put(TaskActions.taskFailure(response.data))
  }
}

export function* getTasks(api, action) {
  const { options } = action
  // make the call to the api
  const apiCall = call(api.getTasks, options)
  const response = yield call(callApi, apiCall)

  // success?
  if (response.ok) {
    yield put(TaskActions.taskAllSuccess(response.data, response.headers))
  } else {
    yield put(TaskActions.taskAllFailure(response.data))
  }
}

export function* updateTask(api, action) {
  const { task } = action
  // make the call to the api
  const idIsNotNull = !!task.id
  const apiCall = call(idIsNotNull ? api.updateTask : api.createTask, task)
  const response = yield call(callApi, apiCall)

  // success?
  if (response.ok) {
    response.data = mapDateFields(response.data)
    yield put(TaskActions.taskUpdateSuccess(response.data))
  } else {
    yield put(TaskActions.taskUpdateFailure(response.data))
  }
}

export function* searchTasks(api, action) {
  const { query } = action
  // make the call to the api
  const apiCall = call(api.searchTasks, query)
  const response = yield call(callApi, apiCall)

  // success?
  if (response.ok) {
    yield put(TaskActions.taskSearchSuccess(response.data))
  } else {
    yield put(TaskActions.taskSearchFailure(response.data))
  }
}
export function* deleteTask(api, action) {
  const { taskId } = action
  // make the call to the api
  const apiCall = call(api.deleteTask, taskId)
  const response = yield call(callApi, apiCall)

  // success?
  if (response.ok) {
    yield put(TaskActions.taskDeleteSuccess())
  } else {
    yield put(TaskActions.taskDeleteFailure(response.data))
  }
}
function mapDateFields(data) {
  if (data.deadline) {
    data.deadline = new Date(data.deadline)
  }
  return data
}

我可能会提供太多代码,但我不知道需要什么才能获得足够的信息。我需要将 id 和 object 发送到后端。

标签: spring-bootreact-nativeapirestjhipster

解决方案


推荐阅读