Merge remote-tracking branch 'origin/devlop-phyz' into devlop-phyz
| | |
| | | <artifactId>spring-boot-starter</artifactId> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.alibaba</groupId> |
| | | <artifactId>druid-spring-boot-starter</artifactId> |
| | | <version>1.2.21</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.projectlombok</groupId> |
| | | <artifactId>lombok</artifactId> |
| | | <version>${lombok.version}</version> |
| | |
| | | |
| | | export const ABORT_SIGNAL = false; |
| | | |
| | | export const DEFAULT_PAGE_SIZE = 25; |
| | | export const DEFAULT_PAGE_SIZE = 20; |
| | | |
| | | export const DEFAULT_START_PAGE = 1; |
| | | |
| | |
| | | // 在 MyMenu 组件的 generateMenu 函数中,确保 MenuItemLink 也左对齐 |
| | | const generateMenu = (permissions) => { |
| | | return permissions.map((node) => { |
| | | if (node.children) { |
| | | const selected = isSelected(node.component) || hasSelectedChild(node); |
| | | return ( |
| | | <SubMenu |
| | | key={node.id} |
| | | handleToggle={() => handleToggle(node.route)} |
| | | isOpen={state[node.route]} |
| | | name={node.name} |
| | | dense={dense} |
| | | icon={getIcon(node.icon)} |
| | | isSelected={selected} |
| | | > |
| | | {generateMenu(node.children)} |
| | | </SubMenu> |
| | | ); |
| | | } else { |
| | | if (node.component) { |
| | | const selected = isSelected(node.component); |
| | | // 在 generateMenu 函数中的 MenuItemLink 部分 |
| | | return ( |
| | | <MenuItemLink |
| | | key={node.id} |
| | | to={node.component} |
| | | state={{ _scrollToTop: true }} |
| | | primaryText={translate(node.name)} |
| | | leftIcon={getIcon(node.icon)} |
| | | dense={dense} |
| | | sx={{ |
| | | backgroundColor: selected ? 'rgba(25, 118, 210, 0.08) !important' : 'transparent', |
| | | color: selected ? '#1976d2 !important' : 'text.secondary', |
| | | '&:hover': { |
| | | backgroundColor: selected ? 'rgba(25, 118, 210, 0.12) !important' : 'rgba(0, 0, 0, 0.04)', |
| | | }, |
| | | borderLeft: 'none', |
| | | borderRadius: '4px', |
| | | margin: '2px 8px', |
| | | width: 'calc(100% - 16px)', |
| | | transition: 'all 0.2s ease-in-out', |
| | | |
| | | // 缩小整体间距 |
| | | padding: '6px 8px', // 减少内边距 |
| | | minHeight: '36px', // 稍微减小高度 |
| | | |
| | | '& .RaMenuItemLink-icon': { |
| | | color: selected ? '#1976d2 !important' : 'text.secondary', |
| | | minWidth: '32px !important', // 缩小图标区域宽度 |
| | | marginRight: '4px', // 缩小图标和文字间距 |
| | | display: 'flex', |
| | | alignItems: 'center', |
| | | justifyContent: 'center', // 图标居中显示 |
| | | }, |
| | | |
| | | fontWeight: selected ? 600 : 400, |
| | | |
| | | // 确保文字内容左对齐 |
| | | '& .MuiListItemText-root': { |
| | | margin: 0, |
| | | '& .MuiTypography-root': { |
| | | textAlign: 'left', |
| | | justifyContent: 'flex-start', |
| | | fontSize: '0.875rem', // 稍微减小字体大小 |
| | | lineHeight: '1.3', |
| | | } |
| | | }, |
| | | }} |
| | | /> |
| | | ); |
| | | } |
| | | if (node.children) { |
| | | const selected = isSelected(node.component) || hasSelectedChild(node); |
| | | return ( |
| | | <SubMenu |
| | | key={node.id} |
| | | handleToggle={() => handleToggle(node.route)} |
| | | isOpen={state[node.route]} |
| | | name={node.name} |
| | | dense={dense} |
| | | icon={getIcon(node.icon)} |
| | | isSelected={selected} |
| | | > |
| | | {generateMenu(node.children)} |
| | | </SubMenu> |
| | | ); |
| | | } else { |
| | | if (node.component) { |
| | | const selected = isSelected(node.component); |
| | | // 在 generateMenu 函数中的 MenuItemLink 部分 |
| | | return ( |
| | | <MenuItemLink |
| | | key={node.id} |
| | | to={node.component} |
| | | state={{ _scrollToTop: true }} |
| | | primaryText={translate(node.name)} |
| | | leftIcon={getIcon(node.icon)} |
| | | dense={dense} |
| | | sx={{ |
| | | backgroundColor: selected ? 'rgba(25, 118, 210, 0.08) !important' : 'transparent', |
| | | color: selected ? '#1976d2 !important' : 'text.secondary', |
| | | '&:hover': { |
| | | backgroundColor: selected ? 'rgba(25, 118, 210, 0.12) !important' : 'rgba(0, 0, 0, 0.04)', |
| | | }, |
| | | borderLeft: 'none', |
| | | borderRadius: '4px', |
| | | margin: '2px 8px', |
| | | width: 'calc(100% - 16px)', |
| | | transition: 'all 0.2s ease-in-out', |
| | | |
| | | // 缩小整体间距 |
| | | padding: '6px 8px', // 减少内边距 |
| | | minHeight: '36px', // 稍微减小高度 |
| | | |
| | | '& .RaMenuItemLink-icon': { |
| | | color: selected ? '#1976d2 !important' : 'text.secondary', |
| | | minWidth: '32px !important', // 缩小图标区域宽度 |
| | | marginRight: '4px', // 缩小图标和文字间距 |
| | | display: 'flex', |
| | | alignItems: 'center', |
| | | justifyContent: 'center', // 图标居中显示 |
| | | }, |
| | | |
| | | fontWeight: selected ? 600 : 400, |
| | | |
| | | // 确保文字内容左对齐 |
| | | '& .MuiListItemText-root': { |
| | | margin: 0, |
| | | '& .MuiTypography-root': { |
| | | textAlign: 'left', |
| | | justifyContent: 'flex-start', |
| | | fontSize: '0.875rem', // 稍微减小字体大小 |
| | | lineHeight: '1.3', |
| | | } |
| | | }, |
| | | }} |
| | | /> |
| | | ); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | |
| | | <span>{getTabLabel(tab)}</span> |
| | | {tab.closable && ( |
| | | <Tooltip title="关闭"> |
| | | <IconButton |
| | | size="small" |
| | | <Box |
| | | component="span" |
| | | onClick={(e) => handleCloseTab(e, tab.path)} |
| | | sx={{ |
| | | display: 'inline-flex', |
| | | alignItems: 'center', |
| | | justifyContent: 'center', |
| | | p: 0.25, |
| | | ml: 0.5, |
| | | borderRadius: '50%', |
| | | cursor: 'pointer', |
| | | '&:hover': { |
| | | backgroundColor: 'rgba(0, 0, 0, 0.1)', |
| | | }, |
| | | }} |
| | | > |
| | | <CloseIcon sx={{ fontSize: 14 }} /> |
| | | </IconButton> |
| | | </Box> |
| | | </Tooltip> |
| | | )} |
| | | </Box> |
| | |
| | | <Box sx={{ |
| | | position: 'fixed', |
| | | top: 48, |
| | | left: sidebarWidth, |
| | | // left: 0, |
| | | left: sidebarWidth + 5, |
| | | right: 0, |
| | | zIndex: 1100, |
| | | transition: (theme) => |
| | |
| | | onChange={(e) => handleChange(+e.target.value, 'channel')} |
| | | size="small" |
| | | type="number" |
| | | validate={[required()]} |
| | | // validate={[required()]} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={4}> |
| | |
| | | onChange={(e) => handleChange(+e.target.value, 'startChannel')} |
| | | size="small" |
| | | type="number" |
| | | validate={[required()]} |
| | | // validate={[required()]} |
| | | /> |
| | | </Grid> |
| | | </Grid> |
| | |
| | | open={!!drawerVal} |
| | | anchor="right" |
| | | onClose={handleClose} |
| | | sx={{ zIndex: 100 }} |
| | | sx={{ |
| | | zIndex: 100, |
| | | '& .MuiDrawer-paper': { |
| | | top: '86px', // AppBar(50px) + TabsBar(36px) |
| | | } |
| | | }} |
| | | > |
| | | {!!drawerVal && ( |
| | | <Box pt={5} width={{ xs: '100vW', sm: width }} height={'calc(100vh - 200px);'} mt={{ xs: 2, sm: 1 }}> |
| | | <Box pt={2} width={{ xs: '100vW', sm: width }} mt={{ xs: 2, sm: 1 }}> |
| | | <Stack direction="row" p={2}> |
| | | <Typography variant="h6" flex="1"> |
| | | {title} |
| New file |
| | |
| | | import React, { useState, useRef, useEffect, useMemo } from "react"; |
| | | import { |
| | | CreateBase, |
| | | useTranslate, |
| | | TextInput, |
| | | NumberInput, |
| | | BooleanInput, |
| | | DateInput, |
| | | SaveButton, |
| | | SelectInput, |
| | | ReferenceInput, |
| | | ReferenceArrayInput, |
| | | AutocompleteInput, |
| | | Toolbar, |
| | | required, |
| | | useDataProvider, |
| | | useNotify, |
| | | Form, |
| | | useCreateController, |
| | | } from 'react-admin'; |
| | | import { |
| | | Dialog, |
| | | DialogActions, |
| | | DialogContent, |
| | | DialogTitle, |
| | | Stack, |
| | | Grid, |
| | | Box, |
| | | } from '@mui/material'; |
| | | import DialogCloseButton from "../components/DialogCloseButton"; |
| | | import StatusSelectInput from "../components/StatusSelectInput"; |
| | | import MemoInput from "../components/MemoInput"; |
| | | |
| | | const MatnrRoleMenuCreate = (props) => { |
| | | const { open, setOpen } = props; |
| | | |
| | | const translate = useTranslate(); |
| | | const notify = useNotify(); |
| | | |
| | | const handleClose = (event, reason) => { |
| | | if (reason !== "backdropClick") { |
| | | setOpen(false); |
| | | } |
| | | }; |
| | | |
| | | const handleSuccess = async (data) => { |
| | | setOpen(false); |
| | | notify('common.response.success'); |
| | | }; |
| | | |
| | | const handleError = async (error) => { |
| | | notify(error.message || 'common.response.fail', { type: 'error', messageArgs: { _: error.message } }); |
| | | }; |
| | | |
| | | return ( |
| | | <> |
| | | <CreateBase |
| | | record={{}} |
| | | transform={(data) => { |
| | | return data; |
| | | }} |
| | | mutationOptions={{ onSuccess: handleSuccess, onError: handleError }} |
| | | > |
| | | <Dialog |
| | | open={open} |
| | | onClose={handleClose} |
| | | aria-labelledby="form-dialog-title" |
| | | fullWidth |
| | | disableRestoreFocus |
| | | maxWidth="md" // 'xs' | 'sm' | 'md' | 'lg' | 'xl' |
| | | > |
| | | <Form> |
| | | <DialogTitle id="form-dialog-title" sx={{ |
| | | position: 'sticky', |
| | | top: 0, |
| | | backgroundColor: 'background.paper', |
| | | zIndex: 1000 |
| | | }} |
| | | > |
| | | {translate('create.title')} |
| | | <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}> |
| | | <DialogCloseButton onClose={handleClose} /> |
| | | </Box> |
| | | </DialogTitle> |
| | | <DialogContent sx={{ mt: 2 }}> |
| | | <Grid container rowSpacing={2} columnSpacing={2}> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <NumberInput |
| | | label="table.field.matnrRoleMenu.roleId" |
| | | source="roleId" |
| | | autoFocus |
| | | validate={required()} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <NumberInput |
| | | label="table.field.matnrRoleMenu.menuId" |
| | | source="menuId" |
| | | validate={required()} |
| | | /> |
| | | </Grid> |
| | | |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <StatusSelectInput /> |
| | | </Grid> |
| | | <Grid item xs={12} display="flex" gap={1}> |
| | | <Stack direction="column" spacing={1} width={'100%'}> |
| | | <MemoInput /> |
| | | </Stack> |
| | | </Grid> |
| | | </Grid> |
| | | </DialogContent> |
| | | <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}> |
| | | <Toolbar sx={{ width: '100%', justifyContent: 'space-between' }} > |
| | | <SaveButton /> |
| | | </Toolbar> |
| | | </DialogActions> |
| | | </Form> |
| | | </Dialog> |
| | | </CreateBase> |
| | | </> |
| | | ) |
| | | } |
| | | |
| | | export default MatnrRoleMenuCreate; |
| New file |
| | |
| | | import React, { useState, useRef, useEffect, useMemo } from "react"; |
| | | import { |
| | | Edit, |
| | | SimpleForm, |
| | | FormDataConsumer, |
| | | useTranslate, |
| | | TextInput, |
| | | NumberInput, |
| | | BooleanInput, |
| | | DateInput, |
| | | SelectInput, |
| | | ReferenceInput, |
| | | ReferenceArrayInput, |
| | | AutocompleteInput, |
| | | SaveButton, |
| | | Toolbar, |
| | | Labeled, |
| | | NumberField, |
| | | required, |
| | | useRecordContext, |
| | | DeleteButton, |
| | | } from 'react-admin'; |
| | | import { useWatch, useFormContext } from "react-hook-form"; |
| | | import { Stack, Grid, Box, Typography } from '@mui/material'; |
| | | import * as Common from '@/utils/common'; |
| | | import { EDIT_MODE, REFERENCE_INPUT_PAGESIZE } from '@/config/setting'; |
| | | import EditBaseAside from "../components/EditBaseAside"; |
| | | import CustomerTopToolBar from "../components/EditTopToolBar"; |
| | | import MemoInput from "../components/MemoInput"; |
| | | import StatusSelectInput from "../components/StatusSelectInput"; |
| | | |
| | | const FormToolbar = () => { |
| | | const { getValues } = useFormContext(); |
| | | |
| | | return ( |
| | | <Toolbar sx={{ justifyContent: 'space-between' }}> |
| | | <SaveButton /> |
| | | <DeleteButton mutationMode="optimistic" /> |
| | | </Toolbar> |
| | | ) |
| | | } |
| | | |
| | | const MatnrRoleMenuEdit = () => { |
| | | const translate = useTranslate(); |
| | | |
| | | return ( |
| | | <Edit |
| | | redirect="list" |
| | | mutationMode={EDIT_MODE} |
| | | actions={<CustomerTopToolBar />} |
| | | aside={<EditBaseAside />} |
| | | > |
| | | <SimpleForm |
| | | shouldUnregister |
| | | warnWhenUnsavedChanges |
| | | toolbar={<FormToolbar />} |
| | | mode="onTouched" |
| | | defaultValues={{}} |
| | | // validate={(values) => { }} |
| | | > |
| | | <Grid container width={{ xs: '100%', xl: '80%' }} rowSpacing={3} columnSpacing={3}> |
| | | <Grid item xs={12} md={8}> |
| | | <Typography variant="h6" gutterBottom> |
| | | {translate('common.edit.title.main')} |
| | | </Typography> |
| | | <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | label="table.field.matnrRoleMenu.roleId" |
| | | source="roleId" |
| | | autoFocus |
| | | validate={required()} |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | label="table.field.matnrRoleMenu.menuId" |
| | | source="menuId" |
| | | validate={required()} |
| | | /> |
| | | </Stack> |
| | | |
| | | </Grid> |
| | | <Grid item xs={12} md={4}> |
| | | <Typography variant="h6" gutterBottom> |
| | | {translate('common.edit.title.common')} |
| | | </Typography> |
| | | <StatusSelectInput /> |
| | | <Box mt="2em" /> |
| | | <MemoInput /> |
| | | </Grid> |
| | | </Grid> |
| | | </SimpleForm> |
| | | </Edit > |
| | | ) |
| | | } |
| | | |
| | | export default MatnrRoleMenuEdit; |
| New file |
| | |
| | | import React, { useState, useRef, useEffect, useMemo, useCallback } from "react"; |
| | | import { useNavigate } from 'react-router-dom'; |
| | | import { |
| | | List, |
| | | DatagridConfigurable, |
| | | SearchInput, |
| | | TopToolbar, |
| | | SelectColumnsButton, |
| | | EditButton, |
| | | FilterButton, |
| | | CreateButton, |
| | | ExportButton, |
| | | BulkDeleteButton, |
| | | WrapperField, |
| | | useRecordContext, |
| | | useTranslate, |
| | | useNotify, |
| | | useListContext, |
| | | FunctionField, |
| | | TextField, |
| | | NumberField, |
| | | DateField, |
| | | BooleanField, |
| | | ReferenceField, |
| | | TextInput, |
| | | DateTimeInput, |
| | | DateInput, |
| | | SelectInput, |
| | | NumberInput, |
| | | ReferenceInput, |
| | | ReferenceArrayInput, |
| | | AutocompleteInput, |
| | | DeleteButton, |
| | | } from 'react-admin'; |
| | | import { Box, Typography, Card, Stack } from '@mui/material'; |
| | | import { styled } from '@mui/material/styles'; |
| | | import MatnrRoleMenuCreate from "./MatnrRoleMenuCreate"; |
| | | import MatnrRoleMenuPanel from "./MatnrRoleMenuPanel"; |
| | | import EmptyData from "../components/EmptyData"; |
| | | import MyCreateButton from "../components/MyCreateButton"; |
| | | import MyExportButton from '../components/MyExportButton'; |
| | | import PageDrawer from "../components/PageDrawer"; |
| | | import MyField from "../components/MyField"; |
| | | import { PAGE_DRAWER_WIDTH, OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting'; |
| | | import * as Common from '@/utils/common'; |
| | | |
| | | const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({ |
| | | '& .css-1vooibu-MuiSvgIcon-root': { |
| | | height: '.9em' |
| | | }, |
| | | '& .RaDatagrid-row': { |
| | | cursor: 'auto' |
| | | }, |
| | | '& .column-name': { |
| | | }, |
| | | '& .opt': { |
| | | width: 200 |
| | | }, |
| | | })); |
| | | |
| | | const filters = [ |
| | | <SearchInput source="condition" alwaysOn />, |
| | | <DateInput label='common.time.after' source="timeStart" alwaysOn />, |
| | | <DateInput label='common.time.before' source="timeEnd" alwaysOn />, |
| | | |
| | | <NumberInput source="roleId" label="table.field.matnrRoleMenu.roleId" />, |
| | | <NumberInput source="menuId" label="table.field.matnrRoleMenu.menuId" />, |
| | | |
| | | <TextInput label="common.field.memo" source="memo" />, |
| | | <SelectInput |
| | | label="common.field.status" |
| | | source="status" |
| | | choices={[ |
| | | { id: '1', name: 'common.enums.statusTrue' }, |
| | | { id: '0', name: 'common.enums.statusFalse' }, |
| | | ]} |
| | | resettable |
| | | />, |
| | | ] |
| | | |
| | | const MatnrRoleMenuList = () => { |
| | | const translate = useTranslate(); |
| | | |
| | | const [createDialog, setCreateDialog] = useState(false); |
| | | const [drawerVal, setDrawerVal] = useState(false); |
| | | |
| | | return ( |
| | | <Box display="flex"> |
| | | <List |
| | | sx={{ |
| | | flexGrow: 1, |
| | | transition: (theme) => |
| | | theme.transitions.create(['all'], { |
| | | duration: theme.transitions.duration.enteringScreen, |
| | | }), |
| | | marginRight: !!drawerVal ? `${PAGE_DRAWER_WIDTH}px` : 0, |
| | | }} |
| | | title={"menu.matnrRoleMenu"} |
| | | empty={<EmptyData onClick={() => { setCreateDialog(true) }} />} |
| | | filters={filters} |
| | | sort={{ field: "create_time", order: "desc" }} |
| | | actions={( |
| | | <TopToolbar> |
| | | <FilterButton /> |
| | | <MyCreateButton onClick={() => { setCreateDialog(true) }} /> |
| | | <SelectColumnsButton preferenceKey='matnrRoleMenu' /> |
| | | <MyExportButton /> |
| | | </TopToolbar> |
| | | )} |
| | | perPage={DEFAULT_PAGE_SIZE} |
| | | > |
| | | <StyledDatagrid |
| | | preferenceKey='matnrRoleMenu' |
| | | bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />} |
| | | rowClick={(id, resource, record) => false} |
| | | expand={() => <MatnrRoleMenuPanel />} |
| | | expandSingle={true} |
| | | omit={['id', 'createTime', 'createBy', 'memo']} |
| | | > |
| | | <NumberField source="id" /> |
| | | <NumberField source="roleId" label="table.field.matnrRoleMenu.roleId" /> |
| | | <NumberField source="menuId" label="table.field.matnrRoleMenu.menuId" /> |
| | | |
| | | <ReferenceField source="updateBy" label="common.field.updateBy" reference="user" link={false} sortable={false}> |
| | | <TextField source="nickname" /> |
| | | </ReferenceField> |
| | | <DateField source="updateTime" label="common.field.updateTime" showTime /> |
| | | <ReferenceField source="createBy" label="common.field.createBy" reference="user" link={false} sortable={false}> |
| | | <TextField source="nickname" /> |
| | | </ReferenceField> |
| | | <DateField source="createTime" label="common.field.createTime" showTime /> |
| | | <BooleanField source="statusBool" label="common.field.status" sortable={false} /> |
| | | <TextField source="memo" label="common.field.memo" sortable={false} /> |
| | | <WrapperField cellClassName="opt" label="common.field.opt"> |
| | | <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} /> |
| | | <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} /> |
| | | </WrapperField> |
| | | </StyledDatagrid> |
| | | </List> |
| | | <MatnrRoleMenuCreate |
| | | open={createDialog} |
| | | setOpen={setCreateDialog} |
| | | /> |
| | | <PageDrawer |
| | | title='MatnrRoleMenu Detail' |
| | | drawerVal={drawerVal} |
| | | setDrawerVal={setDrawerVal} |
| | | > |
| | | </PageDrawer> |
| | | </Box> |
| | | ) |
| | | } |
| | | |
| | | export default MatnrRoleMenuList; |
| New file |
| | |
| | | import React, { useState, useRef, useEffect, useMemo } from "react"; |
| | | import { Box, Card, CardContent, Grid, Typography, Tooltip } from '@mui/material'; |
| | | import { |
| | | useTranslate, |
| | | useRecordContext, |
| | | } from 'react-admin'; |
| | | import PanelTypography from "../components/PanelTypography"; |
| | | import * as Common from '@/utils/common' |
| | | |
| | | const MatnrRoleMenuPanel = () => { |
| | | const record = useRecordContext(); |
| | | if (!record) return null; |
| | | const translate = useTranslate(); |
| | | return ( |
| | | <> |
| | | <Card sx={{ width: { xs: 300, sm: 500, md: 600, lg: 800 }, margin: 'auto' }}> |
| | | <CardContent> |
| | | <Grid container spacing={2}> |
| | | <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'space-between' }}> |
| | | <Typography variant="h6" gutterBottom align="left" sx={{ |
| | | maxWidth: { xs: '100px', sm: '180px', md: '260px', lg: '360px' }, |
| | | whiteSpace: 'nowrap', |
| | | overflow: 'hidden', |
| | | textOverflow: 'ellipsis', |
| | | }}> |
| | | {Common.camelToPascalWithSpaces(translate('table.field.matnrRoleMenu.id'))}: {record.id} |
| | | </Typography> |
| | | {/* inherit, primary, secondary, textPrimary, textSecondary, error */} |
| | | <Typography variant="h6" gutterBottom align="right" > |
| | | ID: {record.id} |
| | | </Typography> |
| | | </Grid> |
| | | </Grid> |
| | | <Grid container spacing={2}> |
| | | <Grid item xs={12} container alignContent="flex-end"> |
| | | <Typography variant="caption" color="textSecondary" sx={{ wordWrap: 'break-word', wordBreak: 'break-all' }}> |
| | | {Common.camelToPascalWithSpaces(translate('common.field.memo'))}:{record.memo} |
| | | </Typography> |
| | | </Grid> |
| | | </Grid> |
| | | <Box height={20}> </Box> |
| | | <Grid container spacing={2}> |
| | | <Grid item xs={6}> |
| | | <PanelTypography |
| | | title="table.field.matnrRoleMenu.roleId" |
| | | property={record.roleId} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6}> |
| | | <PanelTypography |
| | | title="table.field.matnrRoleMenu.menuId" |
| | | property={record.menuId} |
| | | /> |
| | | </Grid> |
| | | |
| | | </Grid> |
| | | </CardContent> |
| | | </Card > |
| | | </> |
| | | ); |
| | | }; |
| | | |
| | | export default MatnrRoleMenuPanel; |
| New file |
| | |
| | | import React, { useState, useRef, useEffect, useMemo } from "react"; |
| | | import { |
| | | ListGuesser, |
| | | EditGuesser, |
| | | ShowGuesser, |
| | | } from "react-admin"; |
| | | |
| | | import MatnrRoleMenuList from "./MatnrRoleMenuList"; |
| | | import MatnrRoleMenuEdit from "./MatnrRoleMenuEdit"; |
| | | |
| | | export default { |
| | | list: MatnrRoleMenuList, |
| | | edit: MatnrRoleMenuEdit, |
| | | show: ShowGuesser, |
| | | recordRepresentation: (record) => { |
| | | return `${record.id}` |
| | | } |
| | | }; |
| | |
| | | }, |
| | | }, |
| | | }} |
| | | pageSizeOptions={[15, 25, 50, 100]} |
| | | pageSizeOptions={[10, 20, 50, 100]} |
| | | editMode="row" |
| | | checkboxSelection |
| | | onRowSelectionModelChange={handleSelectionChange} |
| | |
| | | }, |
| | | }, |
| | | }} |
| | | pageSizeOptions={[15, 25, 50]} |
| | | pageSizeOptions={[10, 20, 50]} |
| | | /> |
| | | </Box > |
| | | |
| New file |
| | |
| | | import React, { useState, useRef, useEffect, useMemo, useCallback } from "react"; |
| | | import { |
| | | useTranslate, |
| | | useNotify, |
| | | TextInput |
| | | } from 'react-admin'; |
| | | import { Box, Button, Card, Stack, CardContent, Skeleton, TextField } from '@mui/material'; |
| | | import { SimpleTreeView, TreeItem, RichTreeView, useTreeViewApiRef } from '@mui/x-tree-view'; |
| | | import SaveIcon from '@mui/icons-material/Save'; |
| | | import request from '@/utils/request' |
| | | |
| | | const DEFAULT_EXPAND_ALL = true; |
| | | |
| | | const AssignPermissionsMatnr = (props) => { |
| | | const { role, originMenuIds, setDrawerVal, closeCallback, authType } = props; |
| | | const translate = useTranslate(); |
| | | const notify = useNotify(); |
| | | |
| | | const [loading, setLoading] = useState(false); |
| | | const [treeData, setTreeData] = useState([]); |
| | | const [selectedItems, setSelectedItems] = useState([]); |
| | | const [expandedItems, setExpandedItems] = useState([]); |
| | | const [parmas, setParmas] = useState({ condition: '', authType: authType }); |
| | | const toggledItemRef = useRef({}); |
| | | const apiRef = useTreeViewApiRef(); |
| | | |
| | | useEffect(() => { |
| | | reload() |
| | | }, [role, originMenuIds]) |
| | | |
| | | const reload = () => { |
| | | setSelectedItems(originMenuIds.map(item => item + "")); |
| | | |
| | | const transformTree = (treeData) => { |
| | | return treeData.map(data => { |
| | | return { |
| | | id: data.id + '', |
| | | label: data.type === 0 ? translate(data.name || data.code) : data.name || data.code, |
| | | type: data.type, |
| | | children: (data.children && data.children.length > 0 ? transformTree(data.children) : null) |
| | | } |
| | | }) |
| | | } |
| | | const http = async () => { |
| | | const res = await request.post('/menuMatnrGroup/tree', parmas); |
| | | if (res?.data?.code === 200) { |
| | | const transformData = transformTree(res.data.data); |
| | | setTreeData(transformData); |
| | | if (DEFAULT_EXPAND_ALL) { |
| | | setExpandedItems(getAllItemsWithChildrenItemIds(transformData)); |
| | | } |
| | | } else { |
| | | notify(res.data.msg, { type: 'error' }); |
| | | } |
| | | setLoading(false); |
| | | } |
| | | setLoading(true); |
| | | setTimeout(() => { |
| | | http(); |
| | | }, 200); |
| | | } |
| | | |
| | | |
| | | const getAllItemItemIds = () => { |
| | | const ids = []; |
| | | const registerItemId = (item) => { |
| | | ids.push(item.id); |
| | | item.children?.forEach(registerItemId); |
| | | }; |
| | | treeData.forEach(registerItemId); |
| | | return ids; |
| | | }; |
| | | |
| | | const getAllItemsWithChildrenItemIds = (treeDataHandle) => { |
| | | const itemIds = []; |
| | | const registerItemId = (item) => { |
| | | if (item.children?.length) { |
| | | itemIds.push(item.id); |
| | | item.children.forEach(registerItemId); |
| | | } |
| | | }; |
| | | if (treeDataHandle) { |
| | | treeDataHandle.forEach(registerItemId); |
| | | } else { |
| | | treeData.forEach(registerItemId); |
| | | } |
| | | return itemIds; |
| | | }; |
| | | |
| | | const handleSelectedItemsChange = (event, newSelectedItems) => { |
| | | const itemsToSelect = []; |
| | | const itemsToUnSelect = {}; |
| | | Object.entries(toggledItemRef.current).forEach(([itemId, isSelected]) => { |
| | | const item = apiRef.current.getItem(itemId); |
| | | if (isSelected) { |
| | | itemsToSelect.push(...getItemDescendantsIds(item)); |
| | | |
| | | const parentIds = getParentIds(treeData, itemId); |
| | | itemsToSelect.push(...parentIds); |
| | | } else { |
| | | // 取消子节点 |
| | | const treeNode = checkoutTreeNode(treeData, itemId); |
| | | if (treeNode?.children && treeNode?.children.length > 0) { |
| | | const allChildren = getItemDescendantsIds(treeNode); |
| | | const childrenSet = new Set(allChildren); |
| | | newSelectedItems = newSelectedItems.filter(item => !childrenSet.has(item)); |
| | | } |
| | | |
| | | // 取消父节点 |
| | | const removeParentIfAllSiblingsDeselected = (itemId, newSelectedItems, treeData) => { |
| | | let updatedSelectedItems = [...newSelectedItems]; |
| | | let currentId = itemId; |
| | | while (true) { |
| | | const parentId = getParentId(treeData, currentId); |
| | | if (!parentId) break; |
| | | const siblings = getChildrenIds(treeData, parentId); |
| | | const allSiblingsDeselected = siblings.every(siblingId => !updatedSelectedItems.includes(siblingId)); |
| | | if (allSiblingsDeselected) { |
| | | updatedSelectedItems = updatedSelectedItems.filter(id => id !== parentId); |
| | | currentId = parentId; |
| | | } else { |
| | | break; |
| | | } |
| | | } |
| | | return updatedSelectedItems; |
| | | }; |
| | | newSelectedItems = removeParentIfAllSiblingsDeselected(itemId, newSelectedItems, treeData); |
| | | } |
| | | }); |
| | | |
| | | const newSelectedItemsWithChildren = Array.from( |
| | | new Set( |
| | | [...newSelectedItems, ...itemsToSelect].filter( |
| | | (itemId) => !itemsToUnSelect[itemId], |
| | | ), |
| | | ), |
| | | ); |
| | | |
| | | setSelectedItems(newSelectedItemsWithChildren); |
| | | |
| | | toggledItemRef.current = {}; |
| | | }; |
| | | |
| | | const handleSave = (event) => { |
| | | request.post('/roleMatnr/scope/update', { |
| | | id: role.id, |
| | | menuIds: { |
| | | checked: selectedItems, |
| | | halfChecked: [] |
| | | } |
| | | }).then(res => { |
| | | if (res?.data.code === 200) { |
| | | setDrawerVal(null); |
| | | if (closeCallback) { |
| | | closeCallback(); |
| | | } |
| | | notify(res.data.msg, { type: 'info', messageArgs: { _: res.data.msg } }) |
| | | } else { |
| | | notify(res.data.msg, { type: 'error' }) |
| | | } |
| | | |
| | | }) |
| | | } |
| | | |
| | | const search = (e) => { |
| | | const value = e.target.value; |
| | | setParmas({ |
| | | ...parmas, |
| | | condition: value |
| | | }) |
| | | reload() |
| | | |
| | | } |
| | | |
| | | return ( |
| | | <> |
| | | <Card sx={{ |
| | | ml: 1, |
| | | mr: 1, |
| | | height: 'calc(100vh - 140px)', |
| | | overflowY: 'auto' |
| | | }}> |
| | | <CardContent sx={{ |
| | | overflow: 'auto', |
| | | display: 'flex', |
| | | flexDirection: 'column', |
| | | justifyContent: 'space-between' |
| | | }}> |
| | | <Box> |
| | | <Box mb={1} sx={{ |
| | | display: 'flex', |
| | | justifyContent: 'space-between' |
| | | }}> |
| | | <Button onClick={() => { |
| | | setSelectedItems((oldSelected) => |
| | | oldSelected.length === 0 ? getAllItemItemIds() : [], |
| | | ); |
| | | }}> |
| | | {selectedItems.length === 0 ? translate('ra.action.select_all') : translate('ra.action.unselect')} |
| | | </Button> |
| | | <Button onClick={() => { |
| | | setExpandedItems((oldExpanded) => |
| | | oldExpanded.length === 0 ? getAllItemsWithChildrenItemIds() : [], |
| | | ); |
| | | }}> |
| | | {expandedItems.length === 0 ? translate('common.action.expandAll') : translate('common.action.collapseAll')} |
| | | </Button> |
| | | </Box> |
| | | <Box sx={{ |
| | | display: 'flex', |
| | | justifyContent: 'space-between', |
| | | alignItems: 'center' |
| | | }}> |
| | | <TextField sx={{ width: '200px' }} label="搜索菜单" variant="outlined" value={parmas.condition} onChange={(e) => search(e)} /> |
| | | <Button startIcon={<SaveIcon />} size="small" variant="contained" onClick={handleSave} sx={{ height: '40px' }}> |
| | | {translate('ra.action.save')} |
| | | </Button> |
| | | </Box> |
| | | |
| | | <Box sx={{ |
| | | minWidth: 290, |
| | | overflow: 'auto', |
| | | marginTop: '10px', |
| | | padding: 1, |
| | | borderBottom: '1px solid background.paper', |
| | | borderRadius: '4px', |
| | | boxShadow: '0 1px 2px rgba(0, 0, 0, 0.2)', |
| | | backgroundColor: 'background.paper', |
| | | }}> |
| | | {loading ? ( |
| | | <SkeletonBox /> |
| | | ) : ( |
| | | <RichTreeView |
| | | multiSelect |
| | | checkboxSelection |
| | | apiRef={apiRef} |
| | | items={treeData} |
| | | selectedItems={selectedItems} |
| | | onSelectedItemsChange={handleSelectedItemsChange} |
| | | onItemSelectionToggle={(event, itemId, isSelected) => { |
| | | toggledItemRef.current[itemId] = isSelected; |
| | | }} |
| | | |
| | | expandedItems={expandedItems} |
| | | onExpandedItemsChange={(event, itemIds) => { |
| | | setExpandedItems(itemIds); |
| | | }} |
| | | /> |
| | | )} |
| | | |
| | | </Box> |
| | | </Box> |
| | | |
| | | </CardContent> |
| | | </Card> |
| | | </> |
| | | ) |
| | | } |
| | | |
| | | const checkoutTreeNode = (treeData, targetId) => { |
| | | let result = null; |
| | | const checkout = (node) => { |
| | | if (node.id === targetId) { |
| | | result = node; |
| | | return true; |
| | | } else { |
| | | if (node.children) { |
| | | for (const child of node.children) { |
| | | if (checkout(child)) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | }; |
| | | treeData.forEach(item => { |
| | | if (checkout(item)) { |
| | | return; |
| | | } |
| | | }); |
| | | return result; |
| | | }; |
| | | |
| | | const getItemDescendantsIds = (item) => { |
| | | const ids = []; |
| | | item.children?.forEach((child) => { |
| | | ids.push(child.id); |
| | | ids.push(...getItemDescendantsIds(child)); |
| | | }); |
| | | return ids; |
| | | } |
| | | |
| | | const getParentIds = (tree, targetId) => { |
| | | let parentIds = []; |
| | | const searchTree = (node, path = []) => { |
| | | if (node.id === targetId) { |
| | | parentIds = [...path]; |
| | | return true; |
| | | } |
| | | if (node.children) { |
| | | for (const child of node.children) { |
| | | if (searchTree(child, [...path, node.id])) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | }; |
| | | tree.forEach(item => { |
| | | searchTree(item); |
| | | }) |
| | | return parentIds; |
| | | }; |
| | | |
| | | const getParentId = (tree, targetId) => { |
| | | let parentId = null; |
| | | const searchTree = (node) => { |
| | | if (node.children) { |
| | | for (const child of node.children) { |
| | | if (child.id === targetId) { |
| | | parentId = node.id; |
| | | return true; |
| | | } |
| | | if (searchTree(child)) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | }; |
| | | tree.forEach(item => { |
| | | if (searchTree(item)) { |
| | | return parentId; |
| | | } |
| | | }); |
| | | return parentId; |
| | | }; |
| | | |
| | | const getChildrenIds = (tree, targetId) => { |
| | | let childrenIds = []; |
| | | const searchTree = (node) => { |
| | | if (node.id === targetId && node.children) { |
| | | childrenIds = node.children.map(child => child.id); |
| | | } else if (node.children) { |
| | | for (const child of node.children) { |
| | | searchTree(child); |
| | | } |
| | | } |
| | | }; |
| | | tree.forEach(item => { |
| | | searchTree(item); |
| | | }); |
| | | return childrenIds; |
| | | }; |
| | | |
| | | const SkeletonBox = () => { |
| | | return ( |
| | | <Stack spacing={1}> |
| | | <Skeleton variant="rounded" width={200} height={20} /> |
| | | <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} /> |
| | | <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} /> |
| | | <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} /> |
| | | <Skeleton variant="rounded" width={200} height={20} /> |
| | | <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} /> |
| | | <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} /> |
| | | <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} /> |
| | | <Skeleton variant="rounded" width={200} height={20} /> |
| | | <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} /> |
| | | <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} /> |
| | | <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} /> |
| | | <Skeleton variant="rounded" width={200} height={20} /> |
| | | <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} /> |
| | | <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} /> |
| | | <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} /> |
| | | </Stack> |
| | | ) |
| | | } |
| | | |
| | | export default AssignPermissionsMatnr; |
| | |
| | | useNotify, |
| | | Button, |
| | | } from 'react-admin'; |
| | | import { Box, Card, Stack } from '@mui/material'; |
| | | import { Box, Card, Stack, Menu, MenuItem, ListItemIcon, ListItemText } from '@mui/material'; |
| | | import SecurityIcon from '@mui/icons-material/Security'; |
| | | import { styled } from '@mui/material/styles'; |
| | | import RoleCreate from "./RoleCreate"; |
| | | import RolePanel from "./RolePanel"; |
| | |
| | | import * as Common from '@/utils/common'; |
| | | import AssignPermissions from "./AssignPermissions"; |
| | | import AssignPermissionsPda from "./AssignPermissions_pda"; |
| | | import AssignPermissionsMatnr from "./AssignPermissions_matnr"; |
| | | import request from '@/utils/request'; |
| | | import AssignmentIndIcon from '@mui/icons-material/AssignmentInd'; |
| | | import AdUnitsIcon from '@mui/icons-material/AdUnits'; |
| | | import ArticleIcon from '@mui/icons-material/Article'; |
| | | import { margin } from "@mui/system"; |
| | | |
| | | const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({ |
| | | '& .css-1vooibu-MuiSvgIcon-root': { |
| | |
| | | const [createDialog, setCreateDialog] = useState(false); |
| | | const [drawerVal, setDrawerVal] = useState(false); |
| | | const [drawerValPda, setDrawerValPda] = useState(false); |
| | | const [drawerValMatnr, setDrawerValMatnr] = useState(false); |
| | | |
| | | const [menuIds, setMenuIds] = useState([]); |
| | | |
| | | const [authType, setAuthType] = useState(0) |
| | | |
| | | const assign = (record) => { |
| | | setDrawerValPda(false); |
| | | request('/role/scope/list', { |
| | | method: 'GET', |
| | | params: { |
| | |
| | | } |
| | | |
| | | const assignPda = (record) => { |
| | | setDrawerVal(false); |
| | | request('/rolePda/scope/list', { |
| | | method: 'GET', |
| | | params: { |
| | |
| | | }); |
| | | } |
| | | |
| | | const assignMatnr = (record) => { |
| | | setDrawerVal(false); |
| | | request('/roleMatnr/scope/list', { |
| | | method: 'GET', |
| | | params: { |
| | | roleId: record.id |
| | | } |
| | | }).then((res) => { |
| | | if (res?.data?.code === 200) { |
| | | const { data: menuIds } = res.data; |
| | | setMenuIds(menuIds || []); |
| | | setDrawerValMatnr(!!drawerValMatnr && drawerValMatnr === record ? null : record); |
| | | } else { |
| | | notify(res.data.msg, { type: 'error' }); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | return ( |
| | | <Box display="flex"> |
| | | <List |
| | |
| | | theme.transitions.create(['all'], { |
| | | duration: theme.transitions.duration.enteringScreen, |
| | | }), |
| | | marginRight: (!!drawerVal || !!drawerValPda) ? `${PAGE_DRAWER_WIDTH}px` : 0, |
| | | marginRight: (!!drawerVal || !!drawerValPda || !!drawerValMatnr) ? `${PAGE_DRAWER_WIDTH}px` : 0, |
| | | }} |
| | | title={"menu.role"} |
| | | empty={<EmptyData onClick={() => { setCreateDialog(true) }} />} |
| | |
| | | <BooleanField source="statusBool" label="common.field.status" sortable={false} /> |
| | | <TextField source="memo" label="common.field.memo" sortable={false} /> |
| | | <WrapperField cellClassName="opt" label="common.field.opt"> |
| | | <ScopeButton sx={{ padding: '1px', fontSize: '.75rem' }} assign={assign} auType={0} setAuthType={setAuthType} label="网页权限 " /> |
| | | <PdaScopeButton sx={{ padding: '1px', fontSize: '.75rem' }} assignPda={assignPda} auType={1} setAuthType={setAuthType} label="PDA权限 " /> |
| | | <ScopeButton sx={{ padding: '1px', fontSize: '.75rem' }} assign={assign} auType={2} setAuthType={setAuthType} label="仓库权限 " /> |
| | | <PermissionMenuButton |
| | | assign={assign} |
| | | assignPda={assignPda} |
| | | assignMatnr={assignMatnr} |
| | | setAuthType={setAuthType} |
| | | /> |
| | | <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} /> |
| | | <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} /> |
| | | </WrapperField> |
| | |
| | | authType={authType} |
| | | /> |
| | | </PageDrawer> |
| | | <PageDrawer |
| | | drawerVal={drawerValMatnr} |
| | | setDrawerVal={setDrawerValMatnr} |
| | | title={!!drawerValMatnr ? `Scope by ${drawerValMatnr.code || drawerValMatnr.name}` : 'Role Detail'} |
| | | closeCallback={() => { |
| | | setMenuIds([]); |
| | | }} |
| | | > |
| | | <AssignPermissionsMatnr |
| | | role={drawerValMatnr} |
| | | originMenuIds={menuIds} |
| | | setDrawerVal={setDrawerValMatnr} |
| | | closeCallback={() => { |
| | | setMenuIds([]); |
| | | }} |
| | | authType={authType} |
| | | /> |
| | | </PageDrawer> |
| | | </Box> |
| | | ) |
| | | } |
| | | |
| | | const ScopeButton = (props) => { |
| | | const PermissionMenuButton = ({ assign, assignPda, assignMatnr, setAuthType }) => { |
| | | const record = useRecordContext(); |
| | | const { assign, auType, setAuthType, label, ...rest } = props; |
| | | return ( |
| | | <Button |
| | | variant="text" |
| | | color="primary" |
| | | startIcon={<AssignmentIndIcon />} |
| | | label={label} |
| | | onClick={(event) => { |
| | | setAuthType(auType); |
| | | event.stopPropagation(); |
| | | assign(record); |
| | | }} |
| | | {...rest} |
| | | /> |
| | | ) |
| | | } |
| | | const [anchorEl, setAnchorEl] = useState(null); |
| | | const open = Boolean(anchorEl); |
| | | |
| | | const PdaScopeButton = (props) => { |
| | | const record = useRecordContext(); |
| | | const { assignPda, auType, setAuthType, label, ...rest } = props; |
| | | const handleClick = (event) => { |
| | | event.stopPropagation(); |
| | | setAnchorEl(event.currentTarget); |
| | | }; |
| | | |
| | | const handleClose = (event) => { |
| | | if (event) event.stopPropagation(); |
| | | setAnchorEl(null); |
| | | }; |
| | | |
| | | const handleWebPermission = (event) => { |
| | | event.stopPropagation(); |
| | | setAuthType(0); |
| | | assign(record); |
| | | handleClose(); |
| | | }; |
| | | |
| | | const handlePdaPermission = (event) => { |
| | | event.stopPropagation(); |
| | | setAuthType(1); |
| | | assignPda(record); |
| | | handleClose(); |
| | | }; |
| | | |
| | | const handleMatnrPermission = (event) => { |
| | | event.stopPropagation(); |
| | | setAuthType(2); |
| | | assignMatnr(record); |
| | | handleClose(); |
| | | }; |
| | | |
| | | return ( |
| | | <Button |
| | | variant="text" |
| | | color="primary" |
| | | startIcon={<AdUnitsIcon />} |
| | | label={label} |
| | | onClick={(event) => { |
| | | setAuthType(auType); |
| | | event.stopPropagation(); |
| | | assignPda(record); |
| | | }} |
| | | {...rest} |
| | | /> |
| | | ) |
| | | <> |
| | | <Button |
| | | variant="text" |
| | | color="primary" |
| | | startIcon={<SecurityIcon />} |
| | | label="权限" |
| | | onClick={handleClick} |
| | | sx={{ marginLeft: '2px', padding: '1px', fontSize: '.75rem' }} |
| | | /> |
| | | <Menu |
| | | anchorEl={anchorEl} |
| | | open={open} |
| | | onClose={handleClose} |
| | | onClick={(e) => e.stopPropagation()} |
| | | anchorOrigin={{ |
| | | vertical: 'bottom', |
| | | horizontal: 'left', |
| | | }} |
| | | transformOrigin={{ |
| | | vertical: 'top', |
| | | horizontal: 'left', |
| | | }} |
| | | > |
| | | <MenuItem onClick={handleWebPermission}> |
| | | <ListItemIcon> |
| | | <AssignmentIndIcon fontSize="small" /> |
| | | </ListItemIcon> |
| | | <ListItemText>网页权限</ListItemText> |
| | | </MenuItem> |
| | | <MenuItem onClick={handlePdaPermission}> |
| | | <ListItemIcon> |
| | | <AdUnitsIcon fontSize="small" /> |
| | | </ListItemIcon> |
| | | <ListItemText>PDA权限</ListItemText> |
| | | </MenuItem> |
| | | <MenuItem onClick={handleMatnrPermission}> |
| | | <ListItemIcon> |
| | | <ArticleIcon fontSize="small" /> |
| | | </ListItemIcon> |
| | | <ListItemText>物料权限</ListItemText> |
| | | </MenuItem> |
| | | </Menu> |
| | | </> |
| | | ); |
| | | } |
| | | |
| | | export default RoleList; |
| | |
| | | }, |
| | | }, |
| | | }} |
| | | pageSizeOptions={[10, 25, 50, 100]} |
| | | pageSizeOptions={[10, 20, 50, 100]} |
| | | editMode="row" |
| | | onRowSelectionModelChange={handleSelectionChange} |
| | | selectionModel={selectedRows} |
| | |
| | | <artifactId>rsf-common</artifactId> |
| | | <version>1.0.0</version> |
| | | </dependency> |
| | | <!-- JWT依赖 --> |
| | | <dependency> |
| | | <groupId>io.jsonwebtoken</groupId> |
| | | <artifactId>jjwt-api</artifactId> |
| | | <version>0.11.5</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>io.jsonwebtoken</groupId> |
| | | <artifactId>jjwt-impl</artifactId> |
| | | <version>0.11.5</version> |
| | | <scope>runtime</scope> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>io.jsonwebtoken</groupId> |
| | | <artifactId>jjwt-jackson</artifactId> |
| | | <version>0.11.5</version> |
| | | <scope>runtime</scope> |
| | | </dependency> |
| | | </dependencies> |
| | | <build> |
| | | <finalName>rsf-open-api</finalName> |
| New file |
| | |
| | | package com.vincent.rsf.openApi.annotation; |
| | | |
| | | |
| | | import java.lang.annotation.*; |
| | | |
| | | /** |
| | | * 操作日志记录注解 |
| | | * |
| | | * @author vincent |
| | | * @since 2020-03-21 17:03:08 |
| | | */ |
| | | @Documented |
| | | @Target({ElementType.METHOD}) |
| | | @Retention(RetentionPolicy.RUNTIME) |
| | | public @interface OperationLog { |
| | | |
| | | /** |
| | | * 操作功能 |
| | | */ |
| | | String value() default ""; |
| | | |
| | | /** |
| | | * 操作模块 |
| | | */ |
| | | String module() default ""; |
| | | |
| | | /** |
| | | * 备注 |
| | | */ |
| | | String comments() default ""; |
| | | |
| | | /** |
| | | * 是否记录请求参数 |
| | | */ |
| | | boolean param() default true; |
| | | |
| | | /** |
| | | * 是否记录返回结果 |
| | | */ |
| | | boolean result() default true; |
| | | |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.aspect; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.vincent.rsf.common.utils.Utils; |
| | | import com.vincent.rsf.framework.common.Cools; |
| | | import com.vincent.rsf.openApi.annotation.OperationLog; |
| | | import com.vincent.rsf.openApi.entity.app.ApiForeignLog; |
| | | import com.vincent.rsf.openApi.entity.dto.CommonResponse; |
| | | import com.vincent.rsf.openApi.service.ApiForeignLogService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.aspectj.lang.JoinPoint; |
| | | import org.aspectj.lang.ProceedingJoinPoint; |
| | | import org.aspectj.lang.annotation.Around; |
| | | import org.aspectj.lang.annotation.Aspect; |
| | | import org.aspectj.lang.annotation.Pointcut; |
| | | import org.aspectj.lang.reflect.MethodSignature; |
| | | import org.springframework.core.annotation.Order; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.web.context.request.RequestContextHolder; |
| | | import org.springframework.web.context.request.ServletRequestAttributes; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.lang.reflect.Method; |
| | | import java.rmi.NoSuchObjectException; |
| | | import java.util.*; |
| | | |
| | | /** |
| | | * Created by Administrator on 2019-07-09. |
| | | */ |
| | | @Component |
| | | @Aspect |
| | | @Slf4j |
| | | @Order(2) |
| | | public class LogAspect { |
| | | |
| | | // 参数、返回结果、错误信息等最大保存长度 |
| | | private static final int MAX_LENGTH = 1000; |
| | | // 用于记录请求耗时 |
| | | private final ThreadLocal<Long> startTime = new ThreadLocal<>(); |
| | | |
| | | @Resource |
| | | private ApiForeignLogService apiForeignLogService; |
| | | |
| | | public LogAspect() { |
| | | } |
| | | |
| | | /** |
| | | * 切入点 |
| | | * 匹配controller包及其所有子包下的所有类的所有方法 |
| | | */ |
| | | @Pointcut("execution(* com.vincent.rsf.openApi.controller..*.*(..))") |
| | | public void controllerPc() { |
| | | } |
| | | |
| | | /** |
| | | * 环绕通知 |
| | | * @param pjp ProceedingJoinPoint |
| | | * @return 方法结果 |
| | | */ |
| | | @Around("controllerPc()") |
| | | public Object around(ProceedingJoinPoint pjp) { |
| | | String methodName = pjp.getSignature().getName(); |
| | | try { |
| | | ServletRequestAttributes attributes = Optional.ofNullable((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) |
| | | .orElseThrow(() -> new NoSuchObjectException("前置通知中获取的 ServletRequestAttributes 对象为空")); |
| | | HttpServletRequest request = attributes.getRequest(); |
| | | |
| | | // if("getToken".equals(methodName) || "getToken".equals(methodName)) |
| | | // return pjp.proceed(); |
| | | |
| | | // 前置通知 |
| | | log.info("------【前置通知】------"); |
| | | |
| | | // 记录请求内容 |
| | | log.info("浏览器输入的网址:{}", request.getRequestURL().toString()); |
| | | log.info("HTTP_METHOD:{}", request.getMethod()); |
| | | log.info("IP:{}", request.getRemoteAddr()); |
| | | log.info("执行的业务方法名:{}", pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName()); |
| | | log.info("业务方法获得的参数:{}", Arrays.toString(pjp.getArgs())); |
| | | |
| | | Object result = pjp.proceed(); |
| | | |
| | | // 后置通知 |
| | | log.info("------【后置通知】------"); |
| | | log.info("{}方法的返回值:{}", pjp.getSignature().getName(), result); |
| | | |
| | | saveLog1(pjp, result, null); |
| | | return result; |
| | | } catch (Throwable e) { |
| | | // 异常通知 |
| | | log.error("------【异常通知】------"); |
| | | log.error("{}方法异常,参数:{},异常:", pjp.getSignature().getName(), Arrays.toString(pjp.getArgs()), e); |
| | | |
| | | saveLog1(pjp, null, (Exception) e); |
| | | return CommonResponse.error("服务器处理数据异常"); |
| | | } finally { |
| | | // 最终通知 |
| | | if(!methodName.isEmpty() && !"getRegistered".equals(methodName)){ |
| | | log.info("------【最终通知】------"); |
| | | log.info("{}方法执行结束", pjp.getSignature().getName()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void saveLog1(JoinPoint joinPoint, Object result, Exception e) { |
| | | |
| | | ApiForeignLog record = new ApiForeignLog(); |
| | | Long endTime = startTime.get(); |
| | | record.setCreateTime(new Date()); |
| | | // 记录操作耗时 |
| | | if (endTime != null) { |
| | | record.setSpendTime((int) (System.currentTimeMillis() - endTime)); |
| | | } |
| | | record.setTimestamp(String.valueOf(endTime)); |
| | | // // 记录当前登录用户id、租户id |
| | | // User user = getLoginUser(); |
| | | // if (user != null) { |
| | | // record.setUserId(user.getId()); |
| | | // record.setTenantId(user.getTenantId()); |
| | | // } |
| | | // 记录请求地址、请求方式、ip |
| | | ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); |
| | | HttpServletRequest request = (attributes == null ? null : attributes.getRequest()); |
| | | if (request != null) { |
| | | record.setUrl(request.getRequestURI()); |
| | | // record.setClientIp(IpTools.gainRealIp(request)); |
| | | } |
| | | // 记录异常信息 |
| | | if (e != null) { |
| | | record.setResult(0); |
| | | record.setErr(Utils.sub(e.toString(), MAX_LENGTH)); |
| | | } else { |
| | | record.setResult(1); |
| | | } |
| | | // // 记录操作功能 |
| | | // record.setNamespace(desc); |
| | | // // 记录备注 |
| | | // if (!Cools.isEmpty(ol.comments())) { |
| | | // record.setMemo(ol.comments()); |
| | | // } |
| | | // 记录请求参数 |
| | | record.setRequest(Utils.sub(Arrays.toString(joinPoint.getArgs()), MAX_LENGTH)); |
| | | record.setResponse(Utils.sub(JSON.toJSONString(result), MAX_LENGTH)); |
| | | |
| | | apiForeignLogService.saveAsync(record); |
| | | } |
| | | |
| | | /** |
| | | * 保存操作记录 |
| | | */ |
| | | private void saveLog(JoinPoint joinPoint, Object result, Exception e) { |
| | | // 记录模块名、操作功能、请求方法、请求参数、返回结果 |
| | | MethodSignature signature = (MethodSignature) joinPoint.getSignature(); |
| | | Method method = signature.getMethod(); |
| | | if (null == method) { |
| | | return; |
| | | } |
| | | OperationLog ol = method.getAnnotation(OperationLog.class); |
| | | if (null == ol) { |
| | | return; |
| | | } |
| | | String desc = getDescription(method, ol); |
| | | if (Cools.isEmpty(desc)) { |
| | | return; |
| | | } |
| | | |
| | | ApiForeignLog record = new ApiForeignLog(); |
| | | Long endTime = startTime.get(); |
| | | record.setCreateTime(new Date()); |
| | | // 记录操作耗时 |
| | | if (endTime != null) { |
| | | record.setSpendTime((int) (System.currentTimeMillis() - endTime)); |
| | | } |
| | | record.setTimestamp(String.valueOf(endTime)); |
| | | // // 记录当前登录用户id、租户id |
| | | // User user = getLoginUser(); |
| | | // if (user != null) { |
| | | // record.setUserId(user.getId()); |
| | | // record.setTenantId(user.getTenantId()); |
| | | // } |
| | | // 记录请求地址、请求方式、ip |
| | | ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); |
| | | HttpServletRequest request = (attributes == null ? null : attributes.getRequest()); |
| | | if (request != null) { |
| | | record.setUrl(request.getRequestURI()); |
| | | // record.setClientIp(IpTools.gainRealIp(request)); |
| | | } |
| | | // 记录异常信息 |
| | | if (e != null) { |
| | | record.setResult(0); |
| | | record.setErr(Utils.sub(e.toString(), MAX_LENGTH)); |
| | | } else { |
| | | record.setResult(1); |
| | | } |
| | | // 记录操作功能 |
| | | record.setNamespace(desc); |
| | | // 记录备注 |
| | | if (!Cools.isEmpty(ol.comments())) { |
| | | record.setMemo(ol.comments()); |
| | | } |
| | | // 记录请求参数 |
| | | if (ol.param() && request != null) { |
| | | record.setRequest(Utils.sub(getParams(joinPoint, request), MAX_LENGTH)); |
| | | } |
| | | // 记录请求结果 |
| | | if (ol.result() && result != null) { |
| | | record.setResponse(Utils.sub(JSON.toJSONString(result), MAX_LENGTH)); |
| | | } |
| | | apiForeignLogService.saveAsync(record); |
| | | } |
| | | |
| | | /** |
| | | * 获取操作功能 |
| | | * |
| | | * @param method Method |
| | | * @param ol OperationLog |
| | | * @return String |
| | | */ |
| | | private String getDescription(Method method, OperationLog ol) { |
| | | if (!Cools.isEmpty(ol.value())) { |
| | | return ol.value(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 获取请求参数 |
| | | * |
| | | * @param joinPoint JoinPoint |
| | | * @param request HttpServletRequest |
| | | * @return String |
| | | */ |
| | | private String getParams(JoinPoint joinPoint, HttpServletRequest request) { |
| | | String params; |
| | | |
| | | Map<String, String> paramsMap = new HashMap<>(); |
| | | |
| | | Map<String, String[]> map = Collections.unmodifiableMap(request.getParameterMap()); |
| | | for (Map.Entry<String, String[]> entry : map.entrySet()) { |
| | | paramsMap.put(entry.getKey(), Utils.join(entry.getValue(), ",")); |
| | | } |
| | | |
| | | if (paramsMap.keySet().size() > 0) { |
| | | params = JSON.toJSONString(paramsMap); |
| | | } else { |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (Object arg : joinPoint.getArgs()) { |
| | | if (null == arg |
| | | || arg instanceof MultipartFile |
| | | || arg instanceof HttpServletRequest |
| | | || arg instanceof HttpServletResponse) { |
| | | continue; |
| | | } |
| | | sb.append(JSON.toJSONString(arg)).append(" "); |
| | | } |
| | | params = sb.toString(); |
| | | } |
| | | return params; |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.config; |
| | | |
| | | import com.vincent.rsf.openApi.security.filter.AppIdAuthenticationFilter; |
| | | import org.springframework.boot.web.servlet.FilterRegistrationBean; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | | import javax.annotation.Resource; |
| | | |
| | | /** |
| | | * API安全配置类 |
| | | * 用于注册API认证过滤器 |
| | | */ |
| | | @Configuration |
| | | public class ApiSecurityConfig { |
| | | |
| | | @Resource |
| | | private AppIdAuthenticationFilter appIdAuthenticationFilter; |
| | | |
| | | /** |
| | | * 注册API认证过滤器(支持AppId/AppSecret和Token认证) |
| | | * |
| | | * @return 过滤器注册Bean |
| | | */ |
| | | @Bean |
| | | public FilterRegistrationBean<AppIdAuthenticationFilter> apiAuthenticationFilter() { |
| | | FilterRegistrationBean<AppIdAuthenticationFilter> registrationBean = new FilterRegistrationBean<>(); |
| | | |
| | | registrationBean.setFilter(appIdAuthenticationFilter); |
| | | registrationBean.addUrlPatterns("/api/*", "/erp/*", "/mes/*", "/agv/*"); // 拦截API请求、ERP请求、MES请求、管理AGV任务请求 |
| | | registrationBean.setName("apiAuthenticationFilter"); |
| | | registrationBean.setOrder(1); // 设置过滤器优先级 |
| | | |
| | | return registrationBean; |
| | | } |
| | | } |
| | |
| | | |
| | | |
| | | import com.vincent.rsf.openApi.entity.constant.Constants; |
| | | import com.vincent.rsf.openApi.security.filter.AppIdAuthenticationFilter; |
| | | import com.vincent.rsf.openApi.utils.Http; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | import org.springframework.web.servlet.AsyncHandlerInterceptor; |
| | |
| | | @Configuration |
| | | public class WebMvcConfig implements WebMvcConfigurer { |
| | | |
| | | @Autowired |
| | | private AppIdAuthenticationFilter appIdAuthenticationFilter; |
| | | |
| | | @Override |
| | | public void addInterceptors(InterceptorRegistry registry) { |
| | | registry.addInterceptor(getAsyncHandlerInterceptor()) |
| | | .addPathPatterns("/**") |
| | | .excludePathPatterns("/swagger-resources/**", "/webjars/**","/erp/**", "/v2/**","/v3/**","/doc.html/**", "/swagger-ui.html/**"); |
| | | } |
| | | |
| | | |
| | | @Bean |
| | | public AsyncHandlerInterceptor getAsyncHandlerInterceptor() { |
| | | return new AsyncHandlerInterceptor(){ |
| | | @Override |
| | | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
| | | Http.cors(response); |
| | | return true; |
| | | } |
| | | }; |
| | | } |
| | | |
| | | @Override |
| | | public void addResourceHandlers(ResourceHandlerRegistry registry) { |
| | | registry.addResourceHandler("/**").addResourceLocations("classpath:/static/"); |
| | | registry.addResourceHandler("swagger-ui.html") |
| | | .addResourceLocations("classpath:/META-INF/resources/"); |
| | | registry.addResourceHandler("doc.html") |
| | | .addResourceLocations("classpath:/META-INF/resources/"); |
| | | registry.addResourceHandler("/webjars/**") |
| | | .addResourceLocations("classpath:/META-INF/resources/webjars/"); |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public void addCorsMappings(CorsRegistry registry) { |
| | |
| | | } |
| | | |
| | | |
| | | public static void cors(HttpServletResponse response){ |
| | | // 跨域设置 |
| | | response.setHeader("Access-Control-Max-Age", "3600"); |
| | | response.setHeader("Access-Control-Allow-Origin", "*"); |
| | | response.setHeader("Access-Control-Allow-Methods", "*"); |
| | | response.setHeader("Access-Control-Allow-Headers", "*"); |
| | | response.setHeader("Access-Control-Expose-Headers", Constants.TOKEN_HEADER_NAME); |
| | | @Bean |
| | | public AsyncHandlerInterceptor getAsyncHandlerInterceptor() { |
| | | return new AsyncHandlerInterceptor(){ |
| | | @Override |
| | | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
| | | Http.cors(response); |
| | | return true; |
| | | } |
| | | }; |
| | | } |
| | | |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.controller; |
| | | |
| | | import com.vincent.rsf.framework.common.Cools; |
| | | import com.vincent.rsf.openApi.entity.constant.Constants; |
| | | import com.vincent.rsf.openApi.entity.dto.CommonResponse; |
| | | import com.vincent.rsf.openApi.entity.AppAuthParam; |
| | | import com.vincent.rsf.openApi.security.service.AppAuthService; |
| | | import com.vincent.rsf.openApi.security.utils.TokenUtils; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | | |
| | | /** |
| | | * App认证控制器 |
| | | * |
| | | * 提供AppId和AppSecret的登录认证功能 |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-05 |
| | | */ |
| | | @RestController |
| | | //@RequestMapping("/auth") |
| | | @Api(tags = "应用认证管理") |
| | | @Slf4j |
| | | public class AuthController { |
| | | |
| | | // 开启模拟数据 |
| | | @Value("${foreign.api.data.simulated}") |
| | | public static String SIMULATED_DATA_ENABLE = "1"; |
| | | |
| | | @Resource |
| | | private AppAuthService appAuthService; |
| | | |
| | | |
| | | /** |
| | | * 获取App认证Token |
| | | * |
| | | * @param param 应用ID和应用密钥 |
| | | * @return 认证Token |
| | | */ |
| | | @ApiOperation("获取App认证Token") |
| | | @PostMapping("/getToken") |
| | | public CommonResponse getToken(@RequestBody AppAuthParam param) { |
| | | String appId = param.getAppId(); |
| | | String appSecret = param.getAppSecret(); |
| | | |
| | | if (Cools.isEmpty(appId, appSecret)) { |
| | | return CommonResponse.error("AppId和AppSecret不能为空"); |
| | | } |
| | | |
| | | boolean isValid = appAuthService.validateApp(appId, appSecret); |
| | | if (isValid) { |
| | | String token = Constants.TOKEN_PREFIX + TokenUtils.generateToken(appId, appSecret); //appAuthService.generateAppToken(appId, appSecret); |
| | | return CommonResponse.ok() |
| | | .setMsg("获取Token成功") |
| | | .setData(token); |
| | | } else { |
| | | return CommonResponse.error("AppId或AppSecret无效"); |
| | | } |
| | | } |
| | | |
| | | // /** |
| | | // * 验证Token的接口 |
| | | // * |
| | | // * @param token 要验证的Token |
| | | // * @return Token验证结果 |
| | | // */ |
| | | // @PostMapping("/validateToken") |
| | | // public Map<String, Object> validateToken(@RequestHeader(name = "authorization") String token) { |
| | | // log.info("验证Token: {}", token.substring(0, Math.min(10, token.length())) + "..."); |
| | | // |
| | | // boolean isValid = TokenUtils.validateToken(token); |
| | | // |
| | | // Map<String, Object> response = new HashMap<>(); |
| | | // response.put("code", "200"); |
| | | // response.put("message", isValid ? "Token有效" : "Token无效"); |
| | | // response.put("data", Map.of( |
| | | // "valid", isValid, |
| | | // "appId", isValid ? TokenUtils.getAppIdFromToken(token) : null, |
| | | // "userId", isValid ? TokenUtils.getUserIdFromToken(token) : null |
| | | // )); |
| | | // response.put("success", isValid); |
| | | // |
| | | // return response; |
| | | // } |
| | | |
| | | // /** |
| | | // * AppId和AppSecret登录认证 |
| | | // * |
| | | // * @param param 认证参数 |
| | | // * @return 认证结果 |
| | | // */ |
| | | // @ApiOperation("AppId和AppSecret登录认证") |
| | | // @PostMapping("/login") |
| | | // public CommonResponse login(@RequestBody AppAuthParam param) { |
| | | // String appId = param.getAppId(); |
| | | // String appSecret = param.getAppSecret(); |
| | | // |
| | | // if (Cools.isEmpty(appId, appSecret)) { |
| | | // return CommonResponse.error("AppId和AppSecret不能为空"); |
| | | // } |
| | | // |
| | | // boolean isValid = appAuthService.validateApp(appId, appSecret); |
| | | // if (isValid) { |
| | | // // 生成Token |
| | | // String token = appAuthService.generateAppToken(appId, appSecret); |
| | | // return CommonResponse.ok() |
| | | // .setMsg("登录成功") |
| | | // .setData(token); |
| | | // } else { |
| | | // return CommonResponse.error("AppId或AppSecret无效"); |
| | | // } |
| | | // } |
| | | // |
| | | // |
| | | // |
| | | // /** |
| | | // * 验证App认证 |
| | | // * |
| | | // * @param request HTTP请求 |
| | | // * @return 验证结果 |
| | | // */ |
| | | // @ApiOperation("验证App认证") |
| | | // @PostMapping("/validate") |
| | | // public CommonResponse validate(HttpServletRequest request) { |
| | | // String appId = request.getHeader(Constants.HEADER_APP_ID); |
| | | // String appSecret = request.getHeader(Constants.HEADER_APP_SECRET); |
| | | // |
| | | // if (Cools.isEmpty(appId, appSecret)) { |
| | | // return CommonResponse.error("缺少AppId或AppSecret"); |
| | | // } |
| | | // |
| | | // boolean isValid = appAuthService.validateApp(appId, appSecret); |
| | | // if (isValid) { |
| | | // return CommonResponse.ok() |
| | | // .setMsg("验证成功") |
| | | // .setData(appAuthService.getAppInfo(appId)); |
| | | // } else { |
| | | // return CommonResponse.error("验证失败"); |
| | | // } |
| | | // } |
| | | // |
| | | // /** |
| | | // * 获取当前认证的App信息 |
| | | // * |
| | | // * @param request HTTP请求 |
| | | // * @return App信息 |
| | | // */ |
| | | // @ApiOperation("获取当前认证的App信息") |
| | | // @GetMapping("/info") |
| | | // public CommonResponse getAppInfo(HttpServletRequest request) { |
| | | // String appId = (String) request.getAttribute("APP_ID"); |
| | | // if (appId == null) { |
| | | // appId = request.getHeader(Constants.HEADER_APP_ID); |
| | | // } |
| | | // |
| | | // if (appId == null) { |
| | | // return CommonResponse.error("未找到AppId"); |
| | | // } |
| | | // |
| | | // var appInfo = appAuthService.getAppInfo(appId); |
| | | // if (appInfo != null) { |
| | | // return CommonResponse.ok() |
| | | // .setMsg("获取App信息成功") |
| | | // .setData(appInfo); |
| | | // } else { |
| | | // return CommonResponse.error("未找到App信息"); |
| | | // } |
| | | // } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.controller; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.openApi.entity.dto.CommonResponse; |
| | | import com.vincent.rsf.openApi.entity.phyz.Task; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | import static com.vincent.rsf.openApi.controller.AuthController.SIMULATED_DATA_ENABLE; |
| | | import static com.vincent.rsf.openApi.controller.phyz.ERPController.paramsFormat; |
| | | |
| | | @RestController |
| | | @Api("任务管理接口") |
| | | @Slf4j |
| | | public class TaskController { |
| | | |
| | | @ApiOperation("点对点创建AGV搬运任务") |
| | | @PostMapping("/agv/transTask/add") |
| | | public CommonResponse addAgvTask(@RequestBody Object objParams) { |
| | | if (Objects.isNull(objParams)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | // 返回模拟数据 |
| | | if (SIMULATED_DATA_ENABLE.equals("1")) { |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | JSONArray params = paramsFormat(objParams); |
| | | List<Task> tasks = JSON.parseArray(params.toJSONString(), Task.class); |
| | | // 数据处理,转发server |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | @ApiOperation("点对点取消AGV搬运任务") |
| | | @PostMapping("/agv/transTask/cancel") |
| | | public CommonResponse cancelAgvTask(@RequestBody Object objParams) { |
| | | if (Objects.isNull(objParams)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | // 返回模拟数据 |
| | | if (SIMULATED_DATA_ENABLE.equals("1")) { |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | JSONArray params = paramsFormat(objParams); |
| | | List<Task> tasks = JSON.parseArray(params.toJSONString(), Task.class); |
| | | // 数据处理,转发server |
| | | return CommonResponse.ok(); |
| | | } |
| | | } |
| | |
| | | import java.util.Objects; |
| | | |
| | | @RestController |
| | | @RequestMapping("/erp") |
| | | @RequestMapping("/erp1") |
| | | @Api("ERP接口对接") |
| | | public class WmsErpController { |
| | | |
| | |
| | | * @return |
| | | */ |
| | | @ApiOperation("单据修改") |
| | | @PostMapping("/order/upadte") |
| | | @PostMapping("/order/update") |
| | | public CommonResponse modifyOrderDtel(@RequestBody ErpOpParams params) { |
| | | if (Objects.isNull(params)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.openApi.entity.dto.CommonResponse; |
| | | import com.vincent.rsf.openApi.entity.dto.SyncLocsDto; |
| | | import com.vincent.rsf.openApi.entity.params.ExMsgCallbackParams; |
| | | import com.vincent.rsf.openApi.entity.params.RcsPubTaskParams; |
| | | import com.vincent.rsf.openApi.entity.params.SyncRcsLocsParam; |
| | |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | |
| | | */ |
| | | @ApiOperation("RCS库位信息同步") |
| | | @PostMapping("/sync/locs") |
| | | public R syncLocsToWms(@RequestBody SyncRcsLocsParam params) { |
| | | public CommonResponse syncLocsToWms(@RequestBody SyncRcsLocsParam params) { |
| | | if (Objects.isNull(params)) { |
| | | return R.error("参数不能为空!!"); |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | return R.ok().add(wmsRcsService.syncLocs(params)); |
| | | List<SyncLocsDto> result = wmsRcsService.syncLocs(params); |
| | | return CommonResponse.ok(result); |
| | | } |
| | | |
| | | |
| New file |
| | |
| | | package com.vincent.rsf.openApi.controller.example; |
| | | |
| | | import com.vincent.rsf.openApi.entity.constant.Constants; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * API认证示例控制器 |
| | | * 演示如何使用统一的认证机制(支持AppId/AppSecret和Token) |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/api/example/auth") |
| | | public class ApiAuthExampleController { |
| | | private static final Logger log = LoggerFactory.getLogger(ApiAuthExampleController.class); |
| | | |
| | | /** |
| | | * 获取受保护的数据 - 支持AppId/AppSecret或Token认证 |
| | | * |
| | | * @param request HTTP请求 |
| | | * @return 受保护的数据 |
| | | */ |
| | | @GetMapping("/protected-data") |
| | | public Map<String, Object> getProtectedData(HttpServletRequest request) { |
| | | // 从请求属性中获取认证信息(由AppIdAuthenticationFilter设置) |
| | | String appId = (String) request.getAttribute(Constants.REQUEST_ATTR_APP_ID); |
| | | String userId = (String) request.getAttribute(Constants.REQUEST_ATTR_USER_ID); |
| | | |
| | | log.info("访问受保护接口,AppId: {}, UserId: {}", appId, userId); |
| | | |
| | | Map<String, Object> response = new HashMap<>(); |
| | | response.put("code", "200"); |
| | | response.put("message", "访问成功"); |
| | | response.put("data", Map.of( |
| | | "appId", appId, |
| | | "userId", userId, |
| | | "protectedInfo", "这是受保护的数据", |
| | | "authType", userId != null ? "Token" : "AppId/AppSecret", |
| | | "timestamp", System.currentTimeMillis() |
| | | )); |
| | | response.put("success", true); |
| | | |
| | | return response; |
| | | } |
| | | |
| | | /** |
| | | * 获取认证信息 - 支持AppId/AppSecret或Token认证 |
| | | * |
| | | * @param request HTTP请求 |
| | | * @return 认证信息 |
| | | */ |
| | | @GetMapping("/auth-info") |
| | | public Map<String, Object> getAuthInfo(HttpServletRequest request) { |
| | | // 从请求属性中获取认证信息 |
| | | String appId = (String) request.getAttribute(Constants.REQUEST_ATTR_APP_ID); |
| | | String userId = (String) request.getAttribute(Constants.REQUEST_ATTR_USER_ID); |
| | | |
| | | log.info("获取认证信息,AppId: {}, UserId: {}", appId, userId); |
| | | |
| | | Map<String, Object> response = new HashMap<>(); |
| | | response.put("code", "200"); |
| | | response.put("message", "获取认证信息成功"); |
| | | response.put("data", Map.of( |
| | | "appId", appId, |
| | | "userId", userId, |
| | | "authType", userId != null ? "Token" : "AppId/AppSecret", |
| | | "authenticated", appId != null |
| | | )); |
| | | response.put("success", true); |
| | | |
| | | return response; |
| | | } |
| | | |
| | | /** |
| | | * 测试接口 - 不需要认证 |
| | | * |
| | | * @return 测试数据 |
| | | */ |
| | | @GetMapping("/public-test") |
| | | public Map<String, Object> getPublicTest() { |
| | | Map<String, Object> response = new HashMap<>(); |
| | | response.put("code", "200"); |
| | | response.put("message", "公开接口访问成功"); |
| | | response.put("data", Map.of( |
| | | "info", "这是一个不需要认证的公开接口", |
| | | "timestamp", System.currentTimeMillis() |
| | | )); |
| | | response.put("success", true); |
| | | |
| | | return response; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.controller.example; |
| | | |
| | | import com.vincent.rsf.openApi.entity.dto.CommonResponse; |
| | | import com.vincent.rsf.openApi.security.utils.AuthUtils; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | |
| | | /** |
| | | * App认证使用示例控制器 |
| | | * |
| | | * 演示如何在控制器中使用AppId认证 |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-05 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/example/auth") |
| | | @Api(tags = "App认证使用示例") |
| | | public class AppAuthExampleController { |
| | | |
| | | /** |
| | | * 需要App认证的接口示例 |
| | | * |
| | | * @param request HTTP请求 |
| | | * @return 响应结果 |
| | | */ |
| | | @ApiOperation("需要App认证的接口示例") |
| | | @GetMapping("/protected") |
| | | public CommonResponse protectedEndpoint(HttpServletRequest request) { |
| | | // 获取认证的AppId |
| | | String appId = AuthUtils.getAppId(request); |
| | | |
| | | // 检查是否已认证 |
| | | if (appId == null) { |
| | | return CommonResponse.error("未通过App认证"); |
| | | } |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("访问成功") |
| | | .setData("认证的AppId: " + appId); |
| | | } |
| | | |
| | | /** |
| | | * 获取当前认证的App信息 |
| | | * |
| | | * @param request HTTP请求 |
| | | * @return App信息 |
| | | */ |
| | | @ApiOperation("获取当前认证的App信息") |
| | | @GetMapping("/app-info") |
| | | public CommonResponse getAppInfo(HttpServletRequest request) { |
| | | String appId = AuthUtils.getAppId(request); |
| | | |
| | | if (appId == null) { |
| | | return CommonResponse.error("未通过App认证"); |
| | | } |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("获取App信息成功") |
| | | .setData("当前AppId: " + appId); |
| | | } |
| | | |
| | | /** |
| | | * 无需认证的公开接口 |
| | | * |
| | | * @return 响应结果 |
| | | */ |
| | | @ApiOperation("无需认证的公开接口") |
| | | @GetMapping("/public") |
| | | public CommonResponse publicEndpoint() { |
| | | return CommonResponse.ok() |
| | | .setMsg("公开接口访问成功") |
| | | .setData("任何人都可以访问此接口"); |
| | | } |
| | | |
| | | /** |
| | | * 检查认证状态 |
| | | * |
| | | * @param request HTTP请求 |
| | | * @return 认证状态 |
| | | */ |
| | | @ApiOperation("检查认证状态") |
| | | @PostMapping("/check-auth") |
| | | public CommonResponse checkAuth(HttpServletRequest request) { |
| | | boolean isAuthenticated = AuthUtils.isAuthenticated(request); |
| | | String appId = AuthUtils.getAppId(request); |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("认证检查完成") |
| | | .setData("isAuthenticated: " + isAuthenticated + ", appId: " + appId); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.controller.example; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.vincent.rsf.openApi.entity.dto.CommonResponse; |
| | | import com.vincent.rsf.openApi.utils.ParamsMapUtils; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | /** |
| | | * 接口字段映射使用示例 |
| | | * |
| | | * 展示如何在实际业务中使用 FuncMap 进行字段映射 |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | @Slf4j |
| | | @RestController |
| | | @RequestMapping("/example/mapping") |
| | | @Api(tags = "字段映射使用示例") |
| | | public class FieldMappingExampleController { |
| | | |
| | | /** |
| | | * 示例1:ERP订单接收 - 字段映射 |
| | | * |
| | | * 场景:接收ERP系统的订单数据,字段名与WMS不一致,需要进行映射 |
| | | * |
| | | * ERP字段 -> WMS字段: |
| | | * orderNumber -> code |
| | | * orderQty -> qty |
| | | * orderAmount -> anfme |
| | | */ |
| | | @ApiOperation("示例:ERP订单接收") |
| | | @PostMapping("/erp/order/receive") |
| | | public CommonResponse erpOrderReceive(@RequestBody JSONObject requestData) { |
| | | log.info("接收到ERP订单数据(映射前):{}", requestData); |
| | | |
| | | // 执行字段映射 |
| | | String appId = "ERP_SYSTEM"; |
| | | String funcId = "ORDER_SYNC"; |
| | | JSONObject mappedData = ParamsMapUtils.apiMaps(appId, funcId, requestData); |
| | | |
| | | log.info("字段映射后的数据:{}", mappedData); |
| | | |
| | | // 这里可以继续调用WMS内部的业务逻辑处理映射后的数据 |
| | | // orderService.createOrder(mappedData); |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("订单接收成功") |
| | | .setData(mappedData); |
| | | } |
| | | |
| | | /** |
| | | * 示例2:物料信息同步 - 字段映射 |
| | | * |
| | | * 场景:接收ERP系统的物料数据 |
| | | * |
| | | * ERP字段 -> WMS字段: |
| | | * materialCode -> matnr |
| | | * materialName -> maktx |
| | | * materialSpec -> spec |
| | | */ |
| | | @ApiOperation("示例:物料信息同步") |
| | | @PostMapping("/erp/material/sync") |
| | | public CommonResponse materialSync(@RequestBody JSONObject requestData) { |
| | | log.info("接收到物料数据(映射前):{}", requestData); |
| | | |
| | | // 执行字段映射 |
| | | String appId = "ERP_SYSTEM"; |
| | | String funcId = "MATNR_SYNC"; |
| | | JSONObject mappedData = ParamsMapUtils.apiMaps(appId, funcId, requestData); |
| | | |
| | | log.info("字段映射后的数据:{}", mappedData); |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("物料同步成功") |
| | | .setData(mappedData); |
| | | } |
| | | |
| | | /** |
| | | * 示例3:WCS任务创建 - 字段映射 |
| | | * |
| | | * 场景:接收WCS系统的任务创建请求 |
| | | * |
| | | * WCS字段 -> WMS字段: |
| | | * containerCode -> barcode |
| | | * stationCode -> sourceStaNo |
| | | * taskType -> ioType |
| | | */ |
| | | @ApiOperation("示例:WCS任务创建") |
| | | @PostMapping("/wcs/task/create") |
| | | public CommonResponse wcsTaskCreate(@RequestBody JSONObject requestData) { |
| | | log.info("接收到WCS任务数据(映射前):{}", requestData); |
| | | |
| | | // 执行字段映射 |
| | | String appId = "WCS_SYSTEM"; |
| | | String funcId = "TASK_CREATE"; |
| | | JSONObject mappedData = ParamsMapUtils.apiMaps(appId, funcId, requestData); |
| | | |
| | | log.info("字段映射后的数据:{}", mappedData); |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("任务创建成功") |
| | | .setData(mappedData); |
| | | } |
| | | |
| | | /** |
| | | * 示例4:批量数据映射 |
| | | * |
| | | * 场景:批量接收订单明细,需要对每条明细进行字段映射 |
| | | */ |
| | | @ApiOperation("示例:批量订单明细映射") |
| | | @PostMapping("/erp/order/batch") |
| | | public CommonResponse batchOrderMapping(@RequestBody JSONObject requestData) { |
| | | log.info("接收到批量订单数据"); |
| | | |
| | | String appId = "ERP_SYSTEM"; |
| | | String funcId = "ORDER_SYNC"; |
| | | |
| | | // 映射订单头 |
| | | JSONObject orderHeader = requestData.getJSONObject("header"); |
| | | JSONObject mappedHeader = ParamsMapUtils.apiMaps(appId, funcId, orderHeader); |
| | | |
| | | // 映射订单明细列表 |
| | | com.alibaba.fastjson.JSONArray items = requestData.getJSONArray("items"); |
| | | com.alibaba.fastjson.JSONArray mappedItems = new com.alibaba.fastjson.JSONArray(); |
| | | |
| | | if (items != null) { |
| | | for (int i = 0; i < items.size(); i++) { |
| | | JSONObject item = items.getJSONObject(i); |
| | | JSONObject mappedItem = ParamsMapUtils.apiMaps(appId, funcId, item); |
| | | mappedItems.add(mappedItem); |
| | | } |
| | | } |
| | | |
| | | // 组装结果 |
| | | JSONObject result = new JSONObject(); |
| | | result.put("header", mappedHeader); |
| | | result.put("items", mappedItems); |
| | | |
| | | log.info("批量映射完成,共处理 {} 条明细", mappedItems.size()); |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("批量处理成功") |
| | | .setData(result); |
| | | } |
| | | |
| | | /** |
| | | * 示例5:条件映射 |
| | | * |
| | | * 场景:根据不同的应用来源,使用不同的映射规则 |
| | | */ |
| | | @ApiOperation("示例:条件映射") |
| | | @PostMapping("/dynamic/mapping") |
| | | public CommonResponse dynamicMapping(@RequestBody JSONObject requestData) { |
| | | |
| | | // 从请求中获取应用标识 |
| | | String source = requestData.getString("source"); |
| | | String funcId = requestData.getString("function"); |
| | | |
| | | // 移除元数据字段 |
| | | requestData.remove("source"); |
| | | requestData.remove("function"); |
| | | |
| | | log.info("动态映射 - 来源:{},功能:{}", source, funcId); |
| | | |
| | | // 根据来源选择不同的映射规则 |
| | | String appId; |
| | | switch (source) { |
| | | case "ERP": |
| | | appId = "ERP_SYSTEM"; |
| | | break; |
| | | case "WCS": |
| | | appId = "WCS_SYSTEM"; |
| | | break; |
| | | case "MES": |
| | | appId = "MES_SYSTEM"; |
| | | break; |
| | | default: |
| | | return CommonResponse.error("未知的数据源:" + source); |
| | | } |
| | | |
| | | // 执行映射 |
| | | JSONObject mappedData = ParamsMapUtils.apiMaps(appId, funcId, requestData); |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("动态映射成功") |
| | | .setData(mappedData); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.controller.example; |
| | | |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.vincent.rsf.openApi.entity.dto.CommonResponse; |
| | | import com.vincent.rsf.openApi.utils.ParamsMapUtils; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * JSON属性名递归替换使用示例 |
| | | * |
| | | * 演示如何使用 FuncMap 递归遍历并替换JSON所有层级的属性名称 |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | @Slf4j |
| | | @RestController |
| | | @RequestMapping("/example/json-replace") |
| | | @Api(tags = "JSON属性递归替换示例") |
| | | public class JsonReplaceExampleController { |
| | | |
| | | /** |
| | | * 示例1:简单对象的属性替换 |
| | | * |
| | | * 输入: |
| | | * { |
| | | * "orderNumber": "PO001", |
| | | * "orderQty": 100, |
| | | * "orderAmount": 5000.00 |
| | | * } |
| | | * |
| | | * 输出: |
| | | * { |
| | | * "code": "PO001", |
| | | * "qty": 100, |
| | | * "anfme": 5000.00 |
| | | * } |
| | | */ |
| | | @ApiOperation("示例1:简单对象属性替换") |
| | | @PostMapping("/simple-replace") |
| | | public CommonResponse simpleReplace(@RequestBody JSONObject data) { |
| | | log.info("原始数据:{}", data); |
| | | |
| | | // 定义映射规则 |
| | | Map<String, String> mappingRules = new HashMap<>(); |
| | | mappingRules.put("orderNumber", "code"); |
| | | mappingRules.put("orderQty", "qty"); |
| | | mappingRules.put("orderAmount", "anfme"); |
| | | |
| | | // 执行替换 |
| | | JSONObject result = ParamsMapUtils.replaceJsonKeys(data, mappingRules); |
| | | |
| | | log.info("替换后数据:{}", result); |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("替换成功") |
| | | .setData(result); |
| | | } |
| | | |
| | | /** |
| | | * 示例2:嵌套对象的深度替换 |
| | | * |
| | | * 输入: |
| | | * { |
| | | * "orderNumber": "PO001", |
| | | * "customer": { |
| | | * "customerName": "张三", |
| | | * "customerPhone": "13800138000", |
| | | * "address": { |
| | | * "cityName": "北京", |
| | | * "streetName": "朝阳路" |
| | | * } |
| | | * } |
| | | * } |
| | | * |
| | | * 输出: |
| | | * { |
| | | * "code": "PO001", |
| | | * "customer": { |
| | | * "name": "张三", |
| | | * "phone": "13800138000", |
| | | * "address": { |
| | | * "city": "北京", |
| | | * "street": "朝阳路" |
| | | * } |
| | | * } |
| | | * } |
| | | */ |
| | | @ApiOperation("示例2:嵌套对象深度替换") |
| | | @PostMapping("/nested-replace") |
| | | public CommonResponse nestedReplace(@RequestBody JSONObject data) { |
| | | log.info("原始嵌套数据:{}", data); |
| | | |
| | | // 定义映射规则(适用于所有层级) |
| | | Map<String, String> mappingRules = new HashMap<>(); |
| | | mappingRules.put("orderNumber", "code"); |
| | | mappingRules.put("customerName", "name"); |
| | | mappingRules.put("customerPhone", "phone"); |
| | | mappingRules.put("cityName", "city"); |
| | | mappingRules.put("streetName", "street"); |
| | | |
| | | // 递归替换所有层级 |
| | | JSONObject result = ParamsMapUtils.replaceJsonKeys(data, mappingRules); |
| | | |
| | | log.info("替换后嵌套数据:{}", result); |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("嵌套替换成功") |
| | | .setData(result); |
| | | } |
| | | |
| | | /** |
| | | * 示例3:数组对象的批量替换 |
| | | * |
| | | * 输入: |
| | | * { |
| | | * "orderNumber": "PO001", |
| | | * "items": [ |
| | | * { |
| | | * "materialCode": "MAT001", |
| | | * "materialName": "物料A", |
| | | * "itemQty": 10 |
| | | * }, |
| | | * { |
| | | * "materialCode": "MAT002", |
| | | * "materialName": "物料B", |
| | | * "itemQty": 20 |
| | | * } |
| | | * ] |
| | | * } |
| | | * |
| | | * 输出: |
| | | * { |
| | | * "code": "PO001", |
| | | * "items": [ |
| | | * { |
| | | * "matnr": "MAT001", |
| | | * "maktx": "物料A", |
| | | * "qty": 10 |
| | | * }, |
| | | * { |
| | | * "matnr": "MAT002", |
| | | * "maktx": "物料B", |
| | | * "qty": 20 |
| | | * } |
| | | * ] |
| | | * } |
| | | */ |
| | | @ApiOperation("示例3:数组对象批量替换") |
| | | @PostMapping("/array-replace") |
| | | public CommonResponse arrayReplace(@RequestBody JSONObject data) { |
| | | log.info("原始数组数据:{}", data); |
| | | |
| | | // 定义映射规则 |
| | | Map<String, String> mappingRules = new HashMap<>(); |
| | | mappingRules.put("orderNumber", "code"); |
| | | mappingRules.put("materialCode", "matnr"); |
| | | mappingRules.put("materialName", "maktx"); |
| | | mappingRules.put("itemQty", "qty"); |
| | | |
| | | // 递归替换(包括数组中的所有对象) |
| | | JSONObject result = ParamsMapUtils.replaceJsonKeys(data, mappingRules); |
| | | |
| | | log.info("替换后数组数据:{}", result); |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("数组替换成功") |
| | | .setData(result); |
| | | } |
| | | |
| | | /** |
| | | * 示例4:复杂结构的完整替换 |
| | | * |
| | | * 输入包含:嵌套对象 + 数组 + 多层嵌套 |
| | | */ |
| | | @ApiOperation("示例4:复杂结构完整替换") |
| | | @PostMapping("/complex-replace") |
| | | public CommonResponse complexReplace(@RequestBody JSONObject data) { |
| | | log.info("原始复杂数据:{}", data); |
| | | |
| | | // 定义完整的映射规则 |
| | | Map<String, String> mappingRules = new HashMap<>(); |
| | | // 订单字段 |
| | | mappingRules.put("orderNumber", "code"); |
| | | mappingRules.put("orderType", "type"); |
| | | mappingRules.put("orderQty", "qty"); |
| | | mappingRules.put("orderAmount", "anfme"); |
| | | mappingRules.put("orderStatus", "exceStatus"); |
| | | |
| | | // 客户字段 |
| | | mappingRules.put("customerName", "custName"); |
| | | mappingRules.put("customerCode", "custCode"); |
| | | |
| | | // 物料字段 |
| | | mappingRules.put("materialCode", "matnr"); |
| | | mappingRules.put("materialName", "maktx"); |
| | | mappingRules.put("materialSpec", "spec"); |
| | | mappingRules.put("materialUnit", "meins"); |
| | | |
| | | // 其他字段 |
| | | mappingRules.put("warehouseCode", "whCode"); |
| | | mappingRules.put("locationCode", "locCode"); |
| | | |
| | | // 执行递归替换 |
| | | JSONObject result = ParamsMapUtils.replaceJsonKeys(data, mappingRules); |
| | | |
| | | log.info("替换后复杂数据:{}", result); |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("复杂替换成功") |
| | | .setData(result); |
| | | } |
| | | |
| | | /** |
| | | * 示例5:使用appId和funcId自动映射 |
| | | * |
| | | * 这个示例展示如何结合数据库配置自动进行字段映射 |
| | | */ |
| | | @ApiOperation("示例5:使用配置自动映射") |
| | | @PostMapping("/auto-replace") |
| | | public CommonResponse autoReplace(@RequestBody JSONObject request) { |
| | | String appId = request.getString("appId"); |
| | | String funcId = request.getString("funcId"); |
| | | JSONObject data = request.getJSONObject("data"); |
| | | |
| | | log.info("自动映射 - appId:{}, funcId:{}, 数据:{}", appId, funcId, data); |
| | | |
| | | // 使用 FuncMap 的 apiMaps 方法,它会自动从数据库加载映射规则并递归替换 |
| | | JSONObject result = ParamsMapUtils.apiMaps(appId, funcId, data); |
| | | |
| | | log.info("自动映射后数据:{}", result); |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("自动映射成功") |
| | | .setData(result); |
| | | } |
| | | |
| | | /** |
| | | * 示例6:直接替换JSON数组 |
| | | */ |
| | | @ApiOperation("示例6:直接替换JSON数组") |
| | | @PostMapping("/array-direct-replace") |
| | | public CommonResponse arrayDirectReplace(@RequestBody JSONArray array) { |
| | | log.info("原始JSON数组:{}", array); |
| | | |
| | | // 定义映射规则 |
| | | Map<String, String> mappingRules = new HashMap<>(); |
| | | mappingRules.put("materialCode", "matnr"); |
| | | mappingRules.put("materialName", "maktx"); |
| | | mappingRules.put("stockQty", "qty"); |
| | | mappingRules.put("warehouseCode", "whCode"); |
| | | |
| | | // 直接替换数组 |
| | | JSONArray result = ParamsMapUtils.replaceJsonArrayKeys(array, mappingRules); |
| | | |
| | | log.info("替换后JSON数组:{}", result); |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("数组直接替换成功") |
| | | .setData(result); |
| | | } |
| | | |
| | | /** |
| | | * 测试用例:生成示例数据 |
| | | */ |
| | | @ApiOperation("生成测试数据") |
| | | @PostMapping("/generate-test-data") |
| | | public CommonResponse generateTestData() { |
| | | // 创建复杂的测试数据 |
| | | JSONObject testData = new JSONObject(); |
| | | testData.put("orderNumber", "PO20260104001"); |
| | | testData.put("orderType", "PURCHASE"); |
| | | testData.put("orderQty", 500); |
| | | testData.put("orderAmount", 25000.00); |
| | | testData.put("orderStatus", "PENDING"); |
| | | |
| | | // 客户信息 |
| | | JSONObject customer = new JSONObject(); |
| | | customer.put("customerCode", "CUST001"); |
| | | customer.put("customerName", "北京科技有限公司"); |
| | | |
| | | JSONObject address = new JSONObject(); |
| | | address.put("cityName", "北京"); |
| | | address.put("districtName", "朝阳区"); |
| | | address.put("streetName", "建国路88号"); |
| | | customer.put("address", address); |
| | | |
| | | testData.put("customer", customer); |
| | | |
| | | // 订单明细 |
| | | JSONArray items = new JSONArray(); |
| | | for (int i = 1; i <= 3; i++) { |
| | | JSONObject item = new JSONObject(); |
| | | item.put("itemNo", i); |
| | | item.put("materialCode", "MAT00" + i); |
| | | item.put("materialName", "测试物料" + i); |
| | | item.put("materialSpec", "规格" + i); |
| | | item.put("materialUnit", "EA"); |
| | | item.put("itemQty", 100 + i * 50); |
| | | item.put("itemAmount", 5000.00 + i * 1000); |
| | | |
| | | // 仓库信息 |
| | | JSONObject warehouse = new JSONObject(); |
| | | warehouse.put("warehouseCode", "WH0" + i); |
| | | warehouse.put("locationCode", "LOC-A-0" + i); |
| | | item.put("warehouse", warehouse); |
| | | |
| | | items.add(item); |
| | | } |
| | | testData.put("items", items); |
| | | |
| | | return CommonResponse.ok() |
| | | .setMsg("测试数据生成成功") |
| | | .setData(testData); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.controller.example; |
| | | |
| | | import com.vincent.rsf.framework.common.Cools; |
| | | import com.vincent.rsf.openApi.entity.constant.Constants; |
| | | import com.vincent.rsf.openApi.entity.dto.CommonResponse; |
| | | import com.vincent.rsf.openApi.entity.AppAuthParam; |
| | | import com.vincent.rsf.openApi.security.service.AppAuthService; |
| | | import com.vincent.rsf.openApi.security.utils.TokenUtils; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * Token认证示例控制器 |
| | | * 演示如何使用JWT Token进行接口保护 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/api/example/token") |
| | | public class TokenAuthExampleController { |
| | | private static final Logger log = LoggerFactory.getLogger(TokenAuthExampleController.class); |
| | | |
| | | @Resource |
| | | private AppAuthService appAuthService; |
| | | |
| | | /** |
| | | * 获取受保护的数据 - 需要有效的Token |
| | | * |
| | | * @param request HTTP请求 |
| | | * @return 受保护的数据 |
| | | */ |
| | | @GetMapping("/protected-data") |
| | | public Map<String, Object> getProtectedData(HttpServletRequest request) { |
| | | // 从请求属性中获取认证信息(由TokenAuthenticationFilter设置) |
| | | String appId = (String) request.getAttribute(Constants.REQUEST_ATTR_APP_ID); |
| | | String userId = (String) request.getAttribute(Constants.REQUEST_ATTR_USER_ID); |
| | | |
| | | log.info("访问受保护接口,AppId: {}, UserId: {}", appId, userId); |
| | | |
| | | Map<String, Object> response = new HashMap<>(); |
| | | response.put("code", "200"); |
| | | response.put("message", "访问成功"); |
| | | response.put("data", Map.of( |
| | | "appId", appId, |
| | | "userId", userId, |
| | | "protectedInfo", "这是受保护的数据", |
| | | "timestamp", System.currentTimeMillis() |
| | | )); |
| | | response.put("success", true); |
| | | |
| | | return response; |
| | | } |
| | | |
| | | /** |
| | | * 获取用户信息 - 需要有效的Token |
| | | * |
| | | * @param request HTTP请求 |
| | | * @return 用户信息 |
| | | */ |
| | | @GetMapping("/user-info") |
| | | public Map<String, Object> getUserInfo(HttpServletRequest request) { |
| | | // 从请求属性中获取认证信息 |
| | | String appId = (String) request.getAttribute(Constants.REQUEST_ATTR_APP_ID); |
| | | String userId = (String) request.getAttribute(Constants.REQUEST_ATTR_USER_ID); |
| | | |
| | | log.info("获取用户信息,AppId: {}, UserId: {}", appId, userId); |
| | | |
| | | Map<String, Object> response = new HashMap<>(); |
| | | response.put("code", "200"); |
| | | response.put("message", "获取用户信息成功"); |
| | | response.put("data", Map.of( |
| | | "appId", appId, |
| | | "userId", userId, |
| | | "userName", "用户" + (userId != null ? userId : "未知"), |
| | | "role", "USER", |
| | | "permissions", new String[]{"read", "write"} |
| | | )); |
| | | response.put("success", true); |
| | | |
| | | return response; |
| | | } |
| | | |
| | | /** |
| | | * 手动生成Token的示例接口 |
| | | * 注意:在实际应用中,这个接口通常需要其他形式的认证 |
| | | * |
| | | * @param appId 应用ID |
| | | * @param userId 用户ID |
| | | * @return 包含Token的响应 |
| | | */ |
| | | @PostMapping("/generate-token") |
| | | public Map<String, Object> generateToken(@RequestParam String appId, @RequestParam(required = false) String userId) { |
| | | log.info("生成Token,AppId: {}, UserId: {}", appId, userId); |
| | | |
| | | try { |
| | | // 生成Token |
| | | String token = TokenUtils.generateToken(appId, userId); |
| | | |
| | | Map<String, Object> response = new HashMap<>(); |
| | | response.put("code", "200"); |
| | | response.put("message", "Token生成成功"); |
| | | response.put("data", Map.of( |
| | | "token", token, |
| | | "appId", appId, |
| | | "userId", userId, |
| | | "expiresIn", 24 * 60 * 60 // 24小时过期 |
| | | )); |
| | | response.put("success", true); |
| | | |
| | | return response; |
| | | } catch (Exception e) { |
| | | log.error("生成Token失败", e); |
| | | |
| | | Map<String, Object> response = new HashMap<>(); |
| | | response.put("code", "500"); |
| | | response.put("message", "生成Token失败: " + e.getMessage()); |
| | | response.put("data", null); |
| | | response.put("success", false); |
| | | |
| | | return response; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * 获取App认证Token |
| | | * |
| | | * @param param 应用ID和应用密钥 |
| | | * @return 认证Token |
| | | */ |
| | | @ApiOperation("获取App认证Token") |
| | | @PostMapping("/getToken") |
| | | public CommonResponse getToken(@RequestBody AppAuthParam param) { |
| | | String appId = param.getAppId(); |
| | | String appSecret = param.getAppSecret(); |
| | | |
| | | if (Cools.isEmpty(appId, appSecret)) { |
| | | return CommonResponse.error("AppId和AppSecret不能为空"); |
| | | } |
| | | |
| | | boolean isValid = appAuthService.validateApp(appId, appSecret); |
| | | if (isValid) { |
| | | String token = appAuthService.generateAppToken(appId, appSecret); |
| | | return CommonResponse.ok() |
| | | .setMsg("获取Token成功") |
| | | .setData(token); |
| | | } else { |
| | | return CommonResponse.error("AppId或AppSecret无效"); |
| | | } |
| | | } |
| | | |
| | | // /** |
| | | // * 验证Token的接口 |
| | | // * |
| | | // * @param token 要验证的Token |
| | | // * @return Token验证结果 |
| | | // */ |
| | | // @PostMapping("/validateToken") |
| | | // public Map<String, Object> validateToken(@RequestParam String token) { |
| | | // log.info("验证Token: {}", token.substring(0, Math.min(10, token.length())) + "..."); |
| | | // |
| | | // boolean isValid = TokenUtils.validateToken(token); |
| | | // |
| | | // Map<String, Object> response = new HashMap<>(); |
| | | // response.put("code", "200"); |
| | | // response.put("message", isValid ? "Token有效" : "Token无效"); |
| | | // response.put("data", Map.of( |
| | | // "valid", isValid, |
| | | // "appId", isValid ? TokenUtils.getAppIdFromToken(token) : null, |
| | | // "userId", isValid ? TokenUtils.getUserIdFromToken(token) : null |
| | | // )); |
| | | // response.put("success", isValid); |
| | | // |
| | | // return response; |
| | | // } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.controller.phyz; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.openApi.entity.dto.CommonResponse; |
| | | import com.vincent.rsf.openApi.entity.phyz.*; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.compress.utils.Lists; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | import static com.vincent.rsf.openApi.controller.AuthController.SIMULATED_DATA_ENABLE; |
| | | |
| | | @RestController |
| | | @RequestMapping("/erp") |
| | | @Api("银座新工厂(五期)ERP接口") |
| | | @Slf4j |
| | | public class ERPController { |
| | | |
| | | @ApiOperation("仓库信息同步") |
| | | @PostMapping("/wareHouse/sync") |
| | | public CommonResponse syncWareHouse(@RequestBody Object objParams) { |
| | | if (Objects.isNull(objParams)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | // 返回模拟数据 |
| | | if (SIMULATED_DATA_ENABLE.equals("1")) { |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | JSONArray params = paramsFormat(objParams); |
| | | List<Warehouse> warehouseList = JSON.parseArray(params.toJSONString(), Warehouse.class); |
| | | // 数据处理,转发server |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | @ApiOperation("物料信息同步") |
| | | @PostMapping("/mat/sync") |
| | | public CommonResponse syncMaterial(@RequestBody Object objParams) { |
| | | if (Objects.isNull(objParams)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | // 返回模拟数据 |
| | | if (SIMULATED_DATA_ENABLE.equals("1")) { |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | JSONArray params = paramsFormat(objParams); |
| | | List<Material> materialList = JSON.parseArray(params.toJSONString(), Material.class); |
| | | // 数据处理,转发server |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | @ApiOperation("客户信息同步") |
| | | @PostMapping("/customer/sync") |
| | | public CommonResponse syncCustomer(@RequestBody Object objParams) { |
| | | if (Objects.isNull(objParams)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | // 返回模拟数据 |
| | | if (SIMULATED_DATA_ENABLE.equals("1")) { |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | JSONArray params = paramsFormat(objParams); |
| | | List<Customer> customerList = JSON.parseArray(params.toJSONString(), Customer.class); |
| | | // 数据处理,转发server |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | @ApiOperation("供应商信息同步") |
| | | @PostMapping("/supplier/sync") |
| | | public CommonResponse syncSupplier(@RequestBody Object objParams) { |
| | | if (Objects.isNull(objParams)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | // 返回模拟数据 |
| | | if (SIMULATED_DATA_ENABLE.equals("1")) { |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | JSONArray params = paramsFormat(objParams); |
| | | List<Supplier> supplierList = JSON.parseArray(params.toJSONString(), Supplier.class); |
| | | // 数据处理,转发server |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | @ApiOperation("入/出库任务通知单") |
| | | @PostMapping("/order/add") |
| | | public CommonResponse addOrder(@RequestBody Object objParams) { |
| | | if (Objects.isNull(objParams)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | // 返回模拟数据 |
| | | if (SIMULATED_DATA_ENABLE.equals("1")) { |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | JSONArray params = paramsFormat(objParams); |
| | | List<Order> orderList = JSON.parseArray(params.toJSONString(), Order.class); |
| | | // 数据处理,转发server |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | @ApiOperation("入/出库任务通知单取消") |
| | | @PostMapping("/order/cancel") |
| | | public CommonResponse cancelOrder(@RequestBody Object objParams) { |
| | | if (Objects.isNull(objParams)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | // 返回模拟数据 |
| | | if (SIMULATED_DATA_ENABLE.equals("1")) { |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | JSONArray params = paramsFormat(objParams); |
| | | List<Order> orderList = JSON.parseArray(params.toJSONString(), Order.class); |
| | | // 数据处理,转发server |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | @ApiOperation("库存查询明细") |
| | | @PostMapping("/inventory/details") |
| | | public CommonResponse queryInventoryDetails(@RequestBody JSONObject params) { |
| | | if (SIMULATED_DATA_ENABLE.equals("1")) { |
| | | String x = "[\n" + |
| | | " {\n" + |
| | | " \"locId\": \"LOC-A-01-01\",\n" + |
| | | " \"wareHouseId\": \"WH001\",\n" + |
| | | " \"wareHouseName\": \"原料仓库\",\n" + |
| | | " \"palletId\": \"PALLET001\",\n" + |
| | | " \"matNr\": \"MAT10001\",\n" + |
| | | " \"makTx\": \"钢材Q235\",\n" + |
| | | " \"spec\": \"国标GB/T700-2006\",\n" + |
| | | " \"anfme\": 10.5,\n" + |
| | | " \"unit\": \"吨\",\n" + |
| | | " \"status\": \"可用\",\n" + |
| | | " \"orderType\": 1,\n" + |
| | | " \"orderNo\": \"Order202698921\",\n" + |
| | | " \"prepareType\": 1,\n" + |
| | | " \"planNo\": \"PLAN202601060001\",\n" + |
| | | " \"batch\": \"BATCH20260106001\",\n" + |
| | | " \"stockOrgId\": \"ORG001\"\n" + |
| | | " },\n" + |
| | | " {\n" + |
| | | " \"locId\": \"LOC-B-02-03\",\n" + |
| | | " \"wareHouseId\": \"WH002\",\n" + |
| | | " \"wareHouseName\": \"成品仓库\",\n" + |
| | | " \"palletId\": \"PALLET002\",\n" + |
| | | " \"matNr\": \"MAT20001\",\n" + |
| | | " \"makTx\": \"电机组件\",\n" + |
| | | " \"spec\": \"380V 50Hz\",\n" + |
| | | " \"anfme\": 50,\n" + |
| | | " \"unit\": \"台\",\n" + |
| | | " \"status\": \"可用\",\n" + |
| | | " \"orderType\": \"1\",\n" + |
| | | " \"orderNo\": \"SO202601060001\",\n" + |
| | | " \"prepareType\": 1,\n" + |
| | | " \"planNo\": \"PLAN202601060002\",\n" + |
| | | " \"batch\": \"BATCH20260106002\",\n" + |
| | | " \"stockOrgId\": \"ORG001\"\n" + |
| | | " }\n" + |
| | | "]"; |
| | | return CommonResponse.ok(JSONArray.parseArray(x, InventoryDetails.class)); |
| | | } |
| | | |
| | | InventoryQueryCondition condition = JSON.parseObject(params.toJSONString(), InventoryQueryCondition.class); |
| | | // 数据处理,转发server |
| | | List<InventoryDetails> inventoryDetails = Lists.newArrayList(); |
| | | return new CommonResponse().setCode(200).setData(inventoryDetails); |
| | | } |
| | | |
| | | @ApiOperation("库存查询汇总") |
| | | @PostMapping("/inventory/summary") |
| | | public CommonResponse queryInventorySummary(@RequestBody JSONObject params) { |
| | | if (SIMULATED_DATA_ENABLE.equals("1")) { |
| | | String s = "{\n" + |
| | | " \"code\": 200,\n" + |
| | | " \"msg\": \"操作成功\",\n" + |
| | | " \"data\": [\n" + |
| | | " {\n" + |
| | | " \"wareHouseId\": \"WH001\",\n" + |
| | | " \"wareHouseName\": \"原料仓库\",\n" + |
| | | " \"matNr\": \"MAT10001\",\n" + |
| | | " \"makTx\": \"钢材Q235\",\n" + |
| | | " \"spec\": \"国标GB/T700-2006\",\n" + |
| | | " \"anfme\": 10.5,\n" + |
| | | " \"unit\": \"吨\",\n" + |
| | | " \"stockOrgId\": \"ORG001\",\n" + |
| | | " \"batch\": \"BATCH20260106001\",\n" + |
| | | " \"planNo\": \"Plan20260106006\"\n" + |
| | | " },\n" + |
| | | " {\n" + |
| | | " \"wareHouseId\": \"WH001\",\n" + |
| | | " \"wareHouseName\": \"原料仓库\",\n" + |
| | | " \"matNr\": \"MAT10002\",\n" + |
| | | " \"makTx\": \"铝型材6061\",\n" + |
| | | " \"spec\": \"国标GB/T3190-2008\",\n" + |
| | | " \"anfme\": 20.3,\n" + |
| | | " \"unit\": \"吨\",\n" + |
| | | " \"stockOrgId\": \"ORG001\",\n" + |
| | | " \"batch\": \"BATCH20260106002\",\n" + |
| | | " \"planNo\": \"Plan20260106005\"\n" + |
| | | " },\n" + |
| | | " {\n" + |
| | | " \"wareHouseId\": \"WH002\",\n" + |
| | | " \"wareHouseName\": \"成品仓库\",\n" + |
| | | " \"matNr\": \"MAT30001\",\n" + |
| | | " \"makTx\": \"电机成品\",\n" + |
| | | " \"spec\": \"380V 50Hz 15KW\",\n" + |
| | | " \"anfme\": 100,\n" + |
| | | " \"unit\": \"台\",\n" + |
| | | " \"stockOrgId\": \"ORG001\",\n" + |
| | | " \"batch\": \"BATCH20260106003\",\n" + |
| | | " \"planNo\": \"Plan20260106004\"\n" + |
| | | " }\n" + |
| | | " ]\n" + |
| | | "}"; |
| | | return JSONObject.parseObject(s, CommonResponse.class); |
| | | } |
| | | |
| | | InventoryQueryCondition condition = JSON.parseObject(params.toJSONString(), InventoryQueryCondition.class); |
| | | // 数据处理,转发server |
| | | List<InventorySummary> inventorySummaries = Lists.newArrayList(); |
| | | return new CommonResponse().setCode(200).setData(inventorySummaries); |
| | | |
| | | } |
| | | |
| | | @ApiOperation("盘点结果确认") |
| | | @PostMapping("/check/confirm") |
| | | public CommonResponse checkConfirm(@RequestBody Object objParams) { |
| | | if (Objects.isNull(objParams)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | // 返回模拟数据 |
| | | if (SIMULATED_DATA_ENABLE.equals("1")) { |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | JSONArray params = paramsFormat(objParams); |
| | | CheckOrder checkResult = JSON.parseObject(params.toJSONString(), CheckOrder.class); |
| | | // 数据处理,转发server |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | /** |
| | | * 兼容JSONObject和JSONArray格式数据 |
| | | * |
| | | * @param data json格式参数 |
| | | * @return JSONArray格式数据 |
| | | */ |
| | | public static JSONArray paramsFormat(Object data) { |
| | | if (Objects.isNull(data)) { |
| | | return new JSONArray(); |
| | | } |
| | | |
| | | try { |
| | | String jsonStr = JSON.toJSONString(data); |
| | | if (jsonStr.startsWith("[")) { |
| | | return JSON.parseArray(jsonStr); |
| | | } else if (jsonStr.startsWith("{")) { |
| | | JSONArray params = new JSONArray(); |
| | | params.add(JSON.parseObject(jsonStr)); |
| | | return params; |
| | | } |
| | | } catch (Exception e) { |
| | | // 解析失败,返回空数组 |
| | | log.error("转换参数为json格式错误", e); |
| | | } |
| | | |
| | | // 默认返回空数组 |
| | | return new JSONArray(); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.controller.phyz; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.openApi.entity.dto.CommonResponse; |
| | | import com.vincent.rsf.openApi.entity.phyz.*; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | import static com.vincent.rsf.openApi.controller.AuthController.SIMULATED_DATA_ENABLE; |
| | | import static com.vincent.rsf.openApi.controller.phyz.ERPController.paramsFormat; |
| | | |
| | | @RestController |
| | | @RequestMapping("/mes") |
| | | @Api("银座新工厂(五期)MES接口") |
| | | public class MESController { |
| | | |
| | | @ApiOperation("备料通知") |
| | | @PostMapping("/callMaterial") |
| | | public CommonResponse callMaterial(@RequestBody Object objParams) { |
| | | if (Objects.isNull(objParams)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | // 返回模拟数据 |
| | | if (SIMULATED_DATA_ENABLE.equals("0")) { |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | JSONArray params = paramsFormat(objParams); |
| | | List<MatPreparationOrder> orders = JSON.parseArray(params.toJSONString(), MatPreparationOrder.class); |
| | | // 数据处理,转发server |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | @ApiOperation("简单生产领料、退料AGV任务") |
| | | @PostMapping("/transTask/simpleProduction") |
| | | public CommonResponse simpleProductionTask(@RequestBody Object objParams) { |
| | | if (Objects.isNull(objParams)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | // 返回模拟数据 |
| | | if (SIMULATED_DATA_ENABLE.equals("0")) { |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | JSONArray params = paramsFormat(objParams); |
| | | List<SimpleProductionTask> tasks = JSON.parseArray(params.toJSONString(), SimpleProductionTask.class); |
| | | // 数据处理,转发server |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | @ApiOperation("空托出库") |
| | | @PostMapping("/emptyTray/callOut") |
| | | public CommonResponse callOutEmptyTray(@RequestBody Object objParams) { |
| | | if (Objects.isNull(objParams)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | // 返回模拟数据 |
| | | if (SIMULATED_DATA_ENABLE.equals("1")) { |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | JSONArray params = paramsFormat(objParams); |
| | | List<Task> tasks = JSON.parseArray(params.toJSONString(), Task.class); |
| | | // 数据处理,转发server |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | @ApiOperation("空托入库") |
| | | @PostMapping("/emptyTray/backIn") |
| | | public CommonResponse emptyTrayBackIn(@RequestBody Object objParams) { |
| | | if (Objects.isNull(objParams)) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | // 返回模拟数据 |
| | | if (SIMULATED_DATA_ENABLE.equals("1")) { |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | JSONArray params = paramsFormat(objParams); |
| | | List<Task> tasks = JSON.parseArray(params.toJSONString(), Task.class); |
| | | // 数据处理,转发server |
| | | return CommonResponse.ok(); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.controller.platform; |
| | | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.vincent.rsf.openApi.entity.app.ApiFunction; |
| | | import com.vincent.rsf.openApi.entity.dto.CommonResponse; |
| | | import com.vincent.rsf.openApi.service.ApiFunctionService; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ApiFunction管理Controller |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/api/function") |
| | | @Api(tags = "接口功能管理") |
| | | public class ApiFunctionController { |
| | | |
| | | @Autowired |
| | | private ApiFunctionService apiFunctionService; |
| | | |
| | | @ApiOperation("分页查询接口功能列表") |
| | | @GetMapping("/page") |
| | | public CommonResponse page(@RequestParam(defaultValue = "1") Integer current, |
| | | @RequestParam(defaultValue = "10") Integer size) { |
| | | Page<ApiFunction> page = apiFunctionService.page(new Page<>(current, size)); |
| | | return CommonResponse.ok().setData(page); |
| | | } |
| | | |
| | | @ApiOperation("查询所有接口功能") |
| | | @GetMapping("/list") |
| | | public CommonResponse list() { |
| | | List<ApiFunction> list = apiFunctionService.list(); |
| | | return CommonResponse.ok().setData(list); |
| | | } |
| | | |
| | | @ApiOperation("根据ID查询接口功能") |
| | | @GetMapping("/{id}") |
| | | public CommonResponse getById(@PathVariable String id) { |
| | | ApiFunction function = apiFunctionService.getById(id); |
| | | return CommonResponse.ok().setData(function); |
| | | } |
| | | |
| | | @ApiOperation("新增接口功能") |
| | | @PostMapping |
| | | public CommonResponse save(@RequestBody ApiFunction function) { |
| | | boolean result = apiFunctionService.save(function); |
| | | if (result) { |
| | | apiFunctionService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("新增成功"); |
| | | } |
| | | return CommonResponse.error("新增失败"); |
| | | } |
| | | |
| | | @ApiOperation("更新接口功能") |
| | | @PutMapping |
| | | public CommonResponse update(@RequestBody ApiFunction function) { |
| | | boolean result = apiFunctionService.updateById(function); |
| | | if (result) { |
| | | apiFunctionService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("更新成功"); |
| | | } |
| | | return CommonResponse.error("更新失败"); |
| | | } |
| | | |
| | | @ApiOperation("删除接口功能") |
| | | @DeleteMapping("/{id}") |
| | | public CommonResponse delete(@PathVariable String id) { |
| | | boolean result = apiFunctionService.removeById(id); |
| | | if (result) { |
| | | apiFunctionService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("删除成功"); |
| | | } |
| | | return CommonResponse.error("删除失败"); |
| | | } |
| | | |
| | | @ApiOperation("批量删除接口功能") |
| | | @DeleteMapping("/batch") |
| | | public CommonResponse deleteBatch(@RequestBody List<String> ids) { |
| | | boolean result = apiFunctionService.removeByIds(ids); |
| | | if (result) { |
| | | apiFunctionService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("批量删除成功"); |
| | | } |
| | | return CommonResponse.error("批量删除失败"); |
| | | } |
| | | |
| | | @ApiOperation("刷新功能缓存") |
| | | @PostMapping("/refresh") |
| | | public CommonResponse refresh() { |
| | | apiFunctionService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("缓存刷新成功"); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.controller.platform; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.vincent.rsf.openApi.entity.app.ApiMap; |
| | | import com.vincent.rsf.openApi.entity.dto.CommonResponse; |
| | | import com.vincent.rsf.openApi.service.ApiMapService; |
| | | import com.vincent.rsf.openApi.utils.ParamsMapUtils; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ApiMap管理Controller |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/api/map") |
| | | @Api(tags = "字段映射管理") |
| | | public class ApiMapController { |
| | | |
| | | @Autowired |
| | | private ApiMapService apiMapService; |
| | | |
| | | |
| | | @ApiOperation("分页查询字段映射列表") |
| | | @GetMapping("/page") |
| | | public CommonResponse page(@RequestParam(defaultValue = "1") Integer current, |
| | | @RequestParam(defaultValue = "10") Integer size, |
| | | @RequestParam(required = false) String appId, |
| | | @RequestParam(required = false) String funcId) { |
| | | LambdaQueryWrapper<ApiMap> wrapper = new LambdaQueryWrapper<>(); |
| | | if (appId != null && !appId.isEmpty()) { |
| | | wrapper.eq(ApiMap::getAppId, appId); |
| | | } |
| | | if (funcId != null && !funcId.isEmpty()) { |
| | | wrapper.eq(ApiMap::getFuncId, funcId); |
| | | } |
| | | Page<ApiMap> page = apiMapService.page(new Page<>(current, size), wrapper); |
| | | return CommonResponse.ok().setData(page); |
| | | } |
| | | |
| | | @ApiOperation("查询所有字段映射") |
| | | @GetMapping("/list") |
| | | public CommonResponse list(@RequestParam(required = false) String appId, |
| | | @RequestParam(required = false) String funcId) { |
| | | LambdaQueryWrapper<ApiMap> wrapper = new LambdaQueryWrapper<>(); |
| | | if (appId != null && !appId.isEmpty()) { |
| | | wrapper.eq(ApiMap::getAppId, appId); |
| | | } |
| | | if (funcId != null && !funcId.isEmpty()) { |
| | | wrapper.eq(ApiMap::getFuncId, funcId); |
| | | } |
| | | List<ApiMap> list = apiMapService.list(wrapper); |
| | | return CommonResponse.ok().setData(list); |
| | | } |
| | | |
| | | @ApiOperation("根据ID查询字段映射") |
| | | @GetMapping("/{id}") |
| | | public CommonResponse getById(@PathVariable Integer id) { |
| | | ApiMap map = apiMapService.getById(id); |
| | | return CommonResponse.ok().setData(map); |
| | | } |
| | | |
| | | @ApiOperation("新增字段映射") |
| | | @PostMapping |
| | | public CommonResponse save(@RequestBody ApiMap map) { |
| | | boolean result = apiMapService.save(map); |
| | | if (result) { |
| | | apiMapService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("新增成功"); |
| | | } |
| | | return CommonResponse.error("新增失败"); |
| | | } |
| | | |
| | | @ApiOperation("批量新增字段映射") |
| | | @PostMapping("/batch") |
| | | public CommonResponse saveBatch(@RequestBody List<ApiMap> maps) { |
| | | boolean result = apiMapService.saveBatch(maps); |
| | | if (result) { |
| | | apiMapService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("批量新增成功"); |
| | | } |
| | | return CommonResponse.error("批量新增失败"); |
| | | } |
| | | |
| | | @ApiOperation("更新字段映射") |
| | | @PutMapping |
| | | public CommonResponse update(@RequestBody ApiMap map) { |
| | | boolean result = apiMapService.updateById(map); |
| | | if (result) { |
| | | apiMapService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("更新成功"); |
| | | } |
| | | return CommonResponse.error("更新失败"); |
| | | } |
| | | |
| | | @ApiOperation("删除字段映射") |
| | | @DeleteMapping("/{id}") |
| | | public CommonResponse delete(@PathVariable Integer id) { |
| | | boolean result = apiMapService.removeById(id); |
| | | if (result) { |
| | | apiMapService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("删除成功"); |
| | | } |
| | | return CommonResponse.error("删除失败"); |
| | | } |
| | | |
| | | @ApiOperation("批量删除字段映射") |
| | | @DeleteMapping("/batch") |
| | | public CommonResponse deleteBatch(@RequestBody List<Integer> ids) { |
| | | boolean result = apiMapService.removeByIds(ids); |
| | | if (result) { |
| | | apiMapService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("批量删除成功"); |
| | | } |
| | | return CommonResponse.error("批量删除失败"); |
| | | } |
| | | |
| | | @ApiOperation("刷新映射缓存") |
| | | @PostMapping("/refresh") |
| | | public CommonResponse refresh() { |
| | | apiMapService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("缓存刷新成功"); |
| | | } |
| | | |
| | | // @ApiOperation("刷新所有缓存") |
| | | // @PostMapping("/refresh/all") |
| | | // public CommonResponse refreshAll() { |
| | | // new ParamsMapUtils().refreshAll(); |
| | | // return CommonResponse.ok().setMsg("所有缓存刷新成功"); |
| | | // } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.controller.platform; |
| | | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.vincent.rsf.openApi.entity.app.App; |
| | | import com.vincent.rsf.openApi.entity.dto.CommonResponse; |
| | | import com.vincent.rsf.openApi.service.AppService; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * App管理Controller |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/api/app") |
| | | @Api(tags = "应用管理") |
| | | public class AppController { |
| | | |
| | | @Autowired |
| | | private AppService appService; |
| | | |
| | | @ApiOperation("分页查询应用列表") |
| | | @GetMapping("/page") |
| | | public CommonResponse page(@RequestParam(defaultValue = "1") Integer current, |
| | | @RequestParam(defaultValue = "10") Integer size) { |
| | | Page<App> page = appService.page(new Page<>(current, size)); |
| | | return CommonResponse.ok().setData(page); |
| | | } |
| | | |
| | | @ApiOperation("查询所有应用") |
| | | @GetMapping("/list") |
| | | public CommonResponse list() { |
| | | List<App> list = appService.list(); |
| | | return CommonResponse.ok().setData(list); |
| | | } |
| | | |
| | | @ApiOperation("根据ID查询应用") |
| | | @GetMapping("/{id}") |
| | | public CommonResponse getById(@PathVariable String id) { |
| | | App app = appService.getById(id); |
| | | return CommonResponse.ok().setData(app); |
| | | } |
| | | |
| | | @ApiOperation("新增应用") |
| | | @PostMapping |
| | | public CommonResponse save(@RequestBody App app) { |
| | | boolean result = appService.save(app); |
| | | if (result) { |
| | | appService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("新增成功"); |
| | | } |
| | | return CommonResponse.error("新增失败"); |
| | | } |
| | | |
| | | @ApiOperation("更新应用") |
| | | @PutMapping |
| | | public CommonResponse update(@RequestBody App app) { |
| | | boolean result = appService.updateById(app); |
| | | if (result) { |
| | | appService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("更新成功"); |
| | | } |
| | | return CommonResponse.error("更新失败"); |
| | | } |
| | | |
| | | @ApiOperation("删除应用") |
| | | @DeleteMapping("/{id}") |
| | | public CommonResponse delete(@PathVariable String id) { |
| | | boolean result = appService.removeById(id); |
| | | if (result) { |
| | | appService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("删除成功"); |
| | | } |
| | | return CommonResponse.error("删除失败"); |
| | | } |
| | | |
| | | @ApiOperation("批量删除应用") |
| | | @DeleteMapping("/batch") |
| | | public CommonResponse deleteBatch(@RequestBody List<String> ids) { |
| | | boolean result = appService.removeByIds(ids); |
| | | if (result) { |
| | | appService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("批量删除成功"); |
| | | } |
| | | return CommonResponse.error("批量删除失败"); |
| | | } |
| | | |
| | | @ApiOperation("刷新应用缓存") |
| | | @PostMapping("/refresh") |
| | | public CommonResponse refresh() { |
| | | appService.refreshCache(); |
| | | return CommonResponse.ok().setMsg("缓存刷新成功"); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity; |
| | | |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | /** |
| | | * App认证参数 |
| | | * |
| | | * 用于AppId和AppSecret的认证请求 |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-05 |
| | | */ |
| | | @Data |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "AppAuthParam", description = "App认证参数") |
| | | public class AppAuthParam { |
| | | |
| | | @ApiModelProperty(value = "应用ID", required = true) |
| | | private String appId; |
| | | |
| | | @ApiModelProperty(value = "应用密钥", required = true) |
| | | private String appSecret; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.app; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.vincent.rsf.framework.common.Cools; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.io.Serial; |
| | | import java.io.Serializable; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Date; |
| | | |
| | | @Data |
| | | @TableName("open_api_foreign_log") |
| | | public class ApiForeignLog implements Serializable { |
| | | |
| | | @Serial |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | /** |
| | | * ID |
| | | */ |
| | | @ApiModelProperty(value= "ID") |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | /** |
| | | * 名称空间 |
| | | */ |
| | | @ApiModelProperty(value= "名称空间") |
| | | private String namespace; |
| | | |
| | | /** |
| | | * 接口地址 |
| | | */ |
| | | @ApiModelProperty(value= "接口地址") |
| | | private String url; |
| | | |
| | | /** |
| | | * 平台密钥 |
| | | */ |
| | | @ApiModelProperty(value= "平台密钥") |
| | | private String appkey; |
| | | |
| | | /** |
| | | * 时间戳 |
| | | */ |
| | | @ApiModelProperty(value= "时间戳") |
| | | private String timestamp; |
| | | |
| | | /** |
| | | * 客户端IP |
| | | */ |
| | | @ApiModelProperty(value= "客户端IP") |
| | | private String clientIp; |
| | | |
| | | /** |
| | | * 请求内容 |
| | | */ |
| | | @ApiModelProperty(value= "请求内容") |
| | | private String request; |
| | | |
| | | /** |
| | | * 响应内容 |
| | | */ |
| | | @ApiModelProperty(value= "响应内容") |
| | | private String response; |
| | | |
| | | /** |
| | | * 消耗时间 |
| | | */ |
| | | @ApiModelProperty(value= "消耗时间") |
| | | private Integer spendTime; |
| | | |
| | | /** |
| | | * 异常内容 |
| | | */ |
| | | @ApiModelProperty(value= "异常内容") |
| | | private String err; |
| | | |
| | | /** |
| | | * 结果 1: 成功 0: 失败 |
| | | */ |
| | | @ApiModelProperty(value= "结果 1: 成功 0: 失败 ") |
| | | private Integer result; |
| | | |
| | | /** |
| | | * 用户 |
| | | */ |
| | | @ApiModelProperty(value= "用户") |
| | | private Long userId; |
| | | |
| | | /** |
| | | * 所属机构 |
| | | */ |
| | | @ApiModelProperty(value= "所属机构") |
| | | private Long tenantId; |
| | | |
| | | /** |
| | | * 添加时间 |
| | | */ |
| | | @ApiModelProperty(value= "添加时间") |
| | | @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") |
| | | @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") |
| | | private Date createTime; |
| | | |
| | | /** |
| | | * 备注 |
| | | */ |
| | | @ApiModelProperty(value= "备注") |
| | | private String memo; |
| | | |
| | | public String getResult$(){ |
| | | if (null == this.result){ return null; } |
| | | switch (this.result){ |
| | | case 1: |
| | | return "成功"; |
| | | case 0: |
| | | return "失败"; |
| | | default: |
| | | return String.valueOf(this.result); |
| | | } |
| | | } |
| | | |
| | | public String getCreateTime$(){ |
| | | if (Cools.isEmpty(this.createTime)){ |
| | | return ""; |
| | | } |
| | | return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.app; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | import java.io.Serial; |
| | | import java.io.Serializable; |
| | | |
| | | @Data |
| | | @TableName("open_api_function") |
| | | public class ApiFunction implements Serializable { |
| | | |
| | | @Serial |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | /** |
| | | * functionId |
| | | */ |
| | | @ApiModelProperty(value= "functionId") |
| | | @TableId(value = "id") |
| | | private String id; |
| | | |
| | | /** |
| | | * functionName |
| | | */ |
| | | @ApiModelProperty(value = "functionName") |
| | | private String name; |
| | | |
| | | /** |
| | | * functionType,1 WMS接收;2 WMS发送; |
| | | */ |
| | | @ApiModelProperty(value = "functionType") |
| | | private Integer type; |
| | | |
| | | /** |
| | | * functionUrl,对应rsf-server的controller接口相对路径 |
| | | */ |
| | | @ApiModelProperty(value = "functionUrl") |
| | | private String url; |
| | | |
| | | /** |
| | | * 租户id |
| | | */ |
| | | @ApiModelProperty(value = "租户id") |
| | | private Long tenant_id; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.app; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | import java.io.Serial; |
| | | import java.io.Serializable; |
| | | |
| | | @Data |
| | | @TableName("open_api_attribute_map") |
| | | public class ApiMap implements Serializable { |
| | | |
| | | @Serial |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | /** |
| | | * id |
| | | */ |
| | | @ApiModelProperty(value= "id") |
| | | @TableId(value = "id") |
| | | private Integer id; |
| | | |
| | | /** |
| | | * appId |
| | | */ |
| | | @ApiModelProperty(value= "appId") |
| | | private String appId; |
| | | |
| | | /** |
| | | * func_id |
| | | */ |
| | | @ApiModelProperty(value= "func_id") |
| | | private String funcId; |
| | | |
| | | /** |
| | | * func_type |
| | | */ |
| | | @ApiModelProperty(value= "func_type") |
| | | private String funcType; |
| | | |
| | | /** |
| | | * source_attribute |
| | | */ |
| | | @ApiModelProperty(value = "source_attribute") |
| | | private String sourceAttribute; |
| | | |
| | | // /** |
| | | // * source_type,源属性类型,Integer、String、Double、JSONObject、JSONArray |
| | | // */ |
| | | // @ApiModelProperty(value = "source_type") |
| | | // private String sourceType; |
| | | |
| | | /** |
| | | * target_attribute |
| | | */ |
| | | @ApiModelProperty(value = "target_attribute") |
| | | private String targetAttribute; |
| | | |
| | | // /** |
| | | // * target_type,目标属性类型,Integer、String、Double、JSONObject、JSONArray |
| | | // */ |
| | | // @ApiModelProperty(value = "target_type") |
| | | // private String targetType; |
| | | |
| | | /** |
| | | * 是否启用,0 未启用;1 启用; |
| | | */ |
| | | @ApiModelProperty(value = "是否启用,0 未启用;1 启用;") |
| | | private Integer enable; |
| | | |
| | | /** |
| | | * 租户id |
| | | */ |
| | | @ApiModelProperty(value = "租户id") |
| | | private Long tenant_id; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.app; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | import java.io.Serial; |
| | | import java.io.Serializable; |
| | | |
| | | @Data |
| | | @TableName("open_api_app") |
| | | public class App implements Serializable { |
| | | |
| | | @Serial |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | /** |
| | | * appId |
| | | */ |
| | | @ApiModelProperty(value= "appId") |
| | | @TableId(value = "id") |
| | | private String id; |
| | | |
| | | /** |
| | | * appScrect |
| | | */ |
| | | @ApiModelProperty(value = "appScrect") |
| | | private String screct; |
| | | |
| | | /** |
| | | * appName |
| | | */ |
| | | @ApiModelProperty(value = "appName") |
| | | private String name; |
| | | |
| | | /** |
| | | * appUrl |
| | | */ |
| | | @ApiModelProperty(value = "appUrl") |
| | | private String url; |
| | | |
| | | /** |
| | | * 是否启用,0 未启用;1 启用; |
| | | */ |
| | | @ApiModelProperty(value = "是否启用,0 未启用;1 启用;") |
| | | private Integer enable; |
| | | |
| | | /** |
| | | * 租户id |
| | | */ |
| | | @ApiModelProperty(value = "租户id") |
| | | private Long tenant_id; |
| | | } |
| | |
| | | /** |
| | | * 无权限错误码 |
| | | */ |
| | | public static final int UNAUTHORIZED_CODE = 403; |
| | | public static final String UNAUTHORIZED_CODE = "403"; |
| | | |
| | | /** |
| | | * 无权限提示信息 |
| | |
| | | /** |
| | | * 未认证错误码 |
| | | */ |
| | | public static final int UNAUTHENTICATED_CODE = 401; |
| | | public static final String UNAUTHENTICATED_CODE = "401"; |
| | | |
| | | /** |
| | | * 未认证提示信息 |
| | |
| | | */ |
| | | public static final Integer TASK_SORT_MIN_VALUE = 0; |
| | | |
| | | // AppId认证相关常量 |
| | | public static final String HEADER_APP_ID = "appId"; //X-App-Id |
| | | public static final String HEADER_APP_SECRET = "appSecret"; //X-App-Secret |
| | | public static final String HEADER_AUTHORIZATION = "authorization"; //Authorization |
| | | public static final String TOKEN_PREFIX = "Zoneyung"; |
| | | public static final String REQUEST_ATTR_APP_ID = "appId"; //request_app_id |
| | | public static final String REQUEST_ATTR_USER_ID = "appSecret"; //request_user_id |
| | | public static final String REQUEST_ATTR_APP_INFO = "APP_INFO"; |
| | | } |
| | |
| | | package com.vincent.rsf.openApi.entity.dto; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | |
| | | @ApiModelProperty("响应结果") |
| | | private Object data; |
| | | |
| | | /** |
| | | * 成功响应 |
| | | */ |
| | | public static CommonResponse ok() { |
| | | CommonResponse response = new CommonResponse(); |
| | | response.setCode(200); |
| | | response.setMsg("操作成功"); |
| | | JSONObject jsonObject = new JSONObject(); |
| | | jsonObject.put("result", "SUCCESS"); |
| | | response.setData(jsonObject); |
| | | return response; |
| | | } |
| | | |
| | | public static CommonResponse ok(Object data) { |
| | | CommonResponse response = new CommonResponse(); |
| | | response.setCode(200); |
| | | response.setMsg("操作成功"); |
| | | response.setData(data); |
| | | return response; |
| | | } |
| | | |
| | | /** |
| | | * 失败响应 |
| | | */ |
| | | public static CommonResponse error(String msg) { |
| | | CommonResponse response = new CommonResponse(); |
| | | response.setCode(500); |
| | | response.setMsg(msg); |
| | | return response; |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | import java.util.List; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "CheckOrder", description = "盘点单") |
| | | public class CheckOrder { |
| | | |
| | | // 盘点单号,唯一标识 |
| | | @NotNull |
| | | @JsonProperty("orderNo") |
| | | private String orderNo; |
| | | // 盘点单名称 |
| | | @JsonProperty("orderName") |
| | | private String orderName; |
| | | // 确认时间,秒级时间戳 |
| | | private Long checkTime; |
| | | // 单据类型,需ERP确定上报时需要使用哪个类型 |
| | | private String orderTypeId; |
| | | // 资产组织 |
| | | private String assetOrgId; |
| | | // 盘点方案,需ERP确定上报时使用哪个方案 |
| | | private String inventSchemeId; |
| | | // 确认结果 |
| | | private List<CheckResult> checkItems; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "CheckResult", description = "盘点结果") |
| | | public class CheckResult { |
| | | |
| | | // 仓库编码 |
| | | private String wareHouseId; |
| | | // 物料编码 |
| | | private String matNr; |
| | | // 库存数量 |
| | | private Double qty; |
| | | // 确认后库存数量,实际库存以WMS盘点为准,只保存不做库存数量调整 |
| | | private Double checkQty; |
| | | // 是否解冻,0 解冻;1 不解冻; |
| | | private Integer defrost; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "Customer", description = "客户信息同步") |
| | | public class Customer { |
| | | |
| | | // 客户编码,唯一标识 |
| | | @NotNull |
| | | @JsonProperty("customerId") |
| | | private String customerId; |
| | | // 客户名称 |
| | | @JsonProperty("customerName") |
| | | private String customerName; |
| | | // 客户昵称 |
| | | private String customerNickName; |
| | | // 客户分组,国内,国外 |
| | | private String customerGroup; |
| | | // 联系人 |
| | | private String contact; |
| | | // 联系电话 |
| | | private String telephone; |
| | | // 邮箱 |
| | | private String email; |
| | | // 地址 |
| | | private String address; |
| | | // 操作类型,1 新增;2 修改;3禁用;4 反禁用; |
| | | @JsonProperty("operateType") |
| | | private Integer operateType; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "InventoryDetails", description = "库存明细") |
| | | public class InventoryDetails { |
| | | |
| | | // 库位编码 |
| | | private String locId; |
| | | // 仓库编码 |
| | | private String wareHouseId; |
| | | // 仓库名称 |
| | | private String wareHouseName; |
| | | // 托盘码,如果一个托盘上备了2个工单号,则分为2条 |
| | | private String palletId; |
| | | // 物料编码 |
| | | private String matNr; |
| | | // 物料名称 |
| | | private String makTx; |
| | | // 规格 |
| | | private String spec; |
| | | // 数量,小数点最长6位 |
| | | private Double anfme; |
| | | // 单位 |
| | | private String unit; |
| | | // 库存状态,待ERP补充,示例:可用;冻结; |
| | | private String status; |
| | | // 绑定的订单类型,1 出库单;2 入库单;3 备料单;未绑定时为空 |
| | | private Integer orderType; |
| | | // 订单号、备料单号,未绑定时为空 |
| | | private String orderNo; |
| | | // 备料类型,为备料单时有,1 正常领料;2 生产补料; |
| | | private Integer prepareType; |
| | | // 计划跟踪号 |
| | | private String planNo; |
| | | // 批次号 |
| | | private String batch; |
| | | // 库存组织 |
| | | private String stockOrgId; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "InventoryQueryCondition", description = "库存查询条件") |
| | | public class InventoryQueryCondition { |
| | | |
| | | // 仓库编码 |
| | | private String wareHouseId; |
| | | // 物料编码 |
| | | private String matNr; |
| | | // 物料组 |
| | | private String matGroup; |
| | | // 库位编码 |
| | | private String locId; |
| | | // 订单号/工单号/MES工单号 |
| | | private String orderNo; |
| | | // 数计划跟踪号 |
| | | private String planNo; |
| | | // 批次号 |
| | | private String batch; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "InventorySummary", description = "库存汇总") |
| | | public class InventorySummary { |
| | | |
| | | // 仓库编码 |
| | | private String wareHouseId; |
| | | // 仓库名称 |
| | | private String wareHouseName; |
| | | // 物料编码 |
| | | private String matNr; |
| | | // 物料名称 |
| | | private String makTx; |
| | | // 规格 |
| | | private String spec; |
| | | // 数量,小数点最长6位 |
| | | private Double anfme; |
| | | // 单位 |
| | | private String unit; |
| | | // 库存组织 |
| | | private String stockOrgId; |
| | | // 批号 |
| | | private String batch; |
| | | // 计划跟踪号 |
| | | private String planNo; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | import java.util.List; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "MatPreparationOrder", description = "备料单") |
| | | public class MatPreparationOrder { |
| | | |
| | | // 备料单号,唯一标识 |
| | | @NotNull |
| | | @JsonProperty("orderNo") |
| | | private String orderNo; |
| | | // 备料单创建时间,秒级时间戳 |
| | | @JsonProperty("orderTime") |
| | | private Long orderTime; |
| | | // 备料类型,为备料单时有,1 正常领料;2 生产补料; |
| | | @JsonProperty("prepareType") |
| | | private Integer prepareType; |
| | | // 备料详情 |
| | | private List<MatPreparationOrderItem> orderItems; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "MatPreparationOrderItem", description = "备料单明细") |
| | | public class MatPreparationOrderItem { |
| | | |
| | | // 物料编码 |
| | | @NotNull |
| | | @JsonProperty("matNr") |
| | | private String matNr; |
| | | // 备料数量 |
| | | @JsonProperty("anfme") |
| | | private Double anfme; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "Material", description = "物料信息同步") |
| | | public class Material { |
| | | |
| | | // 物料编码,唯一标识 |
| | | @NotNull |
| | | @JsonProperty("matNr") |
| | | private String matNr; |
| | | // 物料名称 |
| | | @JsonProperty("makTx") |
| | | private String makTx; |
| | | // 物料分组编码 |
| | | @JsonProperty("groupId") |
| | | private String groupId; |
| | | // 物料分组 |
| | | @JsonProperty("groupName") |
| | | private String groupName; |
| | | // 型号 |
| | | private String model; |
| | | // 重量 |
| | | private Double weight; |
| | | // 颜色 |
| | | private String color; |
| | | // 尺寸 |
| | | private String size; |
| | | // 规格 |
| | | private String spec; |
| | | // 描述 |
| | | private String describe; |
| | | // 单位 |
| | | private String unit; |
| | | // 基本单位,银座有双单位,如长度和重量,可能需要转换 |
| | | private String baseUnit; |
| | | // 使用组织编码 |
| | | private String useOrgId; |
| | | // 使用组织名称 |
| | | private String useOrgName; |
| | | // 物料属性,外购等 |
| | | private String erpClsID; |
| | | // 操作类型,1 新增;2 修改;3禁用;4 反禁用; |
| | | @JsonProperty("operateType") |
| | | private Integer operateType; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | import java.util.List; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "Order", description = "入/出库通知单") |
| | | public class Order { |
| | | |
| | | // 入/出库订单号,唯一标识 |
| | | @NotNull |
| | | @JsonProperty("orderNo") |
| | | private String orderNo; |
| | | // 单据内码,唯一标识 |
| | | @JsonProperty("orderInternalCode") |
| | | private String orderInternalCode; |
| | | // 业务类型,示例: |
| | | // 入库:收料通知单(PUR_ReceiveBill)、采购入库单(STK_InStock)、退料申请单(PUR_MRAPP)、采购退料单(PUR_MRB)、 |
| | | // 退货通知单(SAL_RETURNNOTICE)、销售退货单(SAL_RETURNSTOCK)、生产退料单(PRD_ReturnMtrl)、生产入库单(PRD_INSTOCK)/生产汇报单(PRD_MORPT)、 |
| | | // 其他入库单(STK_MISCELLANEOUS) |
| | | // 出库:发货通知单(SAL_DELIVERYNOTICE)、销售出库单(SAL_OUTSTOCK)、出库申请单(STK_OutStockApply)、生产领料单(PRD_PickMtrl)、 |
| | | // 生产补料单(PRD_FeedMtrl)、其他出库单(STK_MisDelivery) |
| | | // 调拨:调拨申请单(STK_TRANSFERAPPLY)、直接调拨单(STK_TransferDirect) |
| | | private String wkType; |
| | | // 订单类型,1 出库单;2 入库单;3 调拨单; |
| | | private String type; |
| | | // 创建日期,时间戳,精确到秒 |
| | | private Long createTime; |
| | | // 业务日期,对账使用,时间戳,精确到秒 |
| | | private Long businessTime; |
| | | // 库存方向 |
| | | private String stockDirect; |
| | | // 订单明细 |
| | | private List<OrderItem> orderItems; |
| | | // 出入库接驳站点,出库时将物料出库后运输至该站点,入库时从该站点将物料运回库中 |
| | | private String stationId; |
| | | |
| | | // 客户编码 |
| | | private String customerId; |
| | | // 客户名称 |
| | | private String customerName; |
| | | // 供应商编码 |
| | | private String supplierId; |
| | | // 供应商名称 |
| | | private String supplierName; |
| | | // 收料/发货组织 |
| | | private String stockOrgId; |
| | | // 收料/发货组织名称 |
| | | private String stockOrgName; |
| | | // 采购组织 |
| | | private String purchaseOrgId; |
| | | // 采购组织名称 |
| | | private String purchaseOrgName; |
| | | // 采购员 |
| | | private String purchaseUserId; |
| | | // 采购员名称 |
| | | private String purchaseUserName; |
| | | // 生产组织 |
| | | private String prdOrgId; |
| | | // 生产组织名称 |
| | | private String prdOrgName; |
| | | // 销售组织 |
| | | private String saleOrgId; |
| | | // 销售组织名称 |
| | | private String saleOrgName; |
| | | // 销售员 |
| | | private String saleUserId; |
| | | // 销售员名称 |
| | | private String saleUserName; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "OrderItem", description = "入/出库通知单明细") |
| | | public class OrderItem { |
| | | |
| | | // 计划跟踪号 |
| | | @NotNull |
| | | @JsonProperty("planNo") |
| | | private String planNo; |
| | | // 行内码,唯一标识 |
| | | @JsonProperty("lineId") |
| | | private String lineId; |
| | | // 物料编码,唯一标识 |
| | | @NotNull |
| | | @JsonProperty("matNr") |
| | | private String matNr; |
| | | // 物料名称 |
| | | @JsonProperty("makTx") |
| | | private String makTx; |
| | | // 规格 |
| | | private String spec; |
| | | // 型号 |
| | | private String model; |
| | | // 数量 |
| | | private Double anfme; |
| | | // 批号 |
| | | private String batch; |
| | | // 单位 |
| | | private String unit; |
| | | // 基本单位 |
| | | private String baseUnitId; |
| | | // 计价单位 |
| | | private String priceUnitId; |
| | | // 建议目标仓库 |
| | | private String palletId; |
| | | // 调出仓 |
| | | private String targetWareHouseId; |
| | | // 业务日期,对账使用,时间戳,精确到秒 |
| | | private String sourceWareHouseId; |
| | | // 入库类型 |
| | | private String inStockType; |
| | | // 货主类型 |
| | | private String ownerTypeId; |
| | | // 货主 |
| | | private String ownerId; |
| | | // 货主名称 |
| | | private String ownerName; |
| | | // 保管者类型 |
| | | private String keeperTypeId; |
| | | // 保管者 |
| | | private String keeperId; |
| | | // 保管者名称 |
| | | private String keeperName; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "Pallet", description = "托盘信息") |
| | | public class Pallet { |
| | | |
| | | // 托盘条码 |
| | | @NotNull |
| | | @JsonProperty("BarCode") |
| | | private String barCode; |
| | | // 托盘编码 |
| | | @JsonProperty("PalletCode") |
| | | private String palletCode; |
| | | // 托盘名称 |
| | | @JsonProperty("PalletName") |
| | | private String palletName; |
| | | // 托盘类型编码 |
| | | @JsonProperty("PalletTypeCode") |
| | | private String palletTypeCode; |
| | | // 托盘类型 |
| | | @JsonProperty("PalletTypeName") |
| | | private String palletTypeName; |
| | | // 创建人 |
| | | @JsonProperty("CreatedBy") |
| | | private String createdBy; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @Data |
| | | public class SimpleProductionTask extends Task { |
| | | |
| | | // 领料内容 |
| | | private String matText; |
| | | // 领料区域,线下创建于MES同步 |
| | | private String regionId; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "Station", description = "站点信息") |
| | | public class Station { |
| | | |
| | | // 接驳口编码 |
| | | @NotNull |
| | | @JsonProperty("ConnPortCode") |
| | | private String connPortCode; |
| | | // 接驳口名称 |
| | | @JsonProperty("ConnPortName") |
| | | private String connPortName; |
| | | // 车间编码 |
| | | @JsonProperty("WorkshopCode") |
| | | private String workshopCode; |
| | | // 车间 |
| | | @JsonProperty("WorkshopName") |
| | | private String workshopName; |
| | | // 仓库编码 |
| | | @JsonProperty("ProductionLineCode") |
| | | private String productionLineCode; |
| | | // 仓库 |
| | | @JsonProperty("ProductionLineName") |
| | | private String productionLineName; |
| | | // 创建人 |
| | | @JsonProperty("CreatedBy") |
| | | private String createdBy; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "Supplier", description = "供应商信息同步") |
| | | public class Supplier { |
| | | |
| | | // 供应商编码,唯一标识 |
| | | @NotNull |
| | | @JsonProperty("supplierId") |
| | | private String supplierId; |
| | | // 供应商名称 |
| | | @JsonProperty("supplierName") |
| | | private String supplierName; |
| | | // 供应商昵称 |
| | | private String supplierNickName; |
| | | // 供应商分组,国内,国外 |
| | | private String supplierGroup; |
| | | // 联系人 |
| | | private String contact; |
| | | // 联系电话 |
| | | private String telephone; |
| | | // 邮箱 |
| | | private String email; |
| | | // 地址 |
| | | private String address; |
| | | // 操作类型,1 新增;2 修改;3禁用;4 反禁用; |
| | | @JsonProperty("operateType") |
| | | private Integer operateType; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "Task", description = "工作任务") |
| | | public class Task { |
| | | |
| | | // 任务号,唯一标识 |
| | | @NotNull |
| | | @JsonProperty("taskNo") |
| | | private String taskNo; |
| | | // 任务名称 |
| | | @JsonProperty("taskName") |
| | | private String taskName; |
| | | // 任务类型,1 入库;2 出库;3 转移; |
| | | private Integer taskType; |
| | | // 任务优先级,范围:1-100;数字越大,优先级越高,默认10; |
| | | private Integer initPriority; |
| | | // 起始站点编号 |
| | | private String startStationId; |
| | | // 目标站点编号 |
| | | private String endStationId; |
| | | // 托盘编号 |
| | | private String palletId; |
| | | // 托盘类型,枚举类型:1 托盘;2 料架;3 料箱;等枚举类型待后续对接 |
| | | private String palletType; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "TaskResult", description = "工作任务结果") |
| | | public class TaskResult { |
| | | |
| | | // 任务号,唯一标识 |
| | | @NotNull |
| | | @JsonProperty("TaskNo") |
| | | private String taskNo; |
| | | // 状态 |
| | | @JsonProperty("Status") |
| | | private String status; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.entity.phyz; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | @Data |
| | | @JsonIgnoreProperties(ignoreUnknown = true) |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "Warehouse", description = "仓库信息同步") |
| | | public class Warehouse { |
| | | |
| | | // 仓库编码,唯一标识 |
| | | @NotNull |
| | | @JsonProperty("wareHouseId") |
| | | private String wareHouseId; |
| | | // 仓库名称 |
| | | @JsonProperty("wareHouseName") |
| | | private String wareHouseName; |
| | | // 仓库位置 |
| | | private String address; |
| | | // 使用组织编码 |
| | | private String useOrgId; |
| | | // 使用组织名称 |
| | | private String useOrgName; |
| | | // 操作类型,1 新增;2 修改;3禁用;4 反禁用; |
| | | @JsonProperty("operateType") |
| | | private Integer operateType; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.vincent.rsf.openApi.entity.app.ApiForeignLog; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface ApiForeignLogMapper extends BaseMapper<ApiForeignLog> { |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.vincent.rsf.openApi.entity.app.ApiFunction; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | /** |
| | | * ApiFunction Mapper |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | @Mapper |
| | | public interface ApiFunctionMapper extends BaseMapper<ApiFunction> { |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.vincent.rsf.openApi.entity.app.ApiMap; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | /** |
| | | * ApiMap Mapper |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | @Mapper |
| | | public interface ApiMapMapper extends BaseMapper<ApiMap> { |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.vincent.rsf.openApi.entity.app.App; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | /** |
| | | * App Mapper |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | @Mapper |
| | | public interface AppMapper extends BaseMapper<App> { |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.security.filter; |
| | | |
| | | import com.vincent.rsf.openApi.entity.constant.Constants; |
| | | import com.vincent.rsf.openApi.security.service.AppAuthService; |
| | | import com.vincent.rsf.openApi.security.utils.TokenUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.tika.utils.StringUtils; |
| | | import org.springframework.core.annotation.Order; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.web.filter.OncePerRequestFilter; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.FilterChain; |
| | | import javax.servlet.ServletException; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.IOException; |
| | | import java.io.PrintWriter; |
| | | |
| | | /** |
| | | * AppId和AppSecret认证过滤器 |
| | | * |
| | | * 用于验证请求头中的AppId和AppSecret |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-05 |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | | @Order(1) |
| | | public class AppIdAuthenticationFilter extends OncePerRequestFilter { |
| | | |
| | | @Resource |
| | | private AppAuthService appAuthService; |
| | | |
| | | |
| | | @Override |
| | | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) |
| | | throws ServletException, IOException { |
| | | |
| | | String requestURI = request.getRequestURI(); |
| | | |
| | | // 检查是否为认证请求(如获取token) |
| | | if (isAuthRequest(requestURI)) { |
| | | // 对于认证请求,允许通过 |
| | | filterChain.doFilter(request, response); |
| | | return; |
| | | } |
| | | |
| | | String authHeader = request.getHeader(Constants.HEADER_AUTHORIZATION); |
| | | if (authHeader != null) { |
| | | String token = TokenUtils.extractTokenFromHeader(authHeader); |
| | | if (token != null && TokenUtils.validateTokenTime(token)) { |
| | | // Token时间认证成功,认证AppId和AppSecret |
| | | String tokenAppId = TokenUtils.getAppIdFromToken(token); |
| | | String tokenAppSecret = TokenUtils.getSecretFromToken(token); |
| | | if (StringUtils.isBlank(tokenAppId) || StringUtils.isBlank(tokenAppSecret) |
| | | || !appAuthService.validateApp(tokenAppId, tokenAppSecret)) { |
| | | log.warn("Token验证失败"); |
| | | sendErrorResponse(response, Integer.parseInt(Constants.UNAUTHENTICATED_CODE), "认证失败,请提供有效的Token"); |
| | | return; |
| | | } else { |
| | | request.setAttribute(Constants.REQUEST_ATTR_APP_ID, tokenAppId); |
| | | } |
| | | } else { |
| | | log.warn("Token验证失败或缺失"); |
| | | sendErrorResponse(response, Integer.parseInt(Constants.UNAUTHENTICATED_CODE), "认证失败,请提供有效的Token"); |
| | | return; |
| | | } |
| | | } else { |
| | | log.warn("缺少Token认证信息"); |
| | | sendErrorResponse(response, Integer.parseInt(Constants.UNAUTHENTICATED_CODE), "认证失败,请提供有效的Token"); |
| | | return; |
| | | } |
| | | |
| | | filterChain.doFilter(request, response); |
| | | } |
| | | |
| | | /** |
| | | * 发送错误响应 |
| | | * |
| | | * @param response HTTP响应 |
| | | * @param code 错误码 |
| | | * @param message 错误消息 |
| | | * @throws IOException |
| | | */ |
| | | private void sendErrorResponse(HttpServletResponse response, int code, String message) throws IOException { |
| | | response.setStatus(code); |
| | | response.setContentType("application/json;charset=UTF-8"); |
| | | PrintWriter writer = response.getWriter(); |
| | | writer.write("{\"code\": \"" + code + "\", \"msg\": \"" + message + "\", \"data\": null}"); |
| | | writer.flush(); |
| | | } |
| | | |
| | | /** |
| | | * 检查是否为认证请求(不需要认证的请求) |
| | | * |
| | | * @param requestURI 请求URI |
| | | * @return 是否为认证请求 |
| | | */ |
| | | private boolean isAuthRequest(String requestURI) { |
| | | return requestURI.contains("/getToken"); |
| | | // || requestURI.contains("/auth/validate") || |
| | | // requestURI.contains("/auth/login"); |
| | | } |
| | | |
| | | @Override |
| | | protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { |
| | | String requestURI = request.getRequestURI(); |
| | | |
| | | // 不过滤认证相关请求和公开接口 |
| | | return requestURI.contains("/auth/") || |
| | | requestURI.contains("/public/") || |
| | | requestURI.contains("/doc.html") || |
| | | requestURI.contains("/swagger") || |
| | | requestURI.contains("/webjars") || |
| | | requestURI.contains("/v2/api-docs") || |
| | | requestURI.contains("/v3/api-docs"); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.security.service; |
| | | |
| | | import com.vincent.rsf.openApi.entity.app.App; |
| | | import com.vincent.rsf.openApi.service.AppService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import javax.annotation.Resource; |
| | | |
| | | /** |
| | | * App认证服务 |
| | | * |
| | | * 提供AppId和AppSecret的验证功能 |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-05 |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | public class AppAuthService { |
| | | |
| | | @Resource |
| | | private AppService appService; |
| | | |
| | | /** |
| | | * 验证AppId和AppSecret |
| | | * |
| | | * @param appId 应用ID |
| | | * @param appSecret 应用密钥 |
| | | * @return 验证结果 |
| | | */ |
| | | public boolean validateApp(String appId, String appSecret) { |
| | | log.debug("验证AppId: {}, AppSecret: ****", appId); |
| | | |
| | | if (appId == null || appSecret == null) { |
| | | log.warn("AppId或AppSecret为空"); |
| | | return false; |
| | | } |
| | | |
| | | try { |
| | | // 从数据库查询应用信息 |
| | | App app = appService.getById(appId); |
| | | if (app == null) { |
| | | log.warn("未找到应用: {}", appId); |
| | | return false; |
| | | } |
| | | |
| | | // 检查应用是否启用 |
| | | if (app.getEnable() != 1) { |
| | | log.warn("应用未启用: {}", appId); |
| | | return false; |
| | | } |
| | | |
| | | // 验证密钥 |
| | | boolean isValid = appSecret.equals(app.getScrect()); |
| | | if (!isValid) { |
| | | log.warn("AppSecret验证失败: AppId={}", appId); |
| | | } else { |
| | | log.info("AppId认证成功: AppId={}", appId); |
| | | } |
| | | |
| | | return isValid; |
| | | } catch (Exception e) { |
| | | log.error("验证AppId和AppSecret时发生异常", e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | public boolean validateApp(String appId) { |
| | | App app = appService.getById(appId); |
| | | return app != null; |
| | | } |
| | | |
| | | /** |
| | | * 获取应用信息 |
| | | * |
| | | * @param appId 应用ID |
| | | * @return 应用信息 |
| | | */ |
| | | public App getAppInfo(String appId) { |
| | | if (appId == null) { |
| | | return null; |
| | | } |
| | | |
| | | try { |
| | | return appService.getById(appId); |
| | | } catch (Exception e) { |
| | | log.error("获取应用信息失败: {}", appId, e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 生成AppToken(可选功能) |
| | | * |
| | | * @param appId 应用ID |
| | | * @param appSecret 应用密钥 |
| | | * @return 生成的Token |
| | | */ |
| | | public String generateAppToken(String appId, String appSecret) { |
| | | // 这里可以实现基于AppId和AppSecret的Token生成逻辑 |
| | | // 例如使用JWT生成Token |
| | | if (validateApp(appId, appSecret)) { |
| | | // 生成Token的逻辑 |
| | | long timestamp = System.currentTimeMillis(); |
| | | String tokenData = appId + ":" + timestamp; |
| | | // TODO:这里可以使用更安全的加密算法 |
| | | return java.util.Base64.getEncoder().encodeToString(tokenData.getBytes()); |
| | | } |
| | | return null; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.security.utils; |
| | | |
| | | import com.vincent.rsf.openApi.entity.constant.Constants; |
| | | import com.vincent.rsf.openApi.entity.app.App; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | |
| | | /** |
| | | * 认证工具类 |
| | | * |
| | | * 提供认证相关的通用功能 |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-05 |
| | | */ |
| | | public class AuthUtils { |
| | | |
| | | /** |
| | | * 从请求中获取AppId |
| | | * |
| | | * @param request HTTP请求 |
| | | * @return AppId |
| | | */ |
| | | public static String getAppId(HttpServletRequest request) { |
| | | // 优先从请求属性中获取(认证过滤器设置的) |
| | | String appId = (String) request.getAttribute(Constants.REQUEST_ATTR_APP_ID); |
| | | if (appId != null) { |
| | | return appId; |
| | | } |
| | | |
| | | // 从请求头获取 |
| | | return request.getHeader(Constants.HEADER_APP_ID); |
| | | } |
| | | |
| | | /** |
| | | * 从请求中获取App信息 |
| | | * |
| | | * @param request HTTP请求 |
| | | * @return App信息 |
| | | */ |
| | | public static App getAppInfo(HttpServletRequest request) { |
| | | return (App) request.getAttribute(Constants.REQUEST_ATTR_APP_INFO); |
| | | } |
| | | |
| | | /** |
| | | * 检查请求是否已通过App认证 |
| | | * |
| | | * @param request HTTP请求 |
| | | * @return 是否已认证 |
| | | */ |
| | | public static boolean isAuthenticated(HttpServletRequest request) { |
| | | return getAppId(request) != null; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.security.utils; |
| | | |
| | | import com.vincent.rsf.openApi.entity.app.App; |
| | | import com.vincent.rsf.openApi.entity.constant.Constants; |
| | | import com.vincent.rsf.openApi.service.AppService; |
| | | import io.jsonwebtoken.*; |
| | | import io.jsonwebtoken.security.Keys; |
| | | import org.apache.tika.utils.StringUtils; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.crypto.SecretKey; |
| | | import java.util.Date; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * JWT Token工具类 |
| | | * 用于生成和验证JWT Token |
| | | */ |
| | | public class TokenUtils { |
| | | private static final Logger log = LoggerFactory.getLogger(TokenUtils.class); |
| | | |
| | | // 使用一个安全的密钥,实际应用中应该从配置文件读取 |
| | | private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256); |
| | | |
| | | // Token过期时间,默认1小时 |
| | | private static final long TOKEN_EXPIRATION = 60 * 60 * 1000L; // 24小时 |
| | | |
| | | @Resource |
| | | private AppService appService; |
| | | |
| | | /** |
| | | * 生成JWT Token |
| | | * |
| | | * @param claims Token中包含的声明信息 |
| | | * @return 生成的Token字符串 |
| | | */ |
| | | public static String generateToken(Map<String, Object> claims) { |
| | | long now = System.currentTimeMillis(); |
| | | Date expiration = new Date(now + TOKEN_EXPIRATION); |
| | | |
| | | return Jwts.builder() |
| | | .setClaims(claims) |
| | | .setExpiration(expiration) |
| | | .signWith(SECRET_KEY, SignatureAlgorithm.HS256) |
| | | .compact(); |
| | | } |
| | | |
| | | /** |
| | | * 生成带AppId的Token |
| | | * |
| | | * @param appId 应用ID |
| | | * @param appSecret 应用秘钥 |
| | | * @return 生成的Token字符串 |
| | | */ |
| | | public static String generateToken(String appId, String appSecret) { |
| | | Map<String, Object> claims = Map.of( |
| | | "appId", appId, |
| | | "appSecret", appSecret, |
| | | "created", System.currentTimeMillis() |
| | | ); |
| | | return generateToken(claims); |
| | | } |
| | | |
| | | /** |
| | | * 解析Token获取声明信息 |
| | | * |
| | | * @param token Token字符串 |
| | | * @return 声明信息 |
| | | */ |
| | | public static Claims parseToken(String token) { |
| | | try { |
| | | return Jwts.parserBuilder() |
| | | .setSigningKey(SECRET_KEY) |
| | | .build() |
| | | .parseClaimsJws(token) |
| | | .getBody(); |
| | | } catch (JwtException e) { |
| | | log.error("解析Token失败: {}", e.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 验证Token时间是否有效 |
| | | * |
| | | * @param token Token字符串 |
| | | * @return 时间是否有效 |
| | | */ |
| | | public static boolean validateTokenTime(String token) { |
| | | try { |
| | | Claims claims = parseToken(token); |
| | | if (claims == null) { |
| | | return false; |
| | | } |
| | | |
| | | // 检查Token是否过期 |
| | | Date expiration = claims.getExpiration(); |
| | | return expiration != null && expiration.after(new Date()); |
| | | } catch (JwtException e) { |
| | | log.error("验证Token失败: {}", e.getMessage()); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从Token中获取AppId |
| | | * |
| | | * @param token Token字符串 |
| | | * @return AppId |
| | | */ |
| | | public static String getAppIdFromToken(String token) { |
| | | Claims claims = parseToken(token); |
| | | if (claims != null) { |
| | | return (String) claims.get("appId"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 从Token中获取appSecret |
| | | * |
| | | * @param token Token字符串 |
| | | * @return appSecret |
| | | */ |
| | | public static String getSecretFromToken(String token) { |
| | | Claims claims = parseToken(token); |
| | | if (claims != null) { |
| | | return (String) claims.get("appSecret"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | // /** |
| | | // * 从Token中获取UserId |
| | | // * |
| | | // * @param token Token字符串 |
| | | // * @return UserId |
| | | // */ |
| | | // public static String getUserIdFromToken(String token) { |
| | | // Claims claims = parseToken(token); |
| | | // if (claims != null) { |
| | | // return (String) claims.get("userId"); |
| | | // } |
| | | // return null; |
| | | // } |
| | | |
| | | /** |
| | | * 从Authorization头中提取Token |
| | | * |
| | | * @param authHeader Authorization头内容 |
| | | * @return Token字符串(不包含Bearer前缀) |
| | | */ |
| | | public static String extractTokenFromHeader(String authHeader) { |
| | | if (authHeader != null && authHeader.startsWith(Constants.TOKEN_PREFIX)) { |
| | | return authHeader.substring(Constants.TOKEN_PREFIX.length()).trim(); |
| | | } |
| | | return null; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.vincent.rsf.openApi.entity.app.ApiForeignLog; |
| | | |
| | | public interface ApiForeignLogService extends IService<ApiForeignLog> { |
| | | |
| | | void saveAsync(ApiForeignLog log); |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.vincent.rsf.openApi.entity.app.ApiFunction; |
| | | |
| | | /** |
| | | * ApiFunction Service |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | public interface ApiFunctionService extends IService<ApiFunction> { |
| | | |
| | | /** |
| | | * 刷新功能缓存 |
| | | */ |
| | | void refreshCache(); |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.vincent.rsf.openApi.entity.app.ApiMap; |
| | | |
| | | /** |
| | | * ApiMap Service |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | public interface ApiMapService extends IService<ApiMap> { |
| | | |
| | | /** |
| | | * 刷新映射缓存 |
| | | */ |
| | | void refreshCache(); |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.vincent.rsf.openApi.entity.app.App; |
| | | |
| | | /** |
| | | * App Service |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | public interface AppService extends IService<App> { |
| | | |
| | | /** |
| | | * 刷新应用缓存 |
| | | */ |
| | | void refreshCache(); |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.vincent.rsf.openApi.entity.app.ApiForeignLog; |
| | | import com.vincent.rsf.openApi.mapper.ApiForeignLogMapper; |
| | | import com.vincent.rsf.openApi.service.ApiForeignLogService; |
| | | import org.springframework.scheduling.annotation.Async; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | @Service |
| | | public class ApiForeignLogServiceImpl extends ServiceImpl<ApiForeignLogMapper, ApiForeignLog> implements ApiForeignLogService { |
| | | |
| | | @Async |
| | | @Override |
| | | public void saveAsync(ApiForeignLog log) { |
| | | baseMapper.insert(log); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.vincent.rsf.openApi.entity.app.ApiFunction; |
| | | import com.vincent.rsf.openApi.mapper.ApiFunctionMapper; |
| | | import com.vincent.rsf.openApi.service.ApiFunctionService; |
| | | import com.vincent.rsf.openApi.utils.ParamsMapUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ApiFunction Service Implementation |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | public class ApiFunctionServiceImpl extends ServiceImpl<ApiFunctionMapper, ApiFunction> implements ApiFunctionService { |
| | | |
| | | @Override |
| | | public void refreshCache() { |
| | | log.info("开始刷新接口功能缓存..."); |
| | | List<ApiFunction> functions = this.list(); |
| | | ParamsMapUtils.FUNCTIONS = functions; |
| | | log.info("接口功能缓存刷新完成,共加载 {} 个功能", functions.size()); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.vincent.rsf.openApi.entity.app.ApiMap; |
| | | import com.vincent.rsf.openApi.mapper.ApiMapMapper; |
| | | import com.vincent.rsf.openApi.service.ApiMapService; |
| | | import com.vincent.rsf.openApi.utils.ParamsMapUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ApiMap Service Implementation |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | public class ApiMapServiceImpl extends ServiceImpl<ApiMapMapper, ApiMap> implements ApiMapService { |
| | | |
| | | @Override |
| | | public void refreshCache() { |
| | | log.info("开始刷新字段映射缓存..."); |
| | | List<ApiMap> maps = this.list(new LambdaQueryWrapper<ApiMap>() |
| | | .eq(ApiMap::getEnable, 1)); |
| | | ParamsMapUtils.ATTRIBUTE_MAPS = maps; |
| | | log.info("字段映射缓存刷新完成,共加载 {} 条映射规则", maps.size()); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.vincent.rsf.openApi.entity.app.App; |
| | | import com.vincent.rsf.openApi.mapper.AppMapper; |
| | | import com.vincent.rsf.openApi.service.AppService; |
| | | import com.vincent.rsf.openApi.utils.ParamsMapUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * App Service Implementation |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | public class AppServiceImpl extends ServiceImpl<AppMapper, App> implements AppService { |
| | | |
| | | @Override |
| | | public void refreshCache() { |
| | | log.info("开始刷新应用缓存..."); |
| | | List<App> apps = this.list(new LambdaQueryWrapper<App>() |
| | | .eq(App::getEnable, 1)); |
| | | ParamsMapUtils.APPS = apps; |
| | | log.info("应用缓存刷新完成,共加载 {} 个应用", apps.size()); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.utils; |
| | | |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * JSON属性名递归替换测试类 |
| | | * |
| | | * 演示如何使用 FuncMap 进行JSON属性名的递归替换 |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | public class JsonReplaceTest { |
| | | |
| | | public static void main(String[] args) { |
| | | System.out.println("========== JSON属性名递归替换测试 ==========\n"); |
| | | |
| | | // 测试1:简单对象 |
| | | testSimpleObject(); |
| | | |
| | | // 测试2:嵌套对象 |
| | | testNestedObject(); |
| | | |
| | | // 测试3:包含数组 |
| | | testWithArray(); |
| | | |
| | | // 测试4:复杂结构 |
| | | testComplexStructure(); |
| | | } |
| | | |
| | | /** |
| | | * 测试1:简单对象属性替换 |
| | | */ |
| | | private static void testSimpleObject() { |
| | | System.out.println("【测试1:简单对象】"); |
| | | |
| | | // 构造测试数据 |
| | | JSONObject data = new JSONObject(); |
| | | data.put("orderNumber", "PO001"); |
| | | data.put("orderQty", 100); |
| | | data.put("orderAmount", 5000.00); |
| | | |
| | | System.out.println("原始数据:" + data.toJSONString()); |
| | | |
| | | // 定义映射规则 |
| | | Map<String, String> rules = new HashMap<>(); |
| | | rules.put("orderNumber", "code"); |
| | | rules.put("orderQty", "qty"); |
| | | rules.put("orderAmount", "anfme"); |
| | | |
| | | // 执行替换 |
| | | JSONObject result = ParamsMapUtils.replaceJsonKeys(data, rules); |
| | | |
| | | System.out.println("替换结果:" + result.toJSONString()); |
| | | System.out.println(); |
| | | } |
| | | |
| | | /** |
| | | * 测试2:嵌套对象深度替换 |
| | | */ |
| | | private static void testNestedObject() { |
| | | System.out.println("【测试2:嵌套对象】"); |
| | | |
| | | // 构造嵌套数据 |
| | | JSONObject data = new JSONObject(); |
| | | data.put("orderNumber", "PO002"); |
| | | |
| | | JSONObject customer = new JSONObject(); |
| | | customer.put("customerName", "张三"); |
| | | customer.put("customerPhone", "13800138000"); |
| | | |
| | | JSONObject address = new JSONObject(); |
| | | address.put("cityName", "北京"); |
| | | address.put("streetName", "朝阳路88号"); |
| | | customer.put("address", address); |
| | | |
| | | data.put("customer", customer); |
| | | |
| | | System.out.println("原始数据:" + data.toJSONString()); |
| | | |
| | | // 定义映射规则(会应用到所有层级) |
| | | Map<String, String> rules = new HashMap<>(); |
| | | rules.put("orderNumber", "code"); |
| | | rules.put("customerName", "name"); |
| | | rules.put("customerPhone", "phone"); |
| | | rules.put("cityName", "city"); |
| | | rules.put("streetName", "street"); |
| | | |
| | | // 执行递归替换 |
| | | JSONObject result = ParamsMapUtils.replaceJsonKeys(data, rules); |
| | | |
| | | System.out.println("替换结果:" + result.toJSONString()); |
| | | System.out.println(); |
| | | } |
| | | |
| | | /** |
| | | * 测试3:包含数组的替换 |
| | | */ |
| | | private static void testWithArray() { |
| | | System.out.println("【测试3:包含数组】"); |
| | | |
| | | // 构造包含数组的数据 |
| | | JSONObject data = new JSONObject(); |
| | | data.put("orderNumber", "PO003"); |
| | | |
| | | JSONArray items = new JSONArray(); |
| | | for (int i = 1; i <= 3; i++) { |
| | | JSONObject item = new JSONObject(); |
| | | item.put("materialCode", "MAT00" + i); |
| | | item.put("materialName", "物料" + i); |
| | | item.put("itemQty", 10 * i); |
| | | items.add(item); |
| | | } |
| | | data.put("items", items); |
| | | |
| | | System.out.println("原始数据:" + data.toJSONString()); |
| | | |
| | | // 定义映射规则 |
| | | Map<String, String> rules = new HashMap<>(); |
| | | rules.put("orderNumber", "code"); |
| | | rules.put("materialCode", "matnr"); |
| | | rules.put("materialName", "maktx"); |
| | | rules.put("itemQty", "qty"); |
| | | |
| | | // 执行替换(包括数组中的所有对象) |
| | | JSONObject result = ParamsMapUtils.replaceJsonKeys(data, rules); |
| | | |
| | | System.out.println("替换结果:" + result.toJSONString()); |
| | | System.out.println(); |
| | | } |
| | | |
| | | /** |
| | | * 测试4:复杂结构(嵌套+数组+多层) |
| | | */ |
| | | private static void testComplexStructure() { |
| | | System.out.println("【测试4:复杂结构】"); |
| | | |
| | | // 构造复杂结构 |
| | | JSONObject data = new JSONObject(); |
| | | data.put("orderNumber", "PO004"); |
| | | data.put("orderStatus", "PENDING"); |
| | | |
| | | // 客户信息 |
| | | JSONObject customer = new JSONObject(); |
| | | customer.put("customerCode", "CUST001"); |
| | | customer.put("customerName", "北京公司"); |
| | | data.put("customer", customer); |
| | | |
| | | // 订单明细数组 |
| | | JSONArray items = new JSONArray(); |
| | | for (int i = 1; i <= 2; i++) { |
| | | JSONObject item = new JSONObject(); |
| | | item.put("itemNo", i); |
| | | item.put("materialCode", "MAT00" + i); |
| | | item.put("materialName", "物料" + i); |
| | | item.put("itemQty", 50 + i * 10); |
| | | |
| | | // 仓库信息(嵌套在明细中) |
| | | JSONObject warehouse = new JSONObject(); |
| | | warehouse.put("warehouseCode", "WH0" + i); |
| | | warehouse.put("locationCode", "LOC-A-0" + i); |
| | | item.put("warehouse", warehouse); |
| | | |
| | | items.add(item); |
| | | } |
| | | data.put("items", items); |
| | | |
| | | System.out.println("原始数据:" + data.toJSONString()); |
| | | |
| | | // 定义完整的映射规则 |
| | | Map<String, String> rules = new HashMap<>(); |
| | | rules.put("orderNumber", "code"); |
| | | rules.put("orderStatus", "exceStatus"); |
| | | rules.put("customerCode", "custCode"); |
| | | rules.put("customerName", "custName"); |
| | | rules.put("materialCode", "matnr"); |
| | | rules.put("materialName", "maktx"); |
| | | rules.put("itemQty", "qty"); |
| | | rules.put("warehouseCode", "whCode"); |
| | | rules.put("locationCode", "locCode"); |
| | | |
| | | // 执行递归替换 |
| | | JSONObject result = ParamsMapUtils.replaceJsonKeys(data, rules); |
| | | |
| | | System.out.println("替换结果:" + result.toJSONString()); |
| | | System.out.println(); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.openApi.utils; |
| | | |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.vincent.rsf.openApi.entity.app.ApiFunction; |
| | | import com.vincent.rsf.openApi.entity.app.ApiMap; |
| | | import com.vincent.rsf.openApi.entity.app.App; |
| | | import com.vincent.rsf.openApi.service.ApiMapService; |
| | | import com.vincent.rsf.openApi.service.ApiFunctionService; |
| | | import com.vincent.rsf.openApi.service.AppService; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.tika.utils.StringUtils; |
| | | import org.springframework.boot.context.event.ApplicationReadyEvent; |
| | | import org.springframework.context.event.EventListener; |
| | | import org.springframework.stereotype.Component; |
| | | import java.math.BigDecimal; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * 接口字段动态映射工具类 |
| | | * 支持从数据库加载映射配置,并提供字段转换功能 |
| | | * |
| | | * @author vincent |
| | | * @since 2026-01-04 |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | | @RequiredArgsConstructor |
| | | public class ParamsMapUtils { |
| | | |
| | | private final AppService appService; |
| | | private final ApiFunctionService functionService; |
| | | private final ApiMapService mapService; |
| | | |
| | | // 缓存到内存 |
| | | public static List<App> APPS = new ArrayList<>(); |
| | | public static List<ApiFunction> FUNCTIONS = new ArrayList<>(); |
| | | public static List<ApiMap> ATTRIBUTE_MAPS = new ArrayList<>(); |
| | | |
| | | /** |
| | | * 应用完全启动后自动加载配置 |
| | | * 使用 ApplicationReadyEvent 确保 Spring 容器完全初始化 |
| | | */ |
| | | @EventListener(ApplicationReadyEvent.class) |
| | | public void init() { |
| | | log.info("=============== 开始加载接口映射配置 ==============="); |
| | | try { |
| | | appService.refreshCache(); |
| | | functionService.refreshCache(); |
| | | mapService.refreshCache(); |
| | | |
| | | log.info("接口映射配置加载完成 - 应用数:{}, 功能数:{}, 映射规则数:{}", |
| | | APPS.size(), FUNCTIONS.size(), ATTRIBUTE_MAPS.size()); |
| | | } catch (Exception e) { |
| | | log.error("接口映射配置加载失败", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 手动刷新所有缓存 |
| | | */ |
| | | public void refreshAll() { |
| | | log.info("手动刷新所有映射缓存..."); |
| | | appService.refreshCache(); |
| | | functionService.refreshCache(); |
| | | mapService.refreshCache(); |
| | | } |
| | | |
| | | /** |
| | | * 执行字段映射转换 |
| | | * |
| | | * @param appId 应用ID |
| | | * @param funcId 功能ID |
| | | * @param params 原始参数 |
| | | * @return 转换后的参数 |
| | | */ |
| | | public static JSONObject apiMaps(String appId, String funcId, JSONObject params) { |
| | | if (params == null || params.isEmpty()) { |
| | | return params; |
| | | } |
| | | |
| | | // 1、获取映射表 |
| | | List<ApiMap> maps = ATTRIBUTE_MAPS.stream() |
| | | .filter(map -> map.getAppId().equals(appId) && map.getFuncId().equals(funcId)) |
| | | .toList(); |
| | | |
| | | if (maps.isEmpty()) { |
| | | log.debug("未找到映射配置 - appId:{}, funcId:{}", appId, funcId); |
| | | return params; |
| | | } |
| | | |
| | | // 2、构建映射规则Map |
| | | Map<String, String> mappingRules = new HashMap<>(); |
| | | for (ApiMap map : maps) { |
| | | String sourceAttribute = map.getSourceAttribute(); |
| | | String targetAttribute = map.getTargetAttribute(); |
| | | if (!StringUtils.isBlank(sourceAttribute) && !StringUtils.isBlank(targetAttribute)) { |
| | | mappingRules.put(sourceAttribute, targetAttribute); |
| | | } |
| | | } |
| | | |
| | | // 3、递归替换所有层级的属性名称 |
| | | JSONObject result = replaceKeysRecursive(params, mappingRules); |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 递归替换JSON所有层级的属性名称 |
| | | * 支持嵌套对象和数组的深度遍历 |
| | | * |
| | | * @param json 原始JSON对象 |
| | | * @param mappingRules 映射规则 (源字段名 -> 目标字段名) |
| | | * @return 替换后的JSON对象 |
| | | */ |
| | | public static JSONObject replaceKeysRecursive(JSONObject json, Map<String, String> mappingRules) { |
| | | if (json == null || json.isEmpty()) { |
| | | return json; |
| | | } |
| | | |
| | | JSONObject result = new JSONObject(); |
| | | |
| | | for (String key : json.keySet()) { |
| | | Object value = json.get(key); |
| | | |
| | | // 确定新的键名(如果有映射规则则使用映射后的名称) |
| | | String newKey = mappingRules.getOrDefault(key, key); |
| | | |
| | | if (value instanceof JSONObject) { |
| | | // 递归处理嵌套对象 |
| | | JSONObject nestedResult = replaceKeysRecursive((JSONObject) value, mappingRules); |
| | | result.put(newKey, nestedResult); |
| | | log.debug("替换对象字段: {} -> {}", key, newKey); |
| | | |
| | | } else if (value instanceof JSONArray) { |
| | | // 递归处理数组 |
| | | JSONArray arrayResult = replaceKeysInArray((JSONArray) value, mappingRules); |
| | | result.put(newKey, arrayResult); |
| | | log.debug("替换数组字段: {} -> {}", key, newKey); |
| | | |
| | | } else { |
| | | // 普通值直接赋值 |
| | | Object convertedValue = convertValue(value, newKey); |
| | | result.put(newKey, convertedValue); |
| | | if (!key.equals(newKey)) { |
| | | log.debug("替换字段: {} -> {} (值: {})", key, newKey, convertedValue); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 递归处理JSON数组中的所有元素 |
| | | * |
| | | * @param array 原始JSON数组 |
| | | * @param mappingRules 映射规则 |
| | | * @return 处理后的JSON数组 |
| | | */ |
| | | private static JSONArray replaceKeysInArray(JSONArray array, Map<String, String> mappingRules) { |
| | | if (array == null || array.isEmpty()) { |
| | | return array; |
| | | } |
| | | |
| | | JSONArray result = new JSONArray(); |
| | | |
| | | for (int i = 0; i < array.size(); i++) { |
| | | Object element = array.get(i); |
| | | |
| | | if (element instanceof JSONObject) { |
| | | // 数组元素是对象,递归处理 |
| | | JSONObject replacedObject = replaceKeysRecursive((JSONObject) element, mappingRules); |
| | | result.add(replacedObject); |
| | | |
| | | } else if (element instanceof JSONArray) { |
| | | // 数组元素是数组,递归处理 |
| | | JSONArray replacedArray = replaceKeysInArray((JSONArray) element, mappingRules); |
| | | result.add(replacedArray); |
| | | |
| | | } else { |
| | | // 基本类型,直接添加 |
| | | result.add(element); |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 通用的JSON属性名替换方法(对外提供) |
| | | * 可以直接传入映射规则进行替换 |
| | | * |
| | | * @param json 原始JSON对象 |
| | | * @param mappingRules 映射规则 Map<源字段名, 目标字段名> |
| | | * @return 替换后的JSON对象 |
| | | */ |
| | | public static JSONObject replaceJsonKeys(JSONObject json, Map<String, String> mappingRules) { |
| | | return replaceKeysRecursive(json, mappingRules); |
| | | } |
| | | |
| | | /** |
| | | * 通用的JSON数组属性名替换方法(对外提供) |
| | | * |
| | | * @param array 原始JSON数组 |
| | | * @param mappingRules 映射规则 Map<源字段名, 目标字段名> |
| | | * @return 替换后的JSON数组 |
| | | */ |
| | | public static JSONArray replaceJsonArrayKeys(JSONArray array, Map<String, String> mappingRules) { |
| | | return replaceKeysInArray(array, mappingRules); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 值类型转换(可扩展) |
| | | * |
| | | * @param value 原始值 |
| | | * @param targetField 目标字段名 |
| | | * @return 转换后的值 |
| | | */ |
| | | private static Object convertValue(Object value, String targetField) { |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | |
| | | // 这里可以根据需要添加更多类型转换逻辑 |
| | | // 例如:日期格式转换、数字精度转换等 |
| | | |
| | | // 示例:如果字段名包含amount、price等,转换为BigDecimal |
| | | String lowerField = targetField.toLowerCase(); |
| | | if ((lowerField.contains("amount") || lowerField.contains("price") |
| | | || lowerField.contains("qty")) && value instanceof String) { |
| | | try { |
| | | return new BigDecimal(value.toString()); |
| | | } catch (Exception e) { |
| | | log.warn("数字转换失败: {} -> {}", value, targetField); |
| | | return value; |
| | | } |
| | | } |
| | | |
| | | return value; |
| | | } |
| | | |
| | | /** |
| | | * 获取应用信息 |
| | | * |
| | | * @param appId 应用ID |
| | | * @return 应用信息 |
| | | */ |
| | | public static App getApp(String appId) { |
| | | return APPS.stream() |
| | | .filter(app -> app.getId().equals(appId)) |
| | | .findFirst() |
| | | .orElse(null); |
| | | } |
| | | |
| | | /** |
| | | * 获取功能信息 |
| | | * |
| | | * @param funcId 功能ID |
| | | * @return 功能信息 |
| | | */ |
| | | public static ApiFunction getFunction(String funcId) { |
| | | return FUNCTIONS.stream() |
| | | .filter(func -> func.getId().equals(funcId)) |
| | | .findFirst() |
| | | .orElse(null); |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 --> |
| | | <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true --> |
| | | <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 --> |
| | | <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> |
| | | <configuration scan="true" scanPeriod="10 seconds"> |
| | | |
| | | <!--<include resource="org/springframework/boot/logging/logback/base.xml" />--> |
| | | |
| | | <contextName>logback</contextName> |
| | | <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被添加到logger上下文中。定义变量后,可以使“${}”来使用变量。 --> |
| | | <!-- <property name="log.path" value="./emp-log"/>--> |
| | | <!-- 彩色日志 --> |
| | | <!-- 彩色日志依赖的渲染类 --> |
| | | <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/> |
| | | <conversionRule conversionWord="wex" |
| | | converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/> |
| | | <conversionRule conversionWord="wEx" |
| | | converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/> |
| | | <!-- 配置属性 彩色日志格式 --> |
| | | <property name="CONSOLE_LOG_PATTERN" |
| | | value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> |
| | | |
| | | |
| | | <!--输出到控制台的appender--> |
| | | <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> |
| | | <!--日志级别过滤器--> |
| | | <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> |
| | | <!--日志过滤级别--> |
| | | <level>debug</level> |
| | | </filter> |
| | | <encoder> |
| | | <Pattern>${CONSOLE_LOG_PATTERN}</Pattern> |
| | | <!-- 设置字符集 --> |
| | | <charset>UTF-8</charset> |
| | | </encoder> |
| | | </appender> |
| | | |
| | | |
| | | <!--log输出文件路径--> |
| | | <springProperty scope="context" name="log.path" source="logging.file.path"/> |
| | | <!--日志文件路径属性--> |
| | | <property name="logback.logdir" value="${log.path}"/> |
| | | |
| | | <!-- level为 DEBUG 日志 --> |
| | | <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
| | | <!-- 正在记录的日志文件的路径及文件名 --> |
| | | <file>${logback.logdir}/log_debug.log</file> |
| | | <!--日志文件输出格式--> |
| | | <encoder> |
| | | <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> |
| | | <charset>UTF-8</charset> <!-- 设置字符集 --> |
| | | </encoder> |
| | | <!-- 指定日志记录器的拆分归档策略,按日期,按大小记录 --> |
| | | <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> |
| | | <!-- 日志归档 --> |
| | | <fileNamePattern>${logback.logdir}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern> |
| | | <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> |
| | | <maxFileSize>100MB</maxFileSize> |
| | | </timeBasedFileNamingAndTriggeringPolicy> |
| | | <!--日志文件保留天数--> |
| | | <maxHistory>15</maxHistory> |
| | | </rollingPolicy> |
| | | <!--日志级过滤规则--> |
| | | <filter class="ch.qos.logback.classic.filter.LevelFilter"> |
| | | <!--日志过滤级别--> |
| | | <level>debug</level> |
| | | <!--超过过滤级别的策略--> |
| | | <onMatch>ACCEPT</onMatch> |
| | | <!--未超过过滤级别的策略--> |
| | | <onMismatch>DENY</onMismatch> |
| | | </filter> |
| | | </appender> |
| | | |
| | | <!-- level为 INFO 日志 --> |
| | | <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
| | | <!-- 正在记录的日志文件的路径及文件名 --> |
| | | <file>${logback.logdir}/log_info.log</file> |
| | | <!--日志文件输出格式--> |
| | | <encoder> |
| | | <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> |
| | | <charset>UTF-8</charset> |
| | | </encoder> |
| | | <!-- 指定日志记录器的拆分归档策略,按日期,按大小记录 --> |
| | | <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> |
| | | <!-- 每天日志归档路径以及格式 --> |
| | | <fileNamePattern>${logback.logdir}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern> |
| | | <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> |
| | | <maxFileSize>100MB</maxFileSize> |
| | | </timeBasedFileNamingAndTriggeringPolicy> |
| | | <!--日志文件保留天数--> |
| | | <maxHistory>15</maxHistory> |
| | | </rollingPolicy> |
| | | <!--日志级过滤规则--> |
| | | <filter class="ch.qos.logback.classic.filter.LevelFilter"> |
| | | <!--日志过滤级别--> |
| | | <level>info</level> |
| | | <!--超过过滤级别的策略--> |
| | | <onMatch>ACCEPT</onMatch> |
| | | <!--未超过过滤级别的策略--> |
| | | <onMismatch>DENY</onMismatch> |
| | | </filter> |
| | | </appender> |
| | | |
| | | <!-- level为 WARN 日志 --> |
| | | <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
| | | <!-- 正在记录的日志文件的路径及文件名 --> |
| | | <file>${logback.logdir}/log_warn.log</file> |
| | | <!--日志文件输出格式--> |
| | | <encoder> |
| | | <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> |
| | | <charset>UTF-8</charset> <!-- 此处设置字符集 --> |
| | | </encoder> |
| | | <!-- 指定日志记录器的拆分归档策略 --> |
| | | <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> |
| | | <fileNamePattern>${logback.logdir}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern> |
| | | <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> |
| | | <maxFileSize>100MB</maxFileSize> |
| | | </timeBasedFileNamingAndTriggeringPolicy> |
| | | <!--日志文件保留天数--> |
| | | <maxHistory>15</maxHistory> |
| | | </rollingPolicy> |
| | | <!--日志级过滤规则--> |
| | | <filter class="ch.qos.logback.classic.filter.LevelFilter"> |
| | | <!--日志过滤级别--> |
| | | <level>warn</level> |
| | | <!--超过过滤级别的策略--> |
| | | <onMatch>ACCEPT</onMatch> |
| | | <!--未超过过滤级别的策略--> |
| | | <onMismatch>DENY</onMismatch> |
| | | </filter> |
| | | </appender> |
| | | |
| | | |
| | | <!-- level为 ERROR 日志 --> |
| | | <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
| | | <!-- 正在记录的日志文件的路径及文件名 --> |
| | | <file>${logback.logdir}/log_error.log</file> |
| | | <!--日志文件输出格式--> |
| | | <encoder> |
| | | <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> |
| | | <charset>UTF-8</charset> <!-- 此处设置字符集 --> |
| | | </encoder> |
| | | <!--指定日志记录器的拆分归档策略,按日期,按大小记录 --> |
| | | <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> |
| | | <fileNamePattern>${logback.logdir}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern> |
| | | <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> |
| | | <maxFileSize>100MB</maxFileSize> |
| | | </timeBasedFileNamingAndTriggeringPolicy> |
| | | <!--日志文件保留天数--> |
| | | <maxHistory>15</maxHistory> |
| | | </rollingPolicy> |
| | | <!--日志级过滤规则--> |
| | | <filter class="ch.qos.logback.classic.filter.LevelFilter"> |
| | | <!--日志过滤级别--> |
| | | <level>ERROR</level> |
| | | <!--超过过滤级别的策略--> |
| | | <onMatch>ACCEPT</onMatch> |
| | | <!--未超过过滤级别的策略--> |
| | | <onMismatch>DENY</onMismatch> |
| | | </filter> |
| | | </appender> |
| | | |
| | | <!-- |
| | | <logger>用来设置某一个包或者具体的某一个类的日志打印级别、 |
| | | 以及指定<appender>。<logger>仅有一个name属性, |
| | | 一个可选的level和一个可选的addtivity属性。 |
| | | name:用来指定受此logger约束的某一个包或者具体的某一个类。 |
| | | level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF, |
| | | 还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。 |
| | | 如果未设置此属性,那么当前logger将会继承上级的级别。 |
| | | addtivity:是否向上级logger传递打印信息。默认是true。 |
| | | --> |
| | | <!--<logger name="org.springframework.web" level="info"/>--> |
| | | <!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>--> |
| | | <!-- |
| | | 使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作: |
| | | 第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息 |
| | | 第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别: |
| | | --> |
| | | |
| | | |
| | | <!-- |
| | | root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性 |
| | | level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF, |
| | | 不能设置为INHERITED或者同义词NULL。默认是DEBUG |
| | | 可以包含零个或多个元素,标识这个appender将会添加到这个logger。 |
| | | --> |
| | | |
| | | <!--开发环境:打印控制台--> |
| | | <springProfile name="dev"> |
| | | <logger name="com.lg.emp.controller" level="error"/> |
| | | </springProfile> |
| | | |
| | | <!--root logger 配置 --> |
| | | <root level="INFO"> |
| | | <appender-ref ref="CONSOLE"/> |
| | | <appender-ref ref="DEBUG_FILE"/> |
| | | <appender-ref ref="INFO_FILE"/> |
| | | <appender-ref ref="WARN_FILE"/> |
| | | <appender-ref ref="ERROR_FILE"/> |
| | | </root> |
| | | |
| | | <!--生产环境:输出到文件--> |
| | | <!--<springProfile name="pro">--> |
| | | <!--<root level="info">--> |
| | | <!--<appender-ref ref="CONSOLE" />--> |
| | | <!--<appender-ref ref="DEBUG_FILE" />--> |
| | | <!--<appender-ref ref="INFO_FILE" />--> |
| | | <!--<appender-ref ref="ERROR_FILE" />--> |
| | | <!--<appender-ref ref="WARN_FILE" />--> |
| | | <!--</root>--> |
| | | <!--</springProfile>--> |
| | | |
| | | </configuration> |
| | |
| | | // generator.username="sa"; |
| | | // generator.password="Zoneyung@zy56$"; |
| | | |
| | | generator.table = "sys_pda_role_menu"; |
| | | generator.tableDesc = "PDA权限"; |
| | | generator.packagePath = "com.vincent.rsf.server.manager"; |
| | | generator.table = "sys_matnr_role_menu"; |
| | | generator.tableDesc = "物料权限"; |
| | | generator.packagePath = "com.vincent.rsf.server.system"; |
| | | |
| | | generator.build(); |
| | | } |
| | |
| | | "sys_menu", |
| | | "sys_pda_role_menu", |
| | | "sys_menu_pda", |
| | | "sys_matnr_role_menu", |
| | | "man_loc_type_rela", |
| | | "man_qly_inspect_result", |
| | | "view_stock_manage", |
| | |
| | | @ApiModelProperty(value= "上级分类ID") |
| | | private Long parentId; |
| | | |
| | | private Integer sort; |
| | | |
| | | /** |
| | | * 状态 1: 正常 0: 冻结 |
| | | */ |
| New file |
| | |
| | | package com.vincent.rsf.server.system.controller; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.vincent.rsf.common.utils.Utils; |
| | | import com.vincent.rsf.framework.common.Cools; |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.server.common.annotation.OperationLog; |
| | | import com.vincent.rsf.server.manager.entity.MatnrGroup; |
| | | import com.vincent.rsf.server.manager.entity.MenuPda; |
| | | import com.vincent.rsf.server.manager.service.MatnrGroupService; |
| | | import com.vincent.rsf.server.system.controller.param.RoleScopeParam; |
| | | import com.vincent.rsf.server.system.entity.MatnrRoleMenu; |
| | | import com.vincent.rsf.server.system.entity.PdaRoleMenu; |
| | | import com.vincent.rsf.server.system.service.MatnrRoleMenuService; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.*; |
| | | |
| | | @RestController |
| | | public class MatnrRoleMenuController extends BaseController { |
| | | |
| | | @Autowired |
| | | private MatnrRoleMenuService matnrRoleMenuService; |
| | | |
| | | @Autowired |
| | | private MatnrGroupService matnrGroupService; |
| | | |
| | | @GetMapping("/roleMatnr/scope/list") |
| | | public R scopeList(@RequestParam Long roleId) { |
| | | return R.ok().add(matnrRoleMenuService.listStrictlyMenuByRoleId(roleId)); |
| | | } |
| | | |
| | | @PostMapping("/menuMatnrGroup/tree") |
| | | public R tree(@RequestBody Map<String, Object> map) { |
| | | List<MatnrGroup> menuList = matnrGroupService.list(new LambdaQueryWrapper<MatnrGroup>().orderByAsc(MatnrGroup::getSort)); |
| | | List<MatnrGroup> treeData = Utils.toTreeData(menuList, 0L, MatnrGroup::getParentId, MatnrGroup::getId, |
| | | MatnrGroup::setChildren); |
| | | if (!Cools.isEmpty(map.get("condition"))) { |
| | | Utils.treeRemove(treeData, String.valueOf(map.get("condition")), MatnrGroup::getName, MatnrGroup::getChildren); |
| | | Utils.treeRemove(treeData, String.valueOf(map.get("condition")), MatnrGroup::getName, MatnrGroup::getChildren); |
| | | } |
| | | return R.ok().add(treeData); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('system:role:update')") |
| | | @OperationLog("Assign Permissions") |
| | | @PostMapping("/roleMatnr/scope/update") |
| | | @Transactional |
| | | public R scopeUpdate(@RequestBody RoleScopeParam param) { |
| | | Long roleId = param.getId(); |
| | | List<Long> menuIds = new ArrayList<>(param.getMenuIds().getChecked()); |
| | | menuIds.addAll(param.getMenuIds().getHalfChecked()); |
| | | matnrRoleMenuService.remove(new LambdaQueryWrapper<MatnrRoleMenu>().eq(MatnrRoleMenu::getRoleId, roleId)); |
| | | for (Long menuId : menuIds) { |
| | | if (!matnrRoleMenuService.save(new MatnrRoleMenu(roleId, menuId))) { |
| | | throw new CoolException("Internal Server Error!"); |
| | | } |
| | | } |
| | | return R.ok("Assign Success"); |
| | | } |
| | | |
| | | |
| | | |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.system.entity; |
| | | |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableLogic; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | import com.vincent.rsf.framework.common.Cools; |
| | | import com.vincent.rsf.framework.common.SpringUtils; |
| | | import com.vincent.rsf.server.system.service.UserService; |
| | | import com.vincent.rsf.server.system.entity.User; |
| | | import java.io.Serializable; |
| | | import java.util.Date; |
| | | |
| | | @Data |
| | | @TableName("sys_matnr_role_menu") |
| | | public class MatnrRoleMenu implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @ApiModelProperty(value= "") |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | @ApiModelProperty(value= "") |
| | | private Long roleId; |
| | | |
| | | @ApiModelProperty(value= "") |
| | | private Long menuId; |
| | | |
| | | public MatnrRoleMenu() {} |
| | | |
| | | public MatnrRoleMenu(Long roleId,Long menuId) { |
| | | this.roleId = roleId; |
| | | this.menuId = menuId; |
| | | } |
| | | |
| | | // MatnrRoleMenu matnrRoleMenu = new MatnrRoleMenu( |
| | | // null, // [非空] |
| | | // null // [非空] |
| | | // ); |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.system.mapper; |
| | | |
| | | import com.vincent.rsf.server.system.entity.MatnrRoleMenu; |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.springframework.stereotype.Repository; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Mapper |
| | | @Repository |
| | | public interface MatnrRoleMenuMapper extends BaseMapper<MatnrRoleMenu> { |
| | | |
| | | List<Long> listStrictlyMenuByRoleId(Long roleId); |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.system.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.vincent.rsf.server.system.entity.MatnrRoleMenu; |
| | | |
| | | import java.util.List; |
| | | |
| | | public interface MatnrRoleMenuService extends IService<MatnrRoleMenu> { |
| | | |
| | | List<Long> listStrictlyMenuByRoleId(Long roleId); |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.system.service.impl; |
| | | |
| | | import com.vincent.rsf.server.system.mapper.MatnrRoleMenuMapper; |
| | | import com.vincent.rsf.server.system.entity.MatnrRoleMenu; |
| | | import com.vincent.rsf.server.system.service.MatnrRoleMenuService; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Service("matnrRoleMenuService") |
| | | public class MatnrRoleMenuServiceImpl extends ServiceImpl<MatnrRoleMenuMapper, MatnrRoleMenu> implements MatnrRoleMenuService { |
| | | @Override |
| | | public List<Long> listStrictlyMenuByRoleId(Long roleId) { |
| | | return baseMapper.listStrictlyMenuByRoleId(roleId); |
| | | } |
| | | } |
| New file |
| | |
| | | -- save matnrRoleMenu record |
| | | -- mysql |
| | | insert into `sys_menu` ( `name`, `parent_id`, `route`, `component`, `type`, `sort`, `tenant_id`, `status`) values ( 'menu.matnrRoleMenu', '0', '/system/matnrRoleMenu', 'matnrRoleMenu', '0' , '0', '1' , '1'); |
| | | |
| | | insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Query 物料权限', '', '1', 'system:matnrRoleMenu:list', '0', '1', '1'); |
| | | insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Create 物料权限', '', '1', 'system:matnrRoleMenu:save', '1', '1', '1'); |
| | | insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Update 物料权限', '', '1', 'system:matnrRoleMenu:update', '2', '1', '1'); |
| | | insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Delete 物料权限', '', '1', 'system:matnrRoleMenu:remove', '3', '1', '1'); |
| | | |
| | | -- locale menu name |
| | | matnrRoleMenu: 'MatnrRoleMenu', |
| | | |
| | | -- locale field |
| | | matnrRoleMenu: { |
| | | roleId: "roleId", |
| | | menuId: "menuId", |
| | | }, |
| | | |
| | | -- ResourceContent |
| | | import matnrRoleMenu from './matnrRoleMenu'; |
| | | |
| | | case 'matnrRoleMenu': |
| | | return matnrRoleMenu; |
| New file |
| | |
| | | <?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="com.vincent.rsf.server.system.mapper.MatnrRoleMenuMapper"> |
| | | |
| | | <select id="listStrictlyMenuByRoleId" resultType="java.lang.Long"> |
| | | select sm.id |
| | | from man_matnr_group sm |
| | | left join sys_matnr_role_menu srm on sm.id = srm.menu_id |
| | | where 1=1 |
| | | and sm.deleted = 0 |
| | | and srm.role_id = #{roleId} |
| | | <!-- |
| | | and sm.id not in ( |
| | | select sm.parent_id |
| | | from sys_menu sm |
| | | inner join sys_role_menu srm on sm.id = srm.menu_id |
| | | and srm.role_id = #{roleId} |
| | | ) |
| | | --> |
| | | order by sm.sort |
| | | </select> |
| | | </mapper> |