Skip to content
This repository has been archived by the owner on May 10, 2023. It is now read-only.

TAKETODAY/today-web

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

moved to https://github.com/TAKETODAY/today-infrastructure/tree/master/today-web

TODAY Web

🍎 A Java library for building web applications

Java8 GPLv3 Author Codacy Badge Java CI

背景

先不看

本人从2016年大一开始学Java,准确的说是高三最后的几周开始的. 果然兴趣是最好的老师, 在大一下学期自己独自一人从前端到后台写了我的个人网站:TODAY BLOG 。 从注册域名到备案再到网站成功上线,我遇到过的困难数不计其数。因为感兴趣所以我坚持了下来。第一个版本使用的纯Servlet写的。 后来了解到Java有很多开源框架可以简化我的开发。于是又投入到新一轮的学习之中...... 学了Struts2后自己学着写了一个小框架:TODAY Web 1.0 ,几百行搞定从解析xml定义的action到处理对应的请求。学了Spring MVC后,我写了此项目:TODAY Web 2.0

安装

<dependency>
    <groupId>cn.taketoday</groupId>
    <artifactId>today-web</artifactId>
    <version>3.0.2.RELEASE</version>
</dependency>

案例

文档

使用说明

函数式路由

@Component
@ResponseBody
public class FunctionController {

    public String function(RequestContext request) {
        return "body:" + request.method() + " requestURI -> " + request.requestURI();
    }

    public String test(RequestContext request) {
        return "body:" + request.method() + " test -> " + request.requestURI();
    }

    public void script(RequestContext request) throws IOException {

        ModelAndView modelAndView = new ModelAndView();
        request.modelAndView(modelAndView);

        modelAndView.setContentType("text/html;charset=UTF-8");
        modelAndView.setView(new StringBuilder("<script>alert('HELLO, 你好 script');</script>"));
    }
}

@Configuration
//@EnableDefaultMybatis
//@EnableRedissonCaching
public class WebMvcConfig implements WebMvcConfiguration {

    @Autowired
    private FunctionController functionController;

    @Override
    public void configureFunctionHandler(FunctionHandlerRegistry registry) {

        registry.get("/function", functionController::function);
        registry.get("/function/test", functionController::test);
        registry.get("/function/script", functionController::script);

        registry.get("/function/error/500", (context) -> {
            context.sendError(500);
        });
    }
}

注解路由

//@Controller
@RestController
@RequestMapping("/users")
public class UserController {
    

    @GET("index")
    @POST("post")
    @PUT("articles/{id}")
    ......
    @RequestMapping("/users/{id}")
    @RequestMapping(value = "/**", method = {RequestMethod.GET})
    @RequestMapping(value = "/*.html", method = {RequestMethod.GET})
    @RequestMapping(value = {"/index.action", "/index.do", "/index"}, method = RequestMethod.GET)
    @Interceptor({LoginInterceptor.class, ...})
    public (String|List<?>|Set<?>|Map<?>|void|File|Image|...) \\w+ (request, request, session,servletContext, str, int, long , byte, short, boolean, @Session("loginUser"), @Header("User-Agent"), @Cookie("JSESSIONID"), @PathVariable("id"), @RequestBody("users"), @Multipart("uploadFiles") MultipartFile[]) {
        service...
        return </>;
    }
}

ViewController

@Configuration
public class WebMvcConfig implements WebMvcConfiguration {

    @Override
    public void configureViewController(ViewControllerHandlerRegistry registry) {
        registry.addViewController("/github", "redirect:https://github.com");
        registry.addRedirectViewController("/login.do", "/login");
        registry.addViewController("/login.action")
                .setAssetsPath("redirect:/login");
    }
}

静态资源

@Singleton
@Profile("dev")
public ResourceHandlerRegistry devRsourceMappingRegistry(@Env("site.uploadPath") String upload,
                                                         @Env("site.assetsPath") String assetsPath) //
{
    final ResourceHandlerRegistry registry = new ResourceHandlerRegistry();

    registry.addResourceMapping("/assets/**")//
            .addLocations(assetsPath);

    registry.addResourceMapping("/upload/**")//
            .addLocations(upload);

    registry.addResourceMapping("/logo.png")//
            .addLocations("file:///D:/dev/www.yhj.com/webapps/assets/images/logo.png");

    registry.addResourceMapping("/favicon.ico")//
            .addLocations("classpath:/favicon.ico");

    return registry;
}

@Singleton
@Profile("prod")
public ResourceHandlerRegistry prodResourceMappingRegistry() {

    final ResourceHandlerRegistry registry = new ResourceHandlerRegistry();

    registry.addResourceMapping(LoginInterceptor.class)//
            .setPathPatterns("/assets/admin/**")//
            .setOrder(Ordered.HIGHEST_PRECEDENCE)//
            .addLocations("/assets/admin/");

    return registry;
}

@Override
public void configureResourceHandler(ResourceHandlerRegistry registry) {

    registry.addResourceMapping(LoginInterceptor.class)//
            .setPathPatterns("/assets/admin/**")//
            .setOrder(Ordered.HIGHEST_PRECEDENCE)//
            .addLocations("/assets/admin/");
}

自定义参数转换器

@Singleton
public class UserSessionParameterResolver implements OrderedParameterResolver {
  private final WebSessionManager sessionManager;

  public UserSessionParameterResolver(WebSessionManager sessionManager) {
    this.sessionManager = sessionManager;
  }

  @Override
  public boolean supports(MethodParameter parameter) {
    return parameter.isAnnotationPresent(UserSession.class);
  }

  @Override
  public Object resolveParameter(final RequestContext context, final MethodParameter parameter) throws Throwable {
    final WebSession session = sessionManager.getSession(context, false);
    if (session != null) {
      final Object attribute = session.getAttribute(Constant.USER_INFO);
      if (attribute != null) {
        return attribute;
      }
    }
    throw new UnauthorizedException();
  }

  @Override
  public int getOrder() {
    return HIGHEST_PRECEDENCE;
  }

}

@Singleton
@Order(Ordered.HIGHEST_PRECEDENCE)
public class PageableMethodArgumentResolver implements ParameterResolver {

  private static final String PARAMETER_SIZE = "size";
  private static final String PARAMETER_CURRENT = "page";

  private int maxListSize;
  private int defaultListSize;

  @Override
  public boolean supports(MethodParameter parameter) {
    return parameter.isAssignableTo(Pageable.class);
  }

  @Override
  public Object resolveParameter(RequestContext request, MethodParameter parameter) throws Throwable {
    return new RequestContextPageable(request, defaultListSize, maxListSize);
  }

  public int getMaxListSize() {
    return maxListSize;
  }

  public void setMaxListSize(int maxListSize) {
    this.maxListSize = maxListSize;
  }

  public int getDefaultListSize() {
    return defaultListSize;
  }

  public void setDefaultListSize(int listSize) {
    this.defaultListSize = listSize;
  }

  public final static class RequestContextPageable implements Pageable {

    private final int maxListSize;
    private final int defaultListSize;

    private Integer size;
    private Integer current;
    private final RequestContext request;

    public RequestContextPageable(RequestContext request, int listSize, int maxListSize) {
      this.request = request;
      this.defaultListSize = listSize;
      this.maxListSize = maxListSize;
    }

    @Override
    public int getCurrent() {

      if (current == null) {
        final String parameter = request.getParameter(PARAMETER_CURRENT);
        if (StringUtils.isEmpty(parameter)) {
          current = 1;
        }
        else if ((current = Integer.valueOf(parameter)) <= 0) {
          throw new IllegalArgumentException("only 'page > 0'");
        }
      }
      return current.intValue();
    }

    @Override
    public int getSize() {
      if (size == null) {
        int s;
        final String parameter = request.getParameter(PARAMETER_SIZE);
        if (StringUtils.isEmpty(parameter)) {
          s = defaultListSize;
        }
        else {
          s = Integer.parseInt(parameter);
          if (s <= 0) {
            throw new IllegalArgumentException("only 'size > 0'");
          }
          if (s > maxListSize) {
            throw DemoUtils.accessForbidden();
          }
        }
        return size = s;
      }
      return size.intValue();
    }

  }

}

@Component
public class DateConverter implements Converter<String, Date> {
    @Override
    public Date convert(String source) throws ConversionException {
        ...
    }
}


也可以通过xml文件配置简单视图

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Web-Configuration PUBLIC 
		"-//TODAY BLOG//Web - Configuration DTD 2.0//CN"
			"https://taketoday.cn/framework/web/dtd/web-configuration-2.3.7.dtd">

<Web-Configuration>

    <controller prefix="/error/">
        <action resource="400" name="BadRequest" status="400" />
        <action resource="403" name="Forbidden" status="403" />
        <action resource="404" name="NotFound" status="404" />
        <action resource="500" name="ServerIsBusy" status="500" />
        <action resource="405" name="MethodNotAllowed" status="405" />
    </controller>

    <controller>
        <action resource="redirect:http://pipe.b3log.org/blogs/Today" name="today-blog-pipe" />
        <action resource="redirect:https://taketoday.cn" name="today" />
        <action resource="redirect:https://github.com" name="github" />
        <action resource="redirect:/login" name="login.do" />
    </controller>

    <controller class="cn.taketoday.web.demo.controller.XMLController" name="xmlController" prefix="/xml/">
        <action name="obj" method="obj" />
        <action name="test" resource="test" method="test"/>
    </controller>

</Web-Configuration>

登录实例

@Controller
public class UserController {

/* 
    <controller prefix="/WEB-INF/view/" suffix=".ftl">
        <action resource="login" name="login" />
        <action resource="register" name="register" />
    </controller> */
    
    // @GET("login")
    @RequestMapping(value = "/login" , method = RequestMethod.GET)
    public String login() {
        return "/login/login";//支持jsp,FreeMarker,Thymeleaf,自定义视图
    }
    
    @Logger("登录")
    //@POST("/login")
    //@RequestMapping(value = "/login" , method = RequestMethod.POST)
    @ActionMapping(value = "/login", method = RequestMethod.POST)
    public String login(HttpSession session, RedirectModel redirectModel, @Valid User user, Errors error) {
    
        if (error.hasErrors()) {
            System.err.println(error.getAllErrors());
            redirectModel.attribute("msg", error.getAllErrors().toString());
            return "redirect:/login";
        }
    
        User login = userService.login(user);
        if (login == null) {
            redirectModel.attribute("userId", user.getUserId());
            redirectModel.attribute("msg", "登录失败");
            return "redirect:/login";
        }
        redirectModel.attribute("msg", "登录成功");
        session.setAttribute(USER_INFO, login);
        return "redirect:/user/info";
    }
    
}

文件下载,支持直接返回给浏览器图片

@RequestMapping(value = {"/download"}, method = RequestMethod.GET)
public File download(String path) {
    return new File(path);
}
@GET("/display")
public final BufferedImage display(HttpServletResponse response) throws IOException {
    response.setContentType("image/jpeg");
    return ImageIO.read(new File("D:/taketoday.cn/webapps/upload/logo.png"));
}

@GET("captcha")
public final BufferedImage captcha(HttpServletRequest request) throws IOException {
    BufferedImage image = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_RGB);
    Graphics graphics = image.getGraphics();
    graphics.setColor(Color.WHITE);
    graphics.fillRect(0, 0, IMG_WIDTH, IMG_HEIGHT);
    Graphics2D graphics2d = (Graphics2D) graphics;
    drawRandomNum(graphics2d, request);
    return image;
}

文件上传,支持多文件

@RequestMapping(value = { "/upload" }, method = RequestMethod.POST)
public final String upload(@Multipart MultipartFile uploadFile) throws IOException {

    String upload = "D:/www.yhj.com/webapps/upload/";
    String path = upload + uploadFile.getFileName();
    File file = new File(path);
    uploadFile.save(file);

    return "/upload/" + uploadFile.getFileName();
}

@POST({"/upload/multi"})
public final String multiUpload(HttpServletResponse response, @Multipart MultipartFile[] files) throws IOException {

    String upload = "D:/www.yhj.com/webapps/upload/";
    
    for (MultipartFile multipartFile : files) {
        String path = upload + multipartFile.getFileName();
        File file = new File(path);
        System.out.println(path);
        if (!multipartFile.save(file)) {
            return "<script>alert('upload error !')</script>";
            //response.getWriter().print("<script>alert('upload error !')</script>");
        }
    }
    //response.getWriter().print("<script>alert('upload success !')</script>");
    return "<script>alert('upload success !')</script>";
}

🙏 鸣谢

本项目的诞生离不开以下开源项目:

  • Freemarker: Apache Freemarker
  • Slf4j: Simple Logging Facade for Java
  • Spring: Spring Framework
  • EL: Java Unified Expression Language
  • FastJSON: A fast JSON parser/generator for Java
  • Lombok: Very spicy additions to the Java programming language
  • Today Context: A Java library for dependency injection and aspect oriented programing
  • Hibernate Validator: Hibernate Validator - Bean Validation 2.0 (JSR 380) Reference Implementation

📄 开源协议

请查看 GNU GENERAL PUBLIC LICENSE