Springboot MVC

金歌  论坛元老 | 2024-12-8 18:47:23 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1059|帖子 1059|积分 3177

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1. Springboot为MVC提供的主动配置

Spring Boot 为 Spring MVC 提供了主动配置,这在大多数应用步伐中都能很好地工作。除了已经实现了 Spring MVC 的默认功能外,主动配置还提供了以下特性:


  • 包罗 ContentNegotiatingViewResolver 和 BeanNameViewResolver 的 Bean。

    • ContentNegotiatingViewResolver 主动根据HTTP哀求头中Accept字段来选择合适的视图技术渲染响应。
    • BeanNameViewResolver的作用是根据视图名称找到视图View对象。​

  • 支持提供静态资源,包罗对 WebJars的支持。

    • 静态资源路径默认已经配置好了。默认会去static目次下找。

  • 主动注册 Converter、GenericConverter 和 Formatter 的 Bean。

    • Converter:转换器,做类型转换的,例如表单提交了用户数据,将表单数据转换成User对象。
    • Formatter:格式化器,做数据格式化的,例如将Java中的日期类型对象格式化为特定格式的日期字符串。大概将用户提交的日期字符串,转换为Java中的日期对象。

  • 支持 HttpMessageConverters。

    • 内置了很多的HTTP消息转换器。例如:MappingJackson2HttpMessageConverter可以将json转换成java对象,也可以将java对象转换为json字符串。

  • 主动注册 MessageCodesResolver。

    • SpringBoot会主动注册一个默认的消息代码解析器
    • 资助你在表单验证出错时生成一些特殊的代码。这些代码让你可以大概更精确地定位问题,并提供更友好的错误提示。

  • 静态 index.html 文件支持。

    • Spring Boot 会主动处理位于项目静态资源目次下的 index.html 文件,使其成为应用步伐的默认主页

  • 主动使用 ConfigurableWebBindingInitializer Bean。

    • 用它来指定默认使用哪个转换器,默认使用哪个格式化器。在这个类当中都已经配好了。

  • 假如您不想使用主动配置并希望完全控制 Spring MVC,可以添加您自己的带有 @EnableWebMvc注解的 @Configuration。
  • 假如您希望保存这些 Spring Boot MVC 定制化设置并举行更多的 MVC 定制化(如拦截器、格式化步伐、视图控制器等其他功能),可以添加您自己的类型为 WebMvcConfigurer 的 @Configuration 类。但不能使用​@EnableWebMvc注解​。

2. 关键的主动配置类:WebMvcAutoConfiguration

  1. public class WebMvcAutoConfiguration {
  2.   @Bean
  3.   @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
  4.   @ConditionalOnProperty(
  5.     prefix = "spring.mvc.hiddenmethod.filter",
  6.     name = {"enabled"}
  7.   )
  8.   // 这个过滤器是专门处理Rest请求的。GET POST PUT DELETE请求。
  9.   public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
  10.     return new OrderedHiddenHttpMethodFilter();
  11.   }
  12.   @Bean
  13.   @ConditionalOnMissingBean({FormContentFilter.class})
  14.   @ConditionalOnProperty(
  15.     prefix = "spring.mvc.formcontent.filter",
  16.     name = {"enabled"},
  17.     matchIfMissing = true
  18.   )
  19.   //OrderedFormContentFilter 是 Spring Boot 中用于处理 HTTP 请求的一个过滤器,特别是针对 PUT 和 DELETE 请求。
  20.   //这个过滤器的主要作用是在处理 PUT 和 DELETE 请求时,确保如果请求体中有表单格式的数据,这些数据会被正确解析并可用。
  21.   public OrderedFormContentFilter formContentFilter() {
  22.     return new OrderedFormContentFilter();
  23.   }
  24.     @Configuration(
  25.     proxyBeanMethods = false
  26.   )
  27.   @Import({EnableWebMvcConfiguration.class})
  28.   @EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
  29.   @Order(0)
  30.   // 关键方法: 进行了一系列的Spring MVC相关配置
  31.   public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {}
  32.   
  33.    @Configuration(
  34.     proxyBeanMethods = false
  35.   )
  36.   @EnableConfigurationProperties({WebProperties.class})
  37.   // 启动配置
  38.   public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {}
  39. }
复制代码
3. WebMvcAutoConfigurationAdapter :修改配置

1. 类实现的 WebMvcConfigurer接口

  1. public interface WebMvcConfigurer {
  2.     // 用于定制 Spring MVC 如何匹配请求路径到控制器
  3.     default void configurePathMatch(PathMatchConfigurer configurer) {}
  4.     // 用于定制 Spring MVC 的内容协商策略,以确定如何根据请求的内容类型来选择合适的处理方法或返回数据格式
  5.     default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}
  6.     // 用于定制 Spring MVC 处理异步请求的方式
  7.     default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
  8.     // 用于定制是否将某些静态资源请求转发WEB容器默认的Servlet处理
  9.     default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
  10.     // 用于定制 Spring MVC 解析视图的方式,以确定如何将控制器返回的视图名称转换为实际的视图资源。
  11.     default void configureViewResolvers(ViewResolverRegistry registry) {}
  12.     // 用于定制 Spring MVC 如何处理 HTTP 请求和响应的数据格式,包括 JSON、XML 等内容类型的转换
  13.     default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
  14.     // 用于定制 Spring MVC 如何处理控制器方法中发生的异常,并提供相应的错误处理逻辑。
  15.     default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
  16.     // 用于定制 Spring MVC 如何处理数据的格式化和解析,例如日期、数值等类型的对象的输入和输出格式。
  17.     default void addFormatters(FormatterRegistry registry) {}
  18.     // 用于定制 Spring MVC 如何使用拦截器来处理请求和响应,包括在请求进入控制器之前和之后执行特定的操作。
  19.     default void addInterceptors(InterceptorRegistry registry) {}
  20.     // 用于定制 Spring MVC 如何处理静态资源(如 CSS、JavaScript、图片等文件)的请求。
  21.     default void addResourceHandlers(ResourceHandlerRegistry registry) {}
  22.     // 用于定制 Spring MVC 如何处理跨域请求,确保应用程序可以正确地响应来自不同域名的 AJAX 请求或其他跨域请求。
  23.     default void addCorsMappings(CorsRegistry registry) {}
  24.     // 用于快速定义简单的 URL 到视图的映射,而无需编写完整的控制器类和方法。
  25.     default void addViewControllers(ViewControllerRegistry registry) {}
  26.     // 用于定制 Spring MVC 如何解析控制器方法中的参数,包括如何从请求中获取并转换参数值。
  27.     default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}
  28.     // 用于定制 Spring MVC 如何处理控制器方法的返回值,包括如何将返回值转换为实际的 HTTP 响应。
  29.     default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {}
  30.     // 用于定制 Spring MVC 如何处理 HTTP 请求和响应的数据格式,允许你添加或调整默认的消息转换器,以支持特定的数据格式。
  31.     default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
  32.     // 用于定制 Spring MVC 如何处理控制器方法中抛出的异常,允许你添加额外的异常处理逻辑。
  33.     default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
  34. }
复制代码
2. 配置类的配置属性:WebMvcProperties.class



  • 重要用于配置 Spring MVC 的相关举动,例如路径匹配、视图解析、静态资源处理等
  1. @ConfigurationProperties(
  2.   prefix = "spring.mvc"
  3. )
  4. public class WebMvcProperties {}
复制代码
3. 配置类的配置属性:WebProperties.class



  • 通常用于配置一些通用的 Web 层设置,如资源处理、安全性配置等。
  1. @ConfigurationProperties("spring.web")
  2. public class WebProperties {
复制代码
4. 静态资源配置:addResourceHandlers



  • addResourceHandlers方法:使用了方法重载,有3个addResourceHandlers方法
第一个addResourceHandlers方法:静态资源的目次
  1. public void addResourceHandlers(ResourceHandlerRegistry registry) {}
复制代码
此方法指定了静态资源路径:

  • 当哀求路径是/webjars/**,则会去classpath:/META-INF/resources/webjars/找。
  • 当哀求路径是/,则会去classpath:/META-INF/resources/;classpath:/resources/;classpath:/static/;classpath:/public/ 找。
第三个addResourceHandlers方法:静态资源的缓存配置
  1. private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) {
  2.         // 设置缓存的过期时间(如果没有指定单位,默认单位是秒)
  3.     registration.setCachePeriod(this.getSeconds(this.resourceProperties.getCache().getPeriod()));
  4.     //设置静态资源的 Cache-Control HTTP 响应头,告诉浏览器如何去缓存这些资源。
  5.     registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
  6.     // 设置静态资源在响应时,是否在响应头中添加资源的最后一次修改时间。
  7.     registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
  8.     this.customizeResourceHandlerRegistration(registration);
  9.   }
  10. }
复制代码
1. 配置文件修改静态资源



  • 静态资源缓存设置
  1. // 1. 缓存有效期
  2. spring.web.resources.cache.period=100
  3. // 2. 缓存控制(cachecontrol配置的话,period会失效)
  4. spring.web.resources.cache.cachecontrol.max-age=20
  5. // 3. 是否使用缓存的最后修改时间(默认是:使用)
  6. spring.web.resources.cache.use-last-modified=true
  7. // 4. 是否开启静态资源默认处理方式(默认是:开启)
  8. spring.web.resources.add-mappings=true
复制代码


  • 静态资源目次配置:
  1. // 1. 设置webjars静态资源的请求路径的前缀
  2. spring.mvc.webjars-path-pattern=/wjs/**
  3. // 2. 设置普通静态资源的请求路径的前缀
  4. spring.mvc.static-path-pattern=/static/**
  5. // 3. 修改静态资源存放位置
  6. // 但是 classpath:/META-INF/resources/ 静态资源不受此配置的影响
  7. spring.web.resources.static-locations=classpath:/static1/,classpath:/static2/
复制代码
2. 实现接口方式修改静态资源配置

  1. @Configuration
  2. public class WebConfig implements WebMvcConfigurer {
  3.   @Override
  4.   public void addResourceHandlers(ResourceHandlerRegistry registry){
  5.     registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
  6.   }
  7. }
复制代码
5. 拦截器配置

  1. @Configuration
  2. public class WebConfig implements WebMvcConfigurer {
  3.   @Override
  4.   public void addInterceptors(InterceptorRegistry registry) {
  5.     registry.addInterceptor(new HandlerInterceptor() {
  6.       @Override
  7.       public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  8.         System.out.println("拦截器:preHandle");
  9.         return true;
  10.       }
  11.       @Override
  12.       public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  13.         System.out.println("拦截器:postHandle");
  14.       }
  15.       @Override
  16.       public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  17.         System.out.println("拦截器:afterCompletion");
  18.       }
  19.     });
  20.   }
  21. }
复制代码
4. EnableWebMvcConfiguration :启用配置

  1. @Configuration(
  2.     proxyBeanMethods = false
  3.   )
  4.   @EnableConfigurationProperties({WebProperties.class})
  5.   public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {}
复制代码
1. 首页映射

  1.     @Bean
  2.     public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
  3.       return (WelcomePageHandlerMapping)this.createWelcomePageHandlerMapping(applicationContext, mvcConversionService, mvcResourceUrlProvider, WelcomePageHandlerMapping::new);
  4.     }
复制代码


  • 会在4个静态资源目次中查找index.html文件作为欢迎页
5. 异常处理



  • 以下代码得当前后端分离的项目,只要给前端返回错误信息,如何展示由前端决定
1. 局部异常处理 @ExceptionHandler



  • 只针对当前controller的异常举行处理
  1. import org.springframework.web.bind.annotation.ExceptionHandler;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.PathVariable;
  4. import org.springframework.web.bind.annotation.RestController;
  5. @RestController
  6. public class UserController {
  7.     @GetMapping("/resource/{id}")
  8.     public String getResource(@PathVariable Long id){
  9.         if(id == 1){
  10.             throw new IllegalArgumentException("无效ID:" + id);
  11.         }
  12.         return "ID = " + id;
  13.     }
  14.     @ExceptionHandler(IllegalArgumentException.class)
  15.     public String handler(IllegalArgumentException e){
  16.         return "错误信息:" + e.getMessage();
  17.     }
  18. }
复制代码
2. 全局异常处理:@ControllerAdvice + @ExceptionHandler

  1. @ControllerAdvice
  2. public class GlobalExceptionHandler {
  3.     @ExceptionHandler(IllegalArgumentException.class)
  4.     @ResponseBody
  5.     public String handler(IllegalArgumentException e){
  6.         return "错误信息:" + e.getMessage();
  7.     }
  8. }
复制代码
6. web服务器的配置类



  • web服务器的主动配置类:ServletWebServerFactoryAutoConfiguration
  • web服务器默认配置:ServerProperties.class
  1. @EnableConfigurationProperties({ServerProperties.class})
  2. public class ServletWebServerFactoryAutoConfiguration {}
复制代码
1. tomcat服务器常见配置

  1. # 这个参数决定了 Tomcat 在接收请求时,如果在指定的时间内没有收到完整的请求数据,将会关闭连接。这个超时时间是从客户端发送请求开始计算的。
  2. # 防止长时间占用资源:如果客户端发送请求后,长时间没有发送完所有数据,Tomcat 会在这个超时时间内关闭连接,从而释放资源,避免资源被长时间占用。
  3. server.tomcat.connection-timeout=20000
  4. # 设置 Tomcat 服务器处理请求的最大线程数为 200。
  5. # 如果超过这个数量的请求同时到达,Tomcat 会将多余的请求放入一个等待队列中。
  6. # 如果等待队列也满了(由 server.tomcat.accept-count 配置),新的请求将被拒绝,通常会返回一个“503 Service Unavailable”错误。
  7. server.tomcat.max-threads=200
  8. # 用来设置等待队列的最大容量
  9. server.tomcat.accept-count=100
  10. # 设置 Tomcat 服务器在空闲时至少保持 10 个线程处于活动状态,以便快速响应新的请求。
  11. server.tomcat.min-spare-threads=10
  12. # 允许 Tomcat 服务器在关闭后立即重新绑定相同的地址和端口,即使该端口还在 TIME_WAIT 状态
  13. # 当一个网络连接关闭时,操作系统会将该连接的端口保持在 TIME_WAIT 状态一段时间(通常是 2-4 分钟),以确保所有未完成的数据包都能被正确处理。在这段时间内,该端口不能被其他进程绑定。
  14. server.tomcat.address-reuse-enabled=true
  15. # 设置 Tomcat 服务器绑定到所有可用的网络接口,使其可以从任何网络地址访问。
  16. server.tomcat.bind-address=0.0.0.0
  17. # 设置 Tomcat 服务器使用 HTTP/1.1 协议处理请求。
  18. server.tomcat.protocol=HTTP/1.1
  19. # 设置 Tomcat 服务器的会话(session)超时时间为 30 分钟。具体来说,如果用户在 30 分钟内没有与应用进行任何交互,其会话将被自动注销。
  20. server.tomcat.session-timeout=30
  21. # 设置 Tomcat 服务器的静态资源缓存时间为 3600 秒(即 1 小时),这意味着浏览器会在 1 小时内缓存这些静态资源,减少重复请求。
  22. server.tomcat.resource-cache-period=3600
  23. # 解决get请求乱码。对请求行url进行编码。
  24. server.tomcat.uri-encoding=UTF-8
  25. # 设置 Tomcat 服务器的基础目录为当前工作目录(. 表示当前目录)。这个配置指定了 Tomcat 服务器的工作目录,包括日志文件、临时文件和其他运行时生成的文件的存放位置。 生产环境中可能需要重新配置。
  26. server.tomcat.basedir=.
复制代码
7. PageHelper



  • 引入依赖
  1. <dependency>
  2.     <groupId>com.github.pagehelper</groupId>
  3.     <artifactId>pagehelper-spring-boot-starter</artifactId>
  4.     <version>2.1.0</version>
  5. </dependency>
复制代码


  • 编写代码
  1. @RestController
  2. public class VipController {
  3.     @Autowired
  4.     private VipService vipService;
  5.    
  6.     @GetMapping("/list/{pageNo}")
  7.     // 注意方法的返回值
  8.     public PageInfo<Vip> list(@PathVariable("pageNo") Integer pageNo) {
  9.         // 1.设置当前页码和每页显示的记录条数
  10.         PageHelper.startPage(pageNo, Constant.PAGE_SIZE);
  11.         // 2.获取数据(PageHelper会自动给SQL语句添加limit)
  12.         List<Vip> vips = vipService.findAll();
  13.         // 3.将分页数据封装到PageInfo
  14.         PageInfo<Vip> vipPageInfo = new PageInfo<>(vips);
  15.         return vipPageInfo;
  16.     }
  17. }
复制代码
8. R

  1. package org.example.learn.enums;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Getter;
  4. import lombok.NoArgsConstructor;
  5. import lombok.Setter;
  6. @AllArgsConstructor
  7. @NoArgsConstructor
  8. public enum CodeEnum {
  9.   OK (200, "成功"),
  10.   FAIL(500, "失败"),
  11.   NOT_FOUND(404, "未找到资源"),
  12.   UNAUTHORIZED(401, "未授权"),
  13.   FORBIDDEN(403, "禁止访问"),
  14.   BAD_REQUEST(400, "请求错误"),
  15.   INTERNAL_SERVER_ERROR(500, "服务器错误"),
  16.   SERVICE_UNAVAILABLE(503, "服务不可用"),
  17.   GATEWAY_TIMEOUT(504, "网关超时"),
  18.   INTERNAL_ERROR(500, "内部服务器错误"),
  19.   MODIFICATION_FAILED(400, "修改失败"),
  20.   DELETE_FAILED(400, "删除失败"),
  21.   ADD_FAILED(400, "添加失败");
  22.   @Setter
  23.   @Getter
  24.   private int code;
  25.   @Setter
  26.   @Getter
  27.   private String message;
  28.   
  29. }
复制代码
  1. package org.example.learn.result;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Builder;
  4. import lombok.Data;
  5. import lombok.NoArgsConstructor;
  6. import org.example.learn.enums.CodeEnum;
  7. @Data
  8. @Builder
  9. @AllArgsConstructor
  10. @NoArgsConstructor
  11. public class R<T> {
  12.   private Integer code;
  13.   private String message;
  14.   private T data;
  15.   public static R OK() {
  16.     return R.builder().code(CodeEnum.OK.getCode()).message(CodeEnum.OK.getMessage()).build();
  17.   }
  18.   public static <T> R<T> OK(T data) {
  19.     return R.<T>builder().code(CodeEnum.OK.getCode()).message(CodeEnum.OK.getMessage()).data(data).build();
  20.   }
  21.   public static <T> R<T> FAIL() {
  22.     return R.<T>builder().code(CodeEnum.FAIL.getCode()).message(CodeEnum.FAIL.getMessage()).build();
  23.   }
  24.   public static <T> R<T> FAIL(CodeEnum codeEnum){
  25.     return R.<T>builder().code(codeEnum.getCode()).message(codeEnum.getMessage()).build();
  26.   }
  27. }
复制代码
  1.   @GetMapping(value = "/all/{pageNo}")
  2.   public R<PageInfo<Vip>> getAll(@PathVariable int pageNo) {
  3.     PageHelper.startPage(pageNo,3);
  4.     List<Vip> all = vipService.getAll();
  5.     PageInfo<Vip> vipPageinfo = new PageInfo<>(all);
  6.     return R.OK(vipPageinfo);
  7.   }
  8.   @GetMapping(value = "/fail")
  9.   public R<String> fail() {
  10.     return R.FAIL(CodeEnum.INTERNAL_ERROR);
  11.   }
复制代码
9. logger



  • springboot集成logback日记框架,且日记级别默认为info
  • 使用lombok的@Slf4j注解简化日记输出
1. 使用

  1. @Slf4j
  2. @SpringBootApplication
  3. public class Learn10LogApplication {
  4.   public static void main(String[] args) {
  5.     SpringApplication.run(Learn10LogApplication.class, args);
  6.     log.trace("main------trace级别日志");
  7.     log.debug("main------debug级别日志");
  8.     log.info("main------info级别日志");
  9.     log.warn("main------warn级别日志");
  10.     log.error("main------error级别日志");
  11.   }
  12. }
复制代码
2. 调整日记级别及颗粒度

  1. # springboot默认的级别是info
  2. # 设置全局的日志级别是error
  3. logging.level.root=error
  4. # 指定包名调整日志级别
  5. logging.level.org.example.learn.controller=info
  6. # 指定类名调整日志级别,不能是接口
  7. logging.level.org.example.learn.service.impl.UserServiceImpl=trace
复制代码


  • 指定对数据库操纵的日记级别,需要对mapper接口的包名指定日记级别
  1. logging.level.org.example.learn.mapper=DEBUG
复制代码
3. 指定日记的输出路径和日记文件名



  • 指定日记的输出路径和日记的文件名不能同时使用,假如同时使用,则指定路径无效
  1. # 指定日志的输出路径和日志的文件名不能同时使用,如果同时使用,则指定路径无效
  2. # 日志输出到文件,指定文件路径为当前工程根目录下的log目录
  3. logging.file.path=./log/
  4. # 日志输出到文件,指定日志文件的文件名,路径是当前项目的根路径
  5. logging.file.name=my.log
复制代码
4. 滚动日记

  1. # 日志文件达到多大时进行归档
  2. logging.logback.rollingpolicy.max-file-size=100MB
  3. # 所有的归档日志文件总共达到多大时进行删除,默认是0B表示不删除
  4. logging.logback.rollingpolicy.total-size-cap=50GB
  5. # 归档日志文件最多保留几天
  6. logging.logback.rollingpolicy.max-history=60
  7. # 启动项目时是否要清理归档日志文件
  8. logging.logback.rollingpolicy.clean-history-on-start=false
  9. # 归档日志文件名的格式
  10. logging.logback.rollingpolicy.file-name-pattern=${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz
复制代码
10. 事务

在service的类大概方法上使用注解
  1. @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
复制代码


  • rollbackFor = Exception.class 表示当抛出任何类型的 Exception 时,都会回滚事务。
  • propagation = Propagation.REQUIRED 表示假如当前存在事务,则参加该事务;假如当前没有事务,则创建一个新的事务。这是默认的流传举动。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

金歌

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表