基于Springboot+SpringSecurity+Layui实现的进销存管理系统
1.项目介绍
进销存系统是什么?
进销存系统是为了对企业生产经营中进货、出货、批发销售、付款等全程进行(从接获订单合同开 始,进入物料采购、入库、领用到产品完工入库、交货、回收货款、支付原材料款等)跟踪(每一步都 提供详尽准确的数据)、管理(有效辅助企业解决业务管理、分销管理、存货管理、营销计划的执行和 监控、统计信息的收集等方面的业务问题)而设计的整套方案。
1.1 功能模块
1.1.1 基础资料
往来单位资料 货品资料 员工信息 仓库资料 计量单位 账户信息 公司信息 用户可以快速、直观地 查询所需要的数据资料。
1.1.2 系统管理
操作员管理 系统设置 数据初始化 系统管理是整个系统的门户,在系统的安全性上起到了不可估 量的作用。各种信息要求尽量全面详细,使管理变得更轻松更有效。
1.1.3 采购管理
新增采购订单 采购订单查询 新增采购单 采购单查询 采购退货 采购明细表 货品采购汇总表 供应商采购汇总表 采购订单完成情况 采购覆盖企业采购的各个环节。企业通过虚拟的在线货品目 录,迅速而实时的访问货品信息;通过价格和品质的比较,选定产品供应商。
1.1.4 销售管理
新增销售订单 销售订单查询 新增销售单 销售单查询 销售退货 销售明细表 货品销售汇总表 客户销售汇总表 销售订单完成情况 销售覆盖企业销售的各个环节。通过销售订单录入与变更,跟 踪管理商品销售情况;根据货品报价和销售数量自动开出销售发票,根据发货单产生结算凭证和 收货单。
1.1.5 库存管理
新增入库单 新增出库单 仓库调拨 库存盘点 期末提供了货品盘点、货品调价以及业务审核等 期末业务处理功能,业务期末结算为财务期末结算做了必要的铺垫作用。
1.1.6 财务管理
付款单 收款单 其他收入 其他支出 账户查询 应付账款表-单据 应付账款表-往来单位 应收账款 表-单据 应收账款表-往来单位
1.1.7 功能模块图
1.2 技术栈
2.数据库搭建
全局e-r图
3.功能实现
3.1 系统用户登录
3.1.1 系统用户登录流程简介
3.1.2 用户信息展示与更新
3.1.3 用户密码修改与退出
3.1.4 全局异常处理
项目开发中,程序不可避免会出现各种异常情况,此时需要开发者对于项目运行中可能出现的异常 进行捕捉并进行后续处理,通常,对于Web 项目异常处理分以下两种情况: 1. 页面视图异常 2. Ajax 请求响应异常这里,可以借助SpringMvc 提供的全局异常处理机制对项目出现的异常进行统 一处理即可。
3.1.5 非法资源访问控制
3.2 用户&角色管理
3.2.1 权限概念
RBAC 模型
RBAC是基于角色的访问控制( Role-Based Access Control ),在RBAC中,权限与角色相关联,用 户通过扮演适当的角色从而得到这些角色的权限。这样管理都是层级相互依赖的,权限赋予给角色,角 色又赋予用户,这样的权限设计很清楚,管理起来很方便。 RBAC授权实际上是 Who 、 What 、 How 三元组之间的关系,说白了就是谁登录了系统,对什么资源进行了怎样的操作!
3.2.2 数据库设计
实体关系映射图
3.2.3 核心表设计
3.2.4 核心模块
用户管理
-
用户基本信息维护(t_user)
-
角色分配(t_user_role)
角色管理
-
角色基本信息维护(t_role)
-
角色授权(t_user_role)
3.2.4.3资源管理
- 资源菜单信息维护(t_menu)
用户认证
- 你是谁?
- 用户登录操作
- 系统中你能做什么?
- 用户能够操作的资源有哪些?
- 查询用户扮演角色
- 查看角色操作的资源:根据权限表查询操作资源
角色鉴权 用户认证时已查询用户扮演的角色拥有的菜单资源权限,不过在鉴权模块关心的是用户操作的菜单资源是否被系统认可(就好比到游乐场买了张票 代表可以进园耍,但有些景点会另外收费,有没有玩儿的资格
-
提前购票
-
售票员检票通过方可进行下一步 检票过程实际就代表着权限检查的过程),这个认可过程称为鉴权操作
用户通过界面点击的每个菜单 按钮,后端均需要进行鉴权 判断角色是否存在操作该资源的权限!!!
3.2.5 引入SpringSecurity 权限框架
用户登录优化
java
@Bean
public UserDetailsService userDetailsService(){
return new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
// 根据用户名查询用户记录
User userDetails = (User) userService.findUserByUserName(username);
return userDetails;
}
};
生效SpringSecurity 登录配置
java
/**
* 放行静态web 资源
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/images/**",
"/css/**",
"/js/**",
"/lib/**",
"/error/**"
);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//禁用csrf
http.csrf().disable()
// 允许iframe 页面嵌套
.headers().frameOptions().disable()
.and()
.formLogin()
.usernameParameter("userName")
.passwordParameter("password")
.loginPage("/index")
.loginProcessingUrl("/login")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailedHandler)
.and()
.logout()
.logoutUrl("/signout")
.deleteCookies("JSESSIONID")
.logoutSuccessHandler(jxcLogoutSuccessHandler)
.and()
.authorizeRequests().antMatchers("/index","/login").permitAll()
.anyRequest().authenticated();
}
/**
配置SpringSecurity 密码加密Bean 对象
*/
@Bean
public PasswordEncoder encoder(){
return new BCryptPasswordEncoder();
}
/*
配置认证Service接口与密码加密实现类
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService()).passwordEncoder(encoder());
}
jxc-admin pom.xml 引入Spring Security 坐标
```xml
**用户登录成功,用户信息页面展示获取 **
html
${(Session.SPRING_SECURITY_CONTEXT.authentication.principal.username)!'lzj'}
使用SpringSecurity 对密码进行加密
登录用户信息获取:用户登录成功,Controller 层通过方法声明形参Principal principal来获取登录用户信息
java
@RequestMapping("setting")
public String setting(Principal principal, Model model){
String userName = principal.getName();
User user = userService.findUserByUserName(userName);
model.addAttribute("user",user);
return "user/setting";
}
密码修改对明文密码加密处理
使用SpringSecurity 框架实现用户登录时,主要通过PasswordEncoder 接口实现用户密码加密处理,核心实现类BCryptPasswordEncoder常用方法:
方法名 | 描述 |
---|---|
String encode(CharSequencerawPassword) | 字符串序列加密 |
boolean matches(CharSequence rawPassword, String encodedPassword) | 密码匹配,参数1为明文密码,参数2为加密密码,匹配成功返回true,反之false |
添加图片验证码
添加验证码基本属性配置
xml
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
java
@Configuration
@PropertySource(value = {"classpath:kaptcha.properties"})
public class CaptchaConfig {
@Value("${kaptcha.border}")
private String border;
@Value("${kaptcha.border.color}")
private String borderColor;
@Value("${kaptcha.textproducer.font.color}")
private String fontColor;
@Value("${kaptcha.image.width}")
private String imageWidth;
@Value("${kaptcha.image.height}")
private String imageHeight;
@Value("${kaptcha.session.key}")
private String sessionKey;
@Value("${kaptcha.textproducer.char.length}")
private String charLength;
@Value("${kaptcha.textproducer.font.names}")
private String fontNames;
@Value("${kaptcha.textproducer.font.size}")
private String fontSize;
/**
* 自定义 验证码生成器
* @return
*/
@Bean(name = "captchaProducer")
public DefaultKaptcha getKaptchaBean(){
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
//加载验证码配置
Properties properties = new Properties();
properties.setProperty("kaptcha.border", border;
properties.setProperty("kaptcha.border.color", borderColor
);
properties.setProperty("kaptcha.textproducer.font.color", fontColor);
properties.setProperty("kaptcha.image.width", imageWidth);
properties.setProperty("kaptcha.image.height", imageHeight);
properties.setProperty("kaptcha.session.key", sessionKey);
properties.setProperty("kaptcha.textproducer.char.length", charLength);
properties.setProperty("kaptcha.textproducer.font.names", fontNames)
properties.setProperty("kaptcha.textproducer.font.size",fontSize);
defaultKaptcha.setConfig(new Config(properties));
return defaultKaptcha;
}
}
控制器输出验证码
java
@RequestMapping(value="/image",method = RequestMethod.GET)
public void kaptcha(HttpSession session, HttpServletResponse response) throws
IOException {
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("image/jpeg");
//验证码文字
String capText = captchaProducer.createText();
//将验证码存到session
session.setAttribute("captcha_key",
new CaptchaImageModel(capText,2 * 60));
//将图片返回给前端
try(ServletOutputStream out = response.getOutputStream()){
BufferedImage bufferedImage = captchaProducer.createImage(capText); //
生成验证码图片
ImageIO.write(bufferedImage,"jpg",out);
out.flush();
}
添加验证码加油过滤器
```java @Component public class CaptchaCodeFilter extends OncePerRequestFilter { private static ObjectMapper objectMapper = new ObjectMapper(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 只有在登录请求时才有验证码校验 if(StringUtils.equals("/login",request.getRequestURI()) && StringUtils.equalsIgnoreCase(request.getMethod(),"post")){ try{ //验证谜底与用户输入是否匹配 this.validate(new ServletWebRequest(request)); }catch(AuthenticationException e){ response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString( RespBean.error("验证码错误"))); //catch异常后,之后的过滤器就不再执行了 return; } } filterChain.doFilter(request,response); } /* * 验证码 校验 * @param request * @throws ServletRequestBindingException / private void validate(ServletWebRequest request) throws ServletRequestBindingException { HttpSession session = request.getRequest().getSession(); String codeInRequest = ServletRequestUtils.getStringParameter( request.getRequest(),"captchaCode"); if(StringUtils.isEmpty(codeInRequest)){ throw new SessionAuthenticationException("验证码不能为空"); } // 获取session中的验证码 CaptchaImageModel codeInSession = (CaptchaImageModel) session.getAttribute("captcha_key"); if(Objects.isNull(codeInSession)) { throw new SessionAuthenticationException("验证码不存在"); } // 校验服务器session池中的验证码是否过期 if(codeInSession.isExpired()) { session.removeAttribute("captcha_key"); throw new SessionAuthenticationException("验证码已经过期"); } // 请求验证码校验 if(!StringUtils.equals(codeInSession.getCode(), codeInRequest)) { throw new SessionAuthenticationException("验证码不匹配"); } }
// 生效过滤器配置 http.csrf().disable() // 图片验证码 用户名密码登录过滤器前执行 .addFilterBefore(captchaCodeFilter, UsernamePasswordAuthenticationFilter.class) / ... / ```
SpringSecurity 实现7天免登陆
免登陆基本原理:
-
用户认证成功之后调用RemeberMeService根据用户名名生成Token由TokenRepository写入到数 据库,同时也将Token写入到浏览器的Cookie中
-
重启服务之后,用户再次登入系统会由RememberMeAuthenticationFilter拦截,从Cookie中读 取Token信息,与persistent_logins表匹配判断是否使用记住我功能。最中由UserDetailsService查 询用户信息
免登陆实现核心
创建 persistent_logins 表:
sql
CREATE TABLE `persistent_logins` (
`username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`series` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`token` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP,
PRIMARY KEY (`series`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
配置SecurityConfifig:
java
@Override
protected void configure(HttpSecurity http) throws Exception {
//禁用csrf
http.csrf().disable()
// 图片验证码 用户名密码登录过滤器前执行
.addFilterBefore(captchaCodeFilter,
UsernamePasswordAuthenticationFilter.class)
// 允许iframe 页面嵌套
.headers().frameOptions().disable()
.and()
.formLogin()
.usernameParameter("userName")
.passwordParameter("password")
.loginPage("/index")
.loginProcessingUrl("/login")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailedHandler)
.and()
.logout()
.logoutUrl("/signout")
.deleteCookies("JSESSIONID")
.logoutSuccessHandler(jxcLogoutSuccessHandler)
.and()
.rememberMe()
.rememberMeParameter("rememberMe")
//保存在浏览器端的cookie的名称,如果不设置默认也是remember-me
.rememberMeCookieName("remember-me-cookie")
//设置token的有效期,即多长时间内可以免除重复登录,单位是秒。
.tokenValiditySeconds(7 * 24 * 60 * 60)
//自定义
.tokenRepository(persistentTokenRepository())
.and()
.authorizeRequests().antMatchers("/index","/login").permitAll()
.anyRequest().authenticated();
}
/**
* 配置从数据库中获取token
* @return
*/
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
return tokenRepository;
}
3.2.6 用户基本信息管理
界面原型
用户主页列表查询流程
** 添加用户记录**
更新用户记录
添加|更新用户记录流程
删除用户记录
删除用户记录流程
3.2.7 角色基本信息管理
角色主页列表展示
添加|更新角色记录
删除角色记录
3.2.8 用户角色分配
添加用户记录
修改用户记录
用户角色分配实现流程
添加角色
更新角色
3.3 角色&资源管理
3.3.1 角色管理
界面
角色授权流程
权限回显
权限回显流程
3.3.2 系统鉴权
通过前面内容介绍,基于RBAC 实现已完成用户基本信息管理,用户角色分配,角色基本信息管理 以及角色授权与权限记录回显功能,接下来要处理的就是当用户通过扮演不同角色进入系统操作菜单资源时,系统如何控制用户操作菜单资源的权限问题即:系统要判断扮演不同角色的用户在操作相关菜单 项时判断当前用户是否存在这种权限,通常,对于系统权限判断分前端UI资源显示控制与后端资源访问 控制。
用户登录权限查询
java
@Bean
public UserDetailsService userDetailsService(){
return new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
User userDetails = (User) userService.findUserByUserName(username);
// 查询角色列表
List<String> roleNames = rbacService.findRolesByUserName(username);
//通过用户角色列表加载用户的资源权限
List<String> authorties =
rbacService.findAuthorityByRoleNames(roleNames);
//角色ROLE_前缀
roleNames = roleNames.stream()
.map(role -> "ROLE_" +role)
.collect(Collectors.toList());
authorties.addAll(roleNames);
userDetails.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(
String.join(",",authorties)
));
return userDetails;
}
};
控制器方法级别权限控制示例
java
/**
* 用户管理主页
* @return
*/
@RequestMapping("index")
@PreAuthorize("hasAnyAuthority('1010')")
public String index(){
return "user/user";
}
/**
* 用户列表展示
* @param userQuery
* @return
*/
@RequestMapping("list")
@ResponseBody
@PreAuthorize("hasAnyAuthority('101003')")
public Map<String,Object> userList(UserQuery userQuery){
return userService.userList(userQuery);
}
/**
* 添加 & 更新用户页
* @param id
* @param model
* @return
*/
@RequestMapping("addOrUpdateUserPage")
@PreAuthorize("hasAnyAuthority('101004','101005')")
public String addUserPage(Integer id, Model model){
if(null !=id){
model.addAttribute("user",userService.getById(id));
}
return "user/add_update"; }
前端 UI 显示控制
前端 UI 显示控制即完成的是用户通过扮演不同角色登录系统,系统根据不同角色情况对UI 界面展示进行动态控制处理。
admin 用户角色为管理员,登录系统拥有所有菜单权限
admin用户登录后系统设置显示用户、角色、菜单管理
jack 用户 角色为主管,系统设置分配用户 角色管理权限
jack 用户登录系统 显示用户管理 角色管理菜单
1. 标签坐标依赖引入
```xml
**2. ClassPathTldsLoader 加载器类 **
java
public class ClassPathTldsLoader {
@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;
* 指定路径,我们通过pom引入的security.tld 中存放标签
*/
private static final String SECURITY_TLD = "security.tld";
final private List<String> classPathTlds;
public ClassPathTldsLoader(String... classPathTlds) {
super();
if(classPathTlds == null || classPathTlds.length <= 0){
this.classPathTlds = Arrays.asList(SECURITY_TLD);
}else{
this.classPathTlds = Arrays.asList(classPathTlds);
}
}
@PostConstruct
public void loadClassPathTlds() {
freeMarkerConfigurer.getTaglibFactory().setClasspathTlds(classPathTlds);
}
3. SecurityConfifig 配置类生效标签加载器
java
/**
* 加载 ClassPathTldsLoader
* @return
*/
@Bean
@ConditionalOnMissingBean(ClassPathTldsLoader.class)
public ClassPathTldsLoader classPathTldsLoader(){
return new ClassPathTldsLoader();
}
4. 视图中标签使用
视图中标签变量声明 &使用
common.ftl
```html <#assign security=JspTaglibs["http://www.springframework.org/security/tags"]/>
main.ftl
<@security.authorize access="hasAnyAuthority('10')">
```
3.3.3 菜单管理
菜单主要列表展示
添加|更新菜单记录
删除菜单记录
删除菜单记录流程
3.4 供应商管理
-
供应商就是企业进货时提供商品的企业, 在往来单位资料建立时。系统选择这个往来单位是属于
-
供应商还是客户。 在做采购单的时候操作员选择往来单位,系统就会自动展示所有供应商的信息
3.4.1 供应商主页列表展示
供应商核心表结构
供应商列表实现核心流程
3.4.2 添加|更新供应商记录
添加供应商记录
更新供应商记录
添加|更新供应商记录流程
3.4.3 删除供应商记录
删除供应商流程
3.5 客户管理
客户管理即系统进行商品后续销售时,操作人员通常在选择售出商品的时,还要实时方便的查询购买商品客户记录,以便后期系统对商品购买客户记录,客户订单支付状态等进行统计处理操作。
3.5.1 客户管理主页列表展示
客户表结构
客户列表实现核心流程
客户列表查询实现流程参考供应商列表实现
3.5.2 添加|更新客户记录
3.5.3 客户记录删除
删除客户流程
3.6 商品管理
商品管理作为进销存系统的核心模块,系统采购、入库、销售出库、退货、订单等模块都离不开商品信息,模块主要包括商品进本信息管理、商品分类管理、商品单位管理等基本功能。
3.6.1 商品基本信息管理
商品主页列表展示
核心表结构
商品类别表
商品单位表
商品信息查询根据商品名、商品类别等多条件进行筛选查询
3.6.2 添加|更新商品记录
添加商品
更新商品
添加|更新商品记录流程
3.6.3 商品记录删除
3.6.4商品分类管理
商品分类管理主要对商品类别表记录进行管理,通过加入商品类别信息,方便对商品进行归类,检索以及已收商品归类统计等。
表结构
商品类别展示流程
商品列表展示 使用LayUI TreeTable组件,实现方式与菜单管理菜单列表展示相似,参考菜单管理列表展示实现。
商品类别添加
商品类别添加流程
商品类别添加流程类似菜单添加,参考菜单添加实现
商品类别删除
商品类别删除流程
3.6.5 商品期初库存
期初库存就是在一个库存会计时期开始时,可供使用或出售的存货(如:货品、物资或原料)的账面价值。
期初库存信息展示
期初库存列表展示流程
上图展示的表格数据分为两块内容:
-
左侧表数据展示库存量为0 即未设置商品库存值 对应商品列表
-
右侧数据为仓库商品库存量大于0的商品列表
所以在展示上述表格期初库存数据时,查询库存量>0 商品数据,然后对商品进行库存量、成本价格更新等基本设置。
添加商品到仓库
添加商品到仓库实现核心流程
更新商品库存|成本价 实现核心
商品期初库存删除
商品期初库存删除流程
3.7 商品进货
进货管理主要描述企业采购商品时对进货商品数据进行统一化的管理,对于商品销售、已售商品统计起关键作用,其中进货管理模块主要包含商品进货、退货、商品库存查询等基本功能.
进货入库主页
添加采购商品数据
已选进货商品列表信息
进货单表
进货单商品表
进货入库实现流程
进货单据查询
进货单据列表展示
进货单据商品列表
进货单据记录删除
删除进货单记录流程
3.8 商品退货
退货入库主页
退货出库商品选择
已选退货商品列表展示
退货单表
退货单商品表
退货商品出库流程
退货单据查询
退货单据查询流程
退货单据记录删除
删除退货单记录流程
当前库存查询
商品库存查询流程
3.9 商品销售
销售出库主页
销售出库商品选择
已选销售商品列表
销售单表
销售单商品表
销售出库实现流程
销售单据查询
销售单查询流程
销售单据记录删除
删除销售单记录流程
3.10 客户退货
客户退货主页
客户退货商品选择
已选退货商品列表
客户退货单表
客户退货商品表
客户退货实现流程
客户退货单据查询
销售单查询流程
客户退货单据删除
删除销售单记录流程
3.11 库存管理
3.11.1 商品报损
一般情形的报损商品,是指在销售过程中,因出现变质、过保质期、破碎、外包装破损(损坏、溢漏、严重变形)或部件缺损等品质问题,影响正常销售必须折价处理或废弃的商品.
商品报损主页
报损商品添加
已选报损商品列表
报损单表
报损商品表
供应商列表实现核心流程
3.11.2 商品报溢
商品实际数量比进销存管理软件里面统计记录的数量要多,需要将二者的数量调整为一致,就得使用到报溢单。商品报溢情况相对来说要少一些。造成商品多出来的原因一般是软件做单据的时候误操作,或者是实际管理中出现数错的情况.
3.11.3 库存报警
库存报警实现流程
3.11.4 报损报溢查询
报损报溢查询实现流程
3.12 供应商统计
3.13 客户统计
3.14 商品采购统计
3.15 销售统计
销售列表
日销售统计
月销售统计
参考文献
- 基于J2EE的库存管理系统的设计与实现(吉林大学·孟兴业)
- 基于J2EE的库存管理系统的设计与实现(吉林大学·孟兴业)
- 基于轻量级J2EE框架的进销存系统设计与实现(西安电子科技大学·刘靓)
- 基于微服务架构的进销存管理信息系统的研究(北京建筑大学·刘争光)
- 基于Java Swing的中小企业进销存系统的设计与实现(云南大学·王小超)
- SSH技术在供应商管理系统开发中的应用研究(上海交通大学·王垚力)
- 基于微服务架构的进销存管理信息系统的研究(北京建筑大学·刘争光)
- 基于Activiti工作流引擎的进销存管理系统的设计与实现(华中科技大学·袁康)
- 基于Struts+Spring+Hibernate架构的进销存管理系统的研究及实现(西南交通大学·陈洁琴)
- 基于Activiti工作流引擎的进销存管理系统的设计与实现(华中科技大学·袁康)
- 基于Spring的分销管理系统的研究与实现(国防科学技术大学·陈景燕)
- 基于CMMI规范和SSH架构的进销存管理系统(电子科技大学·陈丰平)
- 基于Spring框架的饮料公司进销存系统的设计与实现(吉林大学·张百壮)
- 基于JSP技术的仓库管理货物系统的设计与实现(吉林大学·徐炳轩)
- 基于.NET框架的进销存管理信息系统的设计与实现(电子科技大学·孙华俊)
本文内容包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主题。发布者:代码驿站 ,原文地址:https://m.bishedaima.com/yuanma/35462.html