spring-boot - 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 发送到后端。
解决方案
推荐阅读
- python - 如何将 django 事务应用于每个 celery 任务?
- java - Spring Hibernate中实体字段的本地化
- modal-dialog - 自定义动态对话框 PRIMNG
- node.js - 我无法在其范围之外获得回调函数的结果
- android - Dagger2 + ViewModel + 存储库
- javascript - Javascript 上的 indexOf 无法在 Array 上按预期工作
- javascript - 我可以将 map 函数与 2 个数组一起使用吗?
- python - python - 如何在python中生成具有一致单词定位的wordcloud?
- postgresql - 如何解决:AND 的参数必须是布尔类型,而不是数字类型
- javascript - 从另一个活动执行功能