技术栈

前端界面使用 LayuiAdmin提供的后台管理模板系统
前端与后端数据交互全部采用JQuery提供的ajax异步请求

后端使用Servlet提供HTTP接口,一个Servlet实现类提供多个HTTP接口,由if条件分支映射到具体对应的Services方法处理业务

持久层使用JDBC访问数据库,并通过反射可以将查询结果集映射为java实体类对象或者Map对象

由于要实现后端权限控制,所以一些视图资源统一放在了web目录的WEB-INF文件夹下,由一个名为ViewResolver的servlet通过转发来实现视图的跳转以及非公共静态资源的访问

配置文件:配置文件使用properties文件管理,有统一的配置管理类在系统启动的时候加载配置文件

具体技术实现

持久层 JDBC工具类

  1. public synchronized <T> List<T> executeQueryForList(String sql, Class<T> resultType,Object... params) {
  2. List<T> result = new ArrayList<>();
  3. T bean;
  4. getConnection();
  5. statement = getStatement(sql);
  6. if (params !=null&&params.length!=0){
  7. setParams(params);
  8. }
  9. try {
  10. resultSet = statement.executeQuery();
  11. while (resultSet.next()) {
  12. bean = resultType.newInstance();
  13. Field[] fields = resultType.getDeclaredFields();
  14. for (Field field : fields) {
  15. Object value = null;
  16. try {
  17. value = resultSet.getObject(field.getName());
  18. }catch (SQLException e){
  19. continue;
  20. }
  21. field.setAccessible(true);
  22. field.set(bean, value);
  23. }
  24. result.add(bean);
  25. }
  26. } catch (Exception e) {
  27. e.printStackTrace();
  28. } finally {
  29. close();
  30. }
  31. return result;
  32. }

这是JDBC工具类中的一个方法,是一个实体方法,需要用对象去调用

sql执行使用了 PreparedStatement 可以避免sql注入带来的安全问题

执行sql完成之后返回结果集ResultMap之后,使用反射创建对应的Java实体类对象(方法参数提供了实体类的Class对象)。

默认映射方式是如果结果集中的列名和实体类的属性列表中属性名相同,就把结果集中的这个字段的值赋值给实体类对象的这个属性。具体实现方法见代码详情

  1. while (resultSet.next()) {
  2. bean = resultType.newInstance();
  3. Field[] fields = resultType.getDeclaredFields();
  4. for (Field field : fields) {
  5. Object value = null;
  6. try {
  7. value = resultSet.getObject(field.getName());
  8. }catch (SQLException e){
  9. continue;
  10. }
  11. field.setAccessible(true);
  12. field.set(bean, value);
  13. }

其他返回实体类的方法都基于这个方法来实现

还有一个方法是结果集中的数据映射成Map:

  1. ·public synchronized List<Map<String, Object>> executeQueryForListMap(String sql,Object... params){
  2. List<Map<String, Object>> result = new ArrayList<>();
  3. Map<String, Object> bean;
  4. getConnection();
  5. statement = getStatement(sql);
  6. if (params !=null&&params.length!=0){
  7. setParams(params);
  8. }
  9. try {
  10. resultSet = statement.executeQuery();
  11. ResultSetMetaData metaData = resultSet.getMetaData();
  12. int columnCount = metaData.getColumnCount();
  13. while (resultSet.next()) {
  14. bean = new HashMap<String, Object>();
  15. for (int i = 1; i < columnCount+1; i++) {
  16. bean.put(metaData.getColumnLabel(i),resultSet.getObject(i));
  17. }
  18. result.add(bean);
  19. }
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. } finally {
  23. close();
  24. }
  25. return result;
  26. }

直接把ResultMap中的一行数据映射为一个HashMap对象,列明作为Key值作为Value
多行数据作为多个HashMap对象放入一个List中返回
这种处理结果集的方式更为通用,更灵活

权限控制

全局的访问控制有一个名为AuthorityFilter的Filter实现控制:代码如下:

  1. @WebFilter(filterName = "authorityFilter",urlPatterns = "/*")
  2. public class AuthorityFilter implements Filter {
  3. private final Logger logger = LoggerFactory.getLogger(AuthorityFilter.class);
  4. private List<String> staticResourceList = null;
  5. private List<String> adminList = null;
  6. private List<String> studentList = null;
  7. private List<String> teacherList = null;
  8. @Override
  9. public void init(FilterConfig filterConfig) throws ServletException {
  10. staticResourceList = ConfigUtil.getList("server.resource.static",",");
  11. adminList = ConfigUtil.getList("server.user.authorityList.admin",",");
  12. studentList = ConfigUtil.getList("server.user.authorityList.student",",");
  13. teacherList = ConfigUtil.getList("server.user.authorityList.teacher",",");
  14. }
  15. @Override
  16. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  17. HttpServletRequest request = (HttpServletRequest) servletRequest;
  18. HttpServletResponse response = (HttpServletResponse) servletResponse;
  19. HttpSession session = request.getSession();
  20. String uri = request.getRequestURI().replace(request.getContextPath(),"");
  21. Integer type = (Integer) session.getAttribute(SessionData.TYPE.getSessionKey());
  22. //放行登录页
  23. if (uri.equals("/")||uri.equals("/login")||uri.equals("/login.jsp")){
  24. filterChain.doFilter(request,response);
  25. return;
  26. }
  27. //放行错误页
  28. if (uri.contains("error_page")){
  29. filterChain.doFilter(request,response);
  30. return;
  31. }
  32. //放行静态资源
  33. if (test(uri,staticResourceList)){
  34. filterChain.doFilter(request, response);
  35. return;
  36. }
  37. //权限校验
  38. if (type == null){
  39. response.sendRedirect("/");
  40. }else if (type.equals(AccountType.ADMIN.getCode())){
  41. if (test(uri,adminList)) {
  42. filterChain.doFilter(request,response);
  43. }else{
  44. response.sendRedirect("/error_page/401.html");
  45. }
  46. }else if (type.equals(AccountType.STUDENT.getCode())){
  47. if (test(uri,studentList)) {
  48. filterChain.doFilter(request,response);
  49. }else{
  50. response.sendRedirect("/error_page/401.html");
  51. }
  52. }else if (type.equals(AccountType.TEACHER.getCode())){
  53. if (test(uri,teacherList)) {
  54. filterChain.doFilter(request,response);
  55. }else{
  56. response.sendRedirect("/error_page/401.html");
  57. }
  58. }else{
  59. response.sendRedirect("/error_page/401.html");
  60. }
  61. }
  62. private boolean test(String uri,List<String> ruleList){
  63. for (String s : ruleList) {
  64. if (s.endsWith("/*")){
  65. if (uri.startsWith(s.replace("/*",""))){
  66. return true;
  67. }
  68. }else {
  69. if (s.equals(uri)){
  70. return true;
  71. }
  72. }
  73. }
  74. return false;
  75. }
  76. @Override
  77. public void destroy() {
  78. Filter.super.destroy();
  79. }
  80. }

这个过滤器拦截所有请求,过滤流程如下:

未命名文件.svg

不同角色的权限列表也在properties配置文件中指定,如下:

  1. #用户权限类型
  2. server.user.types=admin,student,teacher
  3. #用户权限列表
  4. server.user.authorityList.admin=/login,/api/common/*,/api/admin/*,/view/common/*,/view/admin/*,/,/index,/api/student/*,/api/teacher/*
  5. server.user.authorityList.student=/login,/api/common/*,/api/student,/view/common/*,/view/student/*,/,/index,/api/student/*
  6. server.user.authorityList.teacher=/login,/api/common/*,/api/teacher/*,/view/common/*,/view/teacher/*,/,/index
  7. server.resource.static=/static/*

视图跳转

由于要实现后端权限控制,视图的访问也要后端实现权限控制,所以将所有的非公共资源的视图资源放在了无法会直接访问的WEB-INF目录下。

前端访问视图资源统一访问一个名为ViewResolver的Servlet通过转发访问。代码如下:

  1. @WebServlet(name = "viewResolver" , urlPatterns = "/view/*")
  2. public class ViewResolver extends HttpServlet {
  3. private static final long serialVersionUID = 8549443362991290786L;
  4. @Override
  5. public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  6. String uri = req.getRequestURI();
  7. String contextPath = req.getContextPath();
  8. uri = uri.replace(contextPath,"");
  9. String viewPath = getViewPath(uri);
  10. req.getRequestDispatcher(viewPath).forward(req,resp);
  11. }
  12. private String getViewPath(String uri){
  13. uri = uri.substring(5);
  14. String prefix = ConfigUtil.getString("server.viewResolver.prefix");
  15. String suffix = ConfigUtil.getString("server.viewResolver.suffix");
  16. return prefix + uri + suffix;
  17. }
  18. }

视图的前缀和后缀在配置文件中统一管理:

  1. server.viewResolver.prefix=/WEB-INF/view
  2. server.viewResolver.suffix=.jsp

这样就可以个不同的用户角色分配不同的文件夹放置视图资源,统一管理视图的访问权限

配置文件的读取

全部的配置文件都写在名为application.properties的配置文件中

通过ConfigUtil工具类读取配置文件的信息,代码如下:

  1. /**
  2. * 读取配置文件工具类
  3. *
  4. * @author 韩晓红
  5. */
  6. public class ConfigUtil {
  7. private static final Logger logger = LoggerFactory.getLogger(ConfigUtil.class);
  8. private static final Properties PROPERTIES;
  9. static {
  10. InputStream resourceAsStream = ConfigUtil.class.getClassLoader().getResourceAsStream("application.properties");
  11. PROPERTIES = new Properties();
  12. try {
  13. PROPERTIES.load(resourceAsStream);
  14. } catch (IOException e) {
  15. e.printStackTrace();
  16. logger.error("load application.properties error!");
  17. System.exit(0);
  18. }
  19. }
  20. public static String getString(String key){
  21. return PROPERTIES.getProperty(key);
  22. }
  23. public static List<String> getList(String key,String regex){
  24. return Arrays.asList(PROPERTIES.getProperty(key).split(regex));
  25. }
  26. }

通过static代码块,在类加载的时候使用加载当前类的类加载器读取配置文件,将配置信息加载进内存,
通过两个staitc方法分别获取单项的配置和列表的配置