• 因为 CSDN 自己排版各种奇葩,导致代码格式可能会有问题,如果出现该情况请前往 github 查看 ---->跳转

【CRM客户管理系统】

调研

产品

需求

UI

开发

  • 技术选型:

      SSM、MYSQL57、tomcat7、jdk1.7、boostrap、anglur.js、quartz、ehcache、富文本编辑器(百度编辑器)
    
  • 框架搭建:

    • 数据库设计:

        数据库名称=工程名称CRM_16表设计:表字符:utf-8、utf8表字段:主键、bigint(20) 自增、(32) UUID、不能为空索引:外键、关联字段、查询比较频繁的字段单表:500M、索引个数16个临时表、存储过程、视图、触发器、监听器
      
    • 数据库优化原则:

  • CRM数据库表设计说明:

      参考脚本文件《crm.sql》hj_user 用户表userid 用户主键roleid  角色主键deptid 部门主键hj_role 角色表roleid 角色IDdeptid 部门IDhj_dept 部门表deptid 部门IDdeptparaid 上一级部门IDhj_menu 菜单表menuid 菜单主键IDmenuparaid 上一级菜单IDhj_role_menu  角色-菜单关系表roleid 角色IDmenuid 菜单ID
    
  • SQL需求:

    • 需求1:查询用户ID=2的信息: 用户ID,用户名,角色ID,角色名称,部门ID,部门名称

        selectu.userid,u.username,u.roleid,r.rolename,u.deptid,d.deptnamefrom hj_user uleft join hj_role r on r.roleid = u.roleidleft join hj_dept d on d.deptid = u.deptidwhere u.userid = 2;
      
    • 需求2:查询角色ID=2所属的菜单信息:角色ID,角色名称,菜单ID,菜单名称

        select rm.roleid,r.rolename,rm.menuid,m.menunamefrom hj_role_menu rmleft join hj_role r on r.roleid = rm.roleidleft join hj_menu m on m.menuid = rm.menuidwhere rm.roleid = 2;select rm.roleid,r.rolename,rm.menuid, -- group_concat()group_concat(m.menuname separator ',') menunamefrom hj_role_menu rmleft join hj_role r on r.roleid = rm.roleidleft join hj_menu m on m.menuid = rm.menuidgroup by r.rolename;
      
    • 需求3:查询系统管理(菜单ID=1)所有下级菜单

        select * from  hj_menu mwhere m.menuparaid = 1;
      
    • 需求4:查询所有的上级菜单信息

        select * from  hj_menu mwhere m.menuparaid is null;
      
  • 项目工程搭建步骤:

    • 1:新建一个web project工程CRM_16
    • 2:解压FTP中CRM压缩包到本地文件夹
    • 3:工程名右键,选择build path,选中最后一个选项,将tomcat的jar包导入工程
    • 4:复制压缩文件中的webroot文件夹“内”的文件,到工程的WebROOT文件夹下
    • 5:复制lib文件夹下的jar包复制到工程中
    • 6:取消前端文件验证表达式错误,操作步骤,参考下图
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S32iVGva-1602315801993)(https://i.imgur.com/7rGu5JI.png)]
  • 框架代码:

    • web.xml:

        通用性设置如下:<!-- spring配置文件信息 --><!-- springMVC配置文件信息 --><!-- 编码格式 --><!-- 日志信息 --><!-- session有效期 --><!-- 404、500 --><!-- 请求后缀拦截.do(后台管理系统),.htm(前端网站) --><?xml version="1.0" encoding="UTF-8"?><web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"><display-name>CRM_16</display-name>   <!-- spring配置文件信息 --><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- springMVC配置文件信息 --><servlet><servlet-name>crm_16</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:context-dispatcher.xml</param-value></init-param><!-- 启用即加载 --><load-on-startup>1</load-on-startup></servlet><!-- 请求后缀拦截.do(后台管理系统),.htm(前端网站) --><servlet-mapping><servlet-name>crm_16</servlet-name><url-pattern>*.do</url-pattern></servlet-mapping><!-- 编码格式UTF-8 --><filter><filter-name>encodingFilter</filter-name ><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param><init-param><param-name>forceEncoding</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>*</url-pattern></filter-mapping><!-- 日志信息 --><context-param><param-name>log4jConfigLocation</param-name><param-value>classpath:/log4j.xml</param-value></context-param><!-- 加载log4j配置文件 --><listener><listener-class>org.springframework.web.util.Log4jConfigListener</listener-class></listener><!-- session有效期 --><session-config><session-timeout>120</session-timeout><!-- 默认的是分钟 --></session-config><!-- 404、500 --><error-page><error-code>404</error-code><location>/404.do</location></error-page><error-page><error-code>500</error-code><location>/500.do</location></error-page><!-- 首页信息(可选) --><welcome-file-list><welcome-file>/index.do</welcome-file></welcome-file-list></web-app>
      
  • 连接池:

    • c3p0:

    • 阿里:

      问题:
      1:游标越界
      2:事务连接次数过多 too many connect

      解决方式:

        参考代码
      

登录页面跳转

  • 开发步骤:

      1:获取跳转页面的接口地址(前端获取)2:增加controller类进行页面跳转
    
  • 代码示例:

    • UserLoginController.java:

        /*** 登录首页* @author likang* @date   2018-4-19 下午5:33:34*/@Controllerpublic class UserLoginController {/*** 跳转登录页面* @param model* @return*/@RequestMapping(value = "/login.do",method = RequestMethod.GET)public String index(Model model){return JumpViewConstants.SYSTEM_LOGIN;}}
      
  • 页面展示:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YUuPckGq-1602315801995)(https://i.imgur.com/nS0sDEd.png)]

登录

  • 开发步骤:

    • 1:通过点击【登录】按钮,获取登录的接口地址
    • 2:在UserLoginController类中,增加登录接口
    • 3:首先判断用户名是否存在,其次判断匹配是否匹配
    • 4:处理session问题
  • 代码如下:

    • UserLoginController.java:

        /*** 登录首页* @author likang* @date   2018-4-19 下午5:33:34*/@Controllerpublic class UserLoginController {@Autowiredprivate IUserService userService;/*** 跳转登录页面* @param model* @return*/@RequestMapping(value = "/login.do",method = RequestMethod.GET)public String index(Model model){return JumpViewConstants.SYSTEM_LOGIN;}/*** 登录功能* @param request* @param email  用户名* @param password 密码* @param sign* @return*/@RequestMapping(value = "/login.do",method = RequestMethod.POST)public String login(HttpServletRequest request,String email,String password,String sign,Model model){//      if (email != null &amp;&amp; !"".equals(email)) {//          //      }//isnotblank:判断参数是否为空和“”//isNotEmpty:只会判断参数是否为nullif (StringUtils.isNotBlank(email) &amp;&amp; StringUtils.isNotBlank(password)) {//利用spring容器获取map中的属性值email = email + ContextUtil.getInitConfig("email_suffix");//验证用户名是否存在User user = userService.queryUserByEmail(email);if (user == null) {//用户不存在//              model.addAttribute(ReturnConstants.USER_NOT_EXIST);model.addAttribute("msg", ReturnConstants.USER_NOT_EXIST);return JumpViewConstants.SYSTEM_LOGIN;}//验证密码是否匹配boolean isExis = userService.isExisPassword(String.valueOf(user.getUserid()), password);if (!isExis) {//              model.addAttribute(ReturnConstants.PASSWORD_ERROR);model.addAttribute("msg", ReturnConstants.PASSWORD_ERROR);return JumpViewConstants.SYSTEM_LOGIN;}//cookie//TODO......//处理sessionUserContext.setLoginUser(user);request.getSession(true).setAttribute("loginName", user.getUsername());request.getSession(true).setAttribute("ischange", user.getIschange());//跳转成功首页return JumpViewConstants.SYSTEM_INDEX;}return ReturnConstants.PARAM_NULL;//接收参数为空}}
      
    • IUserService.java:

        /*** 用户信息接口* @author likang* @date   2018-4-23 上午9:36:12*/public interface IUserService {/*** 根据邮箱查询用户信息* @param email 邮箱* @return*/public User queryUserByEmail(String email);/*** 验证密码是否匹配* @param userid 用户主键ID* @param password  密码* @return*/public boolean isExisPassword(String userid,String password);}
      
    • UserServiceImpl.java:

        @Service@Transactional(rollbackFor=Exception.class)public class UserServiceImpl implements IUserService{//  @Resource//首先按照名称匹配,其次按照类型匹配@Autowired//只会按照类型匹配(推荐使用)IDataAccess<User> userDao;public User queryUserByEmail(String email) {Map<String, Object> param = new HashMap<String, Object>();param.put("email", email);List<User> list = userDao.queryByStatment("queryUserByEmail", param, null);if (list != null &amp;&amp; list.size() > 0) {return list.get(0);}return null;}public boolean isExisPassword(String userid, String password) {Map<String, Object> param = new HashMap<String, Object>();param.put("userid", userid);param.put("password", MD5Tools.encode(password));List<User> list = userDao.queryByStatment("isExisPassword", param, null);if (list != null &amp;&amp; list.size() > 0) {return true;}return false;}}
      
    • UserMapper.xml:

        <!-- 查询用户是否存在 --><select id="queryUserByEmail" parameterType="java.util.Map" resultType="com.hjcrm.system.entity.User">selectu.userid,u.username,u.email,u.roleid,u.deptid,u.ischangefrom hj_user uwhere u.email = #{email}</select><!-- 查询用户密码是否匹配--><select id="isExisPassword" parameterType="java.util.Map" resultType="com.hjcrm.system.entity.User">selectu.userid,u.username,u.email,u.roleid,u.deptidfrom hj_user uwhere u.userid = #{userid} and u.password = #{password}</select></code></pre><p>mybatis-config.xml:</p><pre><code><mappers><mapper resource="commonsqlmappings/CommonMapper.xml" /><mapper resource="mybatis/UserMapper.xml"/></mappers>
      

登录功能-首页访问–完善功能

  • 代码如下:

    • UserLoginController.java:

        /*** 登录首页* @author likang* @date   2018-4-19 下午5:33:34*/@Controllerpublic class UserLoginController {@Autowiredprivate IUserService userService;/*** 跳转登录页面* @param model* @return*/@RequestMapping(value = "/login.do",method = RequestMethod.GET)public String index(Model model){if (UserContext.getLoginUser() != null) {return "redirect:/main.do";}return JumpViewConstants.SYSTEM_LOGIN;}/*** 主页面* @param model* @return*/@RequestMapping(value = "/main.do",method = RequestMethod.GET)public String main(Model model){if (UserContext.getLoginUser() != null) {return JumpViewConstants.SYSTEM_INDEX;}return JumpViewConstants.SYSTEM_LOGIN;}/*** 登录功能* @param request* @param email  用户名* @param password 密码* @param sign* @return*/@RequestMapping(value = "/login.do",method = RequestMethod.POST)public String login(HttpServletRequest request,String email,String password,String sign,Model model){//      if (email != null &amp;&amp; !"".equals(email)) {//          //      }//isnotblank:判断参数是否为空和“”//isNotEmpty:只会判断参数是否为nullif (StringUtils.isNotBlank(email) &amp;&amp; StringUtils.isNotBlank(password)) {//利用spring容器获取map中的属性值email = email + ContextUtil.getInitConfig("email_suffix");//验证用户名是否存在User user = userService.queryUserByEmail(email);if (user == null) {//用户不存在//              model.addAttribute(ReturnConstants.USER_NOT_EXIST);model.addAttribute("msg", ReturnConstants.USER_NOT_EXIST);return JumpViewConstants.SYSTEM_LOGIN;}//验证密码是否匹配boolean isExis = userService.isExisPassword(String.valueOf(user.getUserid()), password);if (!isExis) {//              model.addAttribute(ReturnConstants.PASSWORD_ERROR);model.addAttribute("msg", ReturnConstants.PASSWORD_ERROR);return JumpViewConstants.SYSTEM_LOGIN;}//cookie//TODO......//处理sessionUserContext.setLoginUser(user);request.getSession(true).setAttribute("loginName", user.getUsername());request.getSession(true).setAttribute("ischange", user.getIschange());//跳转成功首页//          return JumpViewConstants.SYSTEM_INDEX;return "redirect:/main.do";}return ReturnConstants.PARAM_NULL;//接收参数为空}}
      

登录功能-cookie问题

  • 开发步骤:

    • 1:为了解决用户端禁用浏览器cookie第三方数据的问题
    • 2:只需要在服务器中保存一份cookie即可,同步到浏览器客户端
  • 示例代码:

    • UserLoginController.java:

        private static final String COOKIE_KEY = "_auth_";private static final String COOKIE_SPI = "_#_";/*** 登录功能* @param request* @param email  用户名* @param password 密码* @param sign* @return*/@RequestMapping(value = "/login.do",method = RequestMethod.POST)public String login(HttpServletRequest request,HttpServletResponse response,String email,String password,String sign,Model model){//      if (email != null &amp;&amp; !"".equals(email)) {//          //      }//isnotblank:判断参数是否为空和“”//isNotEmpty:只会判断参数是否为nullif (StringUtils.isNotBlank(email) &amp;&amp; StringUtils.isNotBlank(password)) {//利用spring容器获取map中的属性值email = email + ContextUtil.getInitConfig("email_suffix");//验证用户名是否存在User user = userService.queryUserByEmail(email);if (user == null) {//用户不存在//              model.addAttribute(ReturnConstants.USER_NOT_EXIST);model.addAttribute("msg", ReturnConstants.USER_NOT_EXIST);return JumpViewConstants.SYSTEM_LOGIN;}//验证密码是否匹配boolean isExis = userService.isExisPassword(String.valueOf(user.getUserid()), password);if (!isExis) {//              model.addAttribute(ReturnConstants.PASSWORD_ERROR);model.addAttribute("msg", ReturnConstants.PASSWORD_ERROR);return JumpViewConstants.SYSTEM_LOGIN;}//cookieCookie cok = new Cookie(COOKIE_KEY, URLEncoder.encode(user.getUsername())+COOKIE_SPI+MD5Tools.encode(user.getEmail()));cok.setPath("/");cok.setMaxAge(-1);//-1:立即创建,并且在登录成功之后,就生效//0:在客户端关闭浏览器之后,即失效response.addCookie(cok);//处理sessionUserContext.setLoginUser(user);request.getSession(true).setAttribute("loginName", user.getUsername());request.getSession(true).setAttribute("ischange", user.getIschange());//跳转成功首页//          return JumpViewConstants.SYSTEM_INDEX;return "redirect:/main.do";}return ReturnConstants.PARAM_NULL;//接收参数为空}
      

退出功能

  • 开发步骤:

    • 1:清除session信息
    • 2:清除存放于服务器中的cookie数据
    • 3:清除客户端中的cookie数据
    • 4:跳转登录页面即可
  • 示例代码:

    • UserLoginController.java:

        /*** 退出* @param request* @return*/@RequestMapping(value = "/logout.do",method = RequestMethod.GET)public String logout(HttpServletRequest request,HttpServletResponse response){//清除sessionUserContext.clearLoginUser();//清除服务器中的cookie数据Cookie cok = new Cookie(COOKIE_KEY,null);cok.setMaxAge(0);cok.setPath("/");response.addCookie(cok);//清除客户端中的cookie数据Cookie coksessionID = new Cookie("JSESSIONID",null);coksessionID.setMaxAge(0);coksessionID.setPath(request.getContextPath());response.addCookie(coksessionID);//跳转登录页面return "redirect:/main.do";}
      

登录成功-权限管理-左侧菜单展示功能

  • 开发步骤:

    • 1:根据登录成功用户的角色ID,查询对应的菜单信息,参考第一天的sql语句
    • 2:首先查询一级菜单信息
    • 3:循环遍历一级菜单信息,查询当前一级菜单对应的二级菜单信息
  • 示例代码:

    • UserLoginController.java:

        /*** 主页面* @param model* @return*/@RequestMapping(value = "/main.do",method = RequestMethod.GET)public String main(Model model){if (UserContext.getLoginUser() != null) {//根据当前登录用户的角色,查询当前角色对应的菜单信息List<Menu> list = userService.queryMenusByRoleId(UserContext.getLoginUser().getRoleid().toString());model.addAttribute("menus", list);return JumpViewConstants.SYSTEM_INDEX;}return JumpViewConstants.SYSTEM_LOGIN;}
      
    • IUserService.java:

        /*** 根据角色主键ID,查询当前角色对应的菜单信息* @param roleId 角色主键ID* @return*/public List<Menu> queryMenusByRoleId(String roleId);
      
    • UserServiceImpl.java:

        @AutowiredIDataAccess<Menu> menuDao;public List<Menu> queryMenusByRoleId(String roleId) {Map<String, Object> param = new HashMap<String, Object>();param.put("roleId", roleId);param.put("isparid", "true");//判断sql语句的标识,不封装到sql执行语句当中//只查询一级菜单信息List<Menu> list = menuDao.queryByStatment("queryMenusByRoleId", param, null);if (list != null &amp;&amp; list.size() > 0) {for (int i = 0; i < list.size(); i++) {Long menuid = list.get(i).getMenuid();//一级菜单主键IDparam.clear();param.put("roleId", roleId);param.put("menuparaid", menuid);//将一级菜单ID,当做二级菜单的父IDparam.put("isparid", null);List<Menu> listch = menuDao.queryByStatment("queryMenusByRoleId", param, null);list.get(i).setChildren(listch);}}return list;}
      
    • UserMapper.xml:

        <!-- 查询用户角色ID,查询菜单信息--><select id="queryMenusByRoleId" parameterType="java.util.Map" resultType="com.hjcrm.system.entity.Menu">select rm.roleid,r.rolename,rm.menuid,m.menunamefrom hj_role_menu rmleft join hj_role r on r.roleid = rm.roleidleft join hj_menu m on m.menuid = rm.menuid<if test=" isparid != null and isparid !=''">where rm.roleid = #{roleId} and m.menuparaid is null</if><if test=" isparid == null">where rm.roleid = #{roleId} and m.menuparaid = #{menuparaid}</if></select>
      

页面展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UirPxjep-1602315801998)(https://i.imgur.com/tIbdpL5.png)]

用户管理

页面跳转

  • 开发步骤:

    • 1:通过前端找到接口地址
    • 2:为菜单的查询添加url的字段
  • 示例代码:

    • UserController.java:

        /*** 跳转用户管理页面* @param model* @return*/@RequestMapping(value = "/system/userMang.do",method = RequestMethod.GET)public String userManger(Model model){if (UserContext.getLoginUser() != null) {return JumpViewConstants.SYSTEM_USER_MANAGE;}return JumpViewConstants.SYSTEM_LOGIN;}
      

列表查询

  • 开发步骤:

    • 1:通过浏览器debug,获取接口地址(红色的404接口)
    • 2:支持分页查询
  • 示例代码:

    • UserController.java:

        /*** 用户管理模块* @author likang* @date   2018-4-23 下午4:55:12*/@Controllerpublic class UserController extends BaseController{@Autowiredprivate IUserService userService;/*** 跳转用户管理页面* @param model* @return*/@RequestMapping(value = "/system/userMang.do",method = RequestMethod.GET)public String userManger(Model model){if (UserContext.getLoginUser() != null) {return JumpViewConstants.SYSTEM_USER_MANAGE;}return JumpViewConstants.SYSTEM_LOGIN;}/*** 查询用户信息列表* @param request* @param currentPage* @param pageSize* @return*/@RequestMapping(value = "/system/userlist.do",method = RequestMethod.GET)public @ResponseBody String queryUserList(HttpServletRequest request,Integer currentPage,Integer pageSize){List<User> list = userService.queryAllUser(processPageBean(pageSize, currentPage));return jsonToPage(list);}}
      
    • IUserService.java:

        /*** 查询用户信息列表* @param pageBean* @return*/public List<User> queryAllUser(PageBean pageBean);
      
    • UserServiceImpl.java:

        public List<User> queryAllUser(PageBean pageBean) {List<User> list = userDao.queryByStatment("queryAllUser", null, pageBean);return list;}
      
    • UserMapper.xml:

        <!-- 查询用户信息列表--><select id="queryAllUser" parameterType="java.util.Map" resultType="com.hjcrm.system.entity.User">selectu.userid,u.username,u.email,u.roleid,u.deptid,u.ischangefrom hj_user u</select>
      

增加、修改

  • 开发步骤:

    • 1:通过前端获取增加、修改的接口地址
    • 2:添加查询所有部门信息接口
    • 3:添加根据部门ID查询当前部门下所有角色信息的接口
    • 4:添加保存、修改的方法controller
    • 5:根据是否存在用户主键ID,来区分到底是修改还是增加
    • 6:增加和修改无需写sql语句
  • 示例代码:

    • DeptController.java:

        /*** 部门管理模块* @author likang* @date   2018-4-24 上午9:06:29*/@Controllerpublic class DeptController extends BaseController{@Autowiredprivate IDeptService deptService;/*** 查询所有部门信息* @param request* @return*/@RequestMapping(value = "/dept/queryDept.do",method = RequestMethod.GET)public @ResponseBody String queryAllDepts(HttpServletRequest request){List<Dept> list = deptService.queryAllDepts();return jsonToPage(list);}/*** 根据部门ID,查询当前部门下的角色信息列表* @param request* @param deptid 部门ID* @return*/@RequestMapping(value = "/role/queryRoleByDeptid.do",method =RequestMethod.GET)public @ResponseBody String queryRolesByDeptId(HttpServletRequest request,String deptid){List<Role> list = deptService.queryRolesByDeptId(deptid);return jsonToPage(list);}}
      
    • IDeptService.java:

        /*** 部门信息接口* @author likang* @date   2018-4-24 上午9:08:04*/public interface IDeptService {/*** 查询所有的部门信息* @return*/public List<Dept> queryAllDepts();/*** 根据部门ID,查询角色信息* @param deptid 部门ID* @return*/public List<Role> queryRolesByDeptId(String deptid);}
      
    • DeptServiceImpl.java:

        @Service@Transactional(rollbackFor = Exception.class)public class DeptServiceImpl implements IDeptService{@AutowiredIDataAccess<Dept> deptDao;@AutowiredIDataAccess<Role> roleDao;public List<Dept> queryAllDepts() {List<Dept> list = deptDao.queryByStatment("queryAllDepts", null, null);return list;}public List<Role> queryRolesByDeptId(String deptid) {Map<String, Object> param = new HashMap<String, Object>();param.put("deptid", deptid);List<Role> list = roleDao.queryByStatment("queryRolesByDeptId", param, null);return list;}}
      
    • DeptMapper.xml:

        <mapper namespace="com.hjcrm.entity"><!-- 查询所有部门 --><select id="queryAllDepts" parameterType="java.util.Map" resultType="com.hjcrm.system.entity.Dept">selectd.deptid,d.deptname,d.deptparaidfrom hj_dept d</select><!-- 根据部门ID,查询角色信息 --><select id="queryRolesByDeptId" parameterType="java.util.Map" resultType="com.hjcrm.system.entity.Role">selectr.roleid,r.rolename,r.deptidfrom hj_role r where r.deptid = #{deptid}</select></mapper>
      
    • mybatis-config.xml:

        <mappers><mapper resource="commonsqlmappings/CommonMapper.xml" /><mapper resource="mybatis/UserMapper.xml"/><mapper resource="mybatis/DeptMapper.xml"/></mappers>
      
    • UserController.java:

        /*** 增加\修改用户信息* @param request* @param user* @return*/@RequestMapping(value = "/system/saveOrUpdate.do",method = RequestMethod.POST)public @ResponseBody String saveOrUpdateUser(HttpServletRequest request,User user){if (user != null) {userService.saveOrUpdateUser(user);return ReturnConstants.SUCCESS;}return ReturnConstants.PARAM_NULL;}
      
    • IUserService.java:

        /*** 增加、修改用户信息* @param user*/public void saveOrUpdateUser(User user);</code></pre><p>UserServiceImpl.java:</p><pre><code>public void saveOrUpdateUser(User user) {if (user != null) {if (user.getUserid() != null) {//修改user.setUpdate_id(UserContext.getLoginUser().getUserid());user.setUpdate_time(new Timestamp(System.currentTimeMillis()));userDao.update(user);}else{//增加user.setCreate_id(UserContext.getLoginUser().getUserid());user.setCreate_time(new Timestamp(System.currentTimeMillis()));userDao.insert(user);}}}
      

删除

  • 开发步骤:

    • 1:通过前端找到删除的接口地址(支持批量删除,真正企业做的时候,接口是后台来定义)
    • 2:增加删除的接口和方法
  • 示例代码:

    • UserController.java:

        /*** 删除用户信息,支持批量删除* @param request* @param ids 用户主键ID,多个用逗号隔开* @return*/@RequestMapping(value = "/system/deleteUser.do",method = RequestMethod.POST)public @ResponseBody String deleteUsers(HttpServletRequest request,String ids){if (StringUtils.isNotBlank(ids)) {userService.deleteUserByIds(ids);return ReturnConstants.SUCCESS;}return ReturnConstants.PARAM_NULL;}
      
    • IUserService.java:

        /*** 删除用户信息,批量删除* @param ids 用户主键ID,多个用逗号隔开*/public void deleteUserByIds(String ids);
      
    • UserServiceImpl.java:

        public void deleteUserByIds(String ids) {if (StringUtils.isNotBlank(ids)) {userDao.deleteByIds(User.class, ids);}}
      
  • 提示:

    • 1:回顾mysql的删除别名问题
    • 2:练习mybatis中的for标签

页面展示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xHQiKuqq-1602315802000)(https://i.imgur.com/rUyy9qq.png)]

测试环境项目部署过程

  • 系统环境:

    • linux-CentOS7
  • 使用工具:

    • CRT\SCP
  • 部署步骤:

    • 1:首先确认本地访问运行没有任何问题
    • 2:导出本地的数据库脚本,将脚本文件在虚拟机服务器的数据库中执行
    • 3:使用scp工具连接服务器,将本地tomcat的webapps目录下的项目,拖到虚拟机服务器tomcat的webapps目录下(可选:修改项目访问名称,ROOT在访问时,不需要输入)
    • 4:修改虚拟机服务器中项目的jdbc配置文件,修改为虚拟机数据库的连接信息
    • 5:使用crt工具,启动tomcat
    • 6:本地访问服务器项目信息
  • 注意事项:

    • 1:本地连接服务器的数据库,需要开启3306端口号(防火墙允许3306端口允许)

        命令:/sbin/iptables -I INPUT -p tcp --dport 3306 -j ACCEPT
      
    • 2:需要开启tomcat访问的端口号,命令如下:

        /sbin/iptables -I INPUT -p tcp --dport 8080 -j ACCEPT
      
    • 3:如果新增增量补丁,如下地方是需要重启tomcat服务的

        - 接口代码- 配置文件、属性文件- xml对应的sql语句
      
    • 4:查看服务器mysql是否开启的命令

            ps -ef|grep mysql
      

菜单管理

跳转页面

略,参考工程文件

查询

略,参考工程文件

增加、修改

略,参考工程文件

删除

  • 需求:

    1:如果菜单存在下一级菜单,则不允许删除
    2:如果菜单已经被分配,则不允许删除

  • 开发步骤:

    1://查询菜单ID,是否存在下一级菜单
    2://查询菜单ID,是否存在权限分配
    3://如果当前菜单ID,既不存在下一级菜单,又不存在权限分配,则可以直接删除成功
    4://如果满足其中一条信息,则都不允许删除,返回错误信息
    5://如果都不满足,删除成功

  • 示例代码:

    • MenuController.java:

        /*** 菜单删除功能* *    1:如果菜单存在下一级菜单,则不允许删除* 2:如果菜单已经被分配,则不允许删除**  error1 = [菜单:用户管理,菜单管理]  存在下一级菜单信息,不允许删除*  error2 = [菜单:角色管理,部门管理]  存在权限分配,不允许删除** @param request* @param ids 菜单主键ID,多个用逗号隔开* @return*/@RequestMapping(value = "/menu/delete.do",method = RequestMethod.POST)public @ResponseBody String deleteMenus(HttpServletRequest request,String ids){if (StringUtils.isNotBlank(ids)) {StringBuffer error1 = new StringBuffer();StringBuffer error2 = new StringBuffer();boolean ishasCh = false;boolean ishasrm = false;for (String id : ids.split(",")) {//查询菜单ID,是否存在下一级菜单if (menuService.isHasChiredMenu(id)) {//true:存在下一级菜单if (!ishasCh) {ishasCh = true;}if (error1.length() == 0){error1.append("[菜单:");}error1.append(menuService.queryMenuById(id) != null ?menuService.queryMenuById(id).getMenuname() : "" ).append(",");continue;}//查询菜单ID,是否存在权限分配if (menuService.isHasRoleMenu(id)) {//true:存在权限分配if (!ishasrm) {ishasrm = true;}if (error2.length() == 0){error2.append("[菜单:");}error2.append(menuService.queryMenuById(id) != null ?menuService.queryMenuById(id).getMenuname() : "" ).append(",");continue;}menuService.deleleMenuById(id);//如果当前菜单ID,既不存在下一级菜单,又不存在权限分配,则可以直接删除成功}//如果满足其中一条信息,则都不允许删除,返回错误信息if (ishasCh){error1.deleteCharAt(error1.length() - 1).append("]  存在下一级菜单信息,不允许删除");}if (ishasrm){error2.deleteCharAt(error2.length() - 1).append("]  存在权限分配,不允许删除");}
      

      // String msgbegin = ishasCh ? error1.toString() : “” + ishasrm ? ishasCh?error1.toString() : “”:""+error2.toString():"";

             String msg = error1.toString()+error2.toString();if (StringUtils.isNotBlank(msg)) {return msg;}//如果都不满足,删除成功return ReturnConstants.SUCCESS;}return ReturnConstants.PARAM_NULL;}
      
    • IMenuService.java:

        /*** 根据菜单主键ID,判断当前菜单是否存在下一级菜单* @param id 菜单主键ID* @return*/public boolean isHasChiredMenu(String id);/*** 根据菜单主键ID,判断当前菜单是否存在权限分配* @param id 菜单主键ID* @return*/public boolean isHasRoleMenu(String id);/*** 根据菜单id,查询菜单信息* @param id 主键ID* @return*/public Menu queryMenuById(String id);/*** 根据主键ID,删除菜单信息* @param id*/public void deleleMenuById(String id);
      

      MenuServiceImpl.java:

        public boolean isHasChiredMenu(String id) {Map<String, Object> param = new HashMap<String, Object>();param.put("mid", id);List<Menu> list = menuDao.queryByStatment("isHasChiredMenu", param, null);if (list != null && list.size() > 0) {return true;}return false;}public boolean isHasRoleMenu(String id) {Map<String, Object> param = new HashMap<String, Object>();param.put("mid", id);List<Role_menu> list = rmDao.queryByStatment("isHasRoleMenu", param, null);if (list != null && list.size() > 0) {return true;}return false;}public Menu queryMenuById(String id) {Map<String, Object> param = new HashMap<String, Object>();param.put("mid", id);List<Menu> list = menuDao.queryByStatment("queryMenuById", param, null);if (list != null && list.size() > 0) {return list.get(0);}return null;}public void deleleMenuById(String id) {if (StringUtils.isNotBlank(id)) {menuDao.deleteByIds(Menu.class, id);}}
      
    • RoleMapper.xml:

        <!-- 根据菜单ID,查询当前菜单是否存在下一级菜单信息 --><select id="isHasChiredMenu" parameterType="java.util.Map" resultType="com.hjcrm.system.entity.Menu">select  m.menuid,m.menuname from hj_menu m where m.menuparaid = #{mid}</select><!-- 根据菜单id,查询菜单信息 --><select id="queryMenuById" parameterType="java.util.Map" resultType="com.hjcrm.system.entity.Menu">select  m.menuid,m.menuname from hj_menu m where m.menuid = #{mid}</select><!-- 根据菜单ID,查询当前菜单是否存在权限分配 --><select id="isHasRoleMenu" parameterType="java.util.Map" resultType="com.hjcrm.system.entity.Role_menu">select rm.id from hj_role_menu rm where rm.menuid = #{mid}</select>
      

角色管理

跳转页面

    略,参考工程文件

查询

    略,参考工程文件

增加、修改

    略,参考工程文件

删除

    略,参考工程文件

###权限分配

  • 需求:

      1:选中角色,点击【权限分配】,弹出分配框2:展示所有的菜单信息,并树形分级展示3:如果之前已经分配过的菜单,则自动勾选复选框4:重新勾选菜单之后,点击【保存】按钮,则分配成功
    
  • 开发步骤:

      1:查询所有菜单信息,并且已经分配过的菜单,复选框自动勾选- 查询所有菜单信息list- 查询已经分配过的菜单信息listch- 遍历list和listch,如果listch中的菜单ID在list中存在,则把list.get(i).setSelected(true);设置为true即可- 遍历list的子菜单信息,和listch对比ID,如果存在,则listchilere.get(j).setSelected(true);子菜单设置为true即可- 最终返回list的json格式数据2:权限分配---保存菜单信息- 删除数据库中开始的角色对应的菜单信息- 保存一份最新的角色对应菜单信息数据即可
    
  • 示例代码:

    • RoleController.java:

        /*** 角色管理模块* @author likang* @date   2018-4-24 下午3:18:49*/@Controllerpublic class RoleController extends BaseController{@Autowiredprivate IRoleService roleService;@Autowiredprivate IMenuService menuService;/*** 跳转角色管理页面* @param model* @return*/@RequestMapping(value = "/system/roleMang.do",method = RequestMethod.GET)public String roleManger(Model model){if (UserContext.getLoginUser() != null) {return JumpViewConstants.SYSTEM_ROLE_MANAGE;}return JumpViewConstants.SYSTEM_LOGIN;}/*** 查询角色信息列表* @param request* @param currentPage 第几页* @param pageSize 每页多少条* @return*/@RequestMapping(value = "/role/queryAllRole.do",method = RequestMethod.GET)public @ResponseBody String queryAllRoles(HttpServletRequest request,Integer currentPage,Integer pageSize){List<Role> list = roleService.queryAllRoles(processPageBean(pageSize, currentPage));return jsonToPage(list);}/*** 权限分配---查询所有菜单信息,并且已经分配过的菜单,复选框自动勾选* @param request* @param roleid* @return*/@RequestMapping(value = "/rolemenu/queryAllMenuAndSelected.do",method = RequestMethod.GET)public @ResponseBody String queryAllMenuAndSelected(HttpServletRequest request,String roleid){if (StringUtils.isNotBlank(roleid)) {//查询所有菜单信息List<Menu> list = menuService.queryAllMenus();//1,2,3,4,5,6---分级展示//查询已经分配过的菜单信息List<Menu> listch = menuService.queryAlMenuSeleted(roleid);//1,2,3---不分级if (listch != null && listch.size() > 0) {for (int i = 0; i < list.size(); i++) {Long listid = list.get(i).getMenuid();//上一级的菜单IDfor (int j = 0; j < listch.size(); j++) {Long listchid = listch.get(j).getMenuid();if (listid == listchid) {list.get(i).setSelected(true);//                            continue;break;}}}for (int i = 0; i < list.size(); i++) {List<Menu> listchilere = list.get(i).getChildren();//一级菜单的子菜单for (int j = 0; j < listchilere.size(); j++) {Long listchilereID = listchilere.get(j).getMenuid();//子菜单的主键IDfor (int k = 0; k < listch.size(); k++) {Long listchid = listch.get(k).getMenuid();//所有的分配过的菜单IDif (listchilereID == listchid) {listchilere.get(j).setSelected(true);break;}}}}}//打钩return jsonToPage(list);}return ReturnConstants.PARAM_NULL;}/*** 权限分配---保存菜单信息*      思路:1:删除数据库中开始的角色对应的菜单信息*           2:保存一份最新的角色对应菜单信息数据即可* @param request* @param roleid* @param menuid* @return*/@RequestMapping(value = "/rolemenu/assignMenu.do",method = RequestMethod.POST)public @ResponseBody String assignMenu(HttpServletRequest request,String roleid,String menuid){if (StringUtils.isNotBlank(roleid)) {//删除数据库中开始的角色对应的菜单信息menuService.deleteRoleMenu(roleid);//2:保存一份最新的角色对应菜单信息数据即可Role_menu rm = new Role_menu();rm.setRoleid(Long.valueOf(roleid));for (String mid : menuid.split(",")) {rm.setMenuid(Long.valueOf(mid));menuService.saveRoleMenu(rm);rm.setMenuid(null);}return ReturnConstants.SUCCESS;}return ReturnConstants.PARAM_NULL;}}
      
    • IMenuService.java:

        /*** 菜单管理接口* @author likang* @date   2018-4-24 下午3:00:35*/public interface IMenuService {/*** 查询所有菜单信息* @return*/public List<Menu> queryAllMenus();/*** 新增、修改菜单信息* @param menu*/public void saveOrUpdateMenu(Menu menu);/*** 查询当前角色已经分配过的菜单信息列表* @param roleid 角色ID* @return*/public List<Menu> queryAlMenuSeleted(String roleid);/*** 根据角色ID,删除数据库中对应的菜单信息数据* @param roleid*/public void deleteRoleMenu(String roleid);/*** 保存角色-菜单关系数据* @param rm*/public void saveRoleMenu(Role_menu rm);}
      
    • MenuServiceImpl.java:

        @Service@Transactional(rollbackFor = Exception.class)public class MenuServiceImpl implements IMenuService {@AutowiredIDataAccess<Menu> menuDao;@AutowiredIDataAccess<Role_menu> rmDao;public List<Menu> queryAllMenus() {Map<String, Object> param = new HashMap<String, Object>();param.put("isparid", "true");List<Menu> list = menuDao.queryByStatment("queryAllMenus", param, null);if (list != null && list.size() > 0) {for (int i = 0; i < list.size(); i++) {Long menuid = list.get(i).getMenuid();param.clear();param.put("isparid", null);param.put("menuparaid", menuid);List<Menu> listch = menuDao.queryByStatment("queryAllMenus", param, null);list.get(i).setChildren(listch);}}return list;}public void saveOrUpdateMenu(Menu menu) {if (menu != null) {if (menu.getMenuid() != null) {//修改menu.setUpdate_id(UserContext.getLoginUser().getUserid());menu.setUpdate_time(new Timestamp(System.currentTimeMillis()));menuDao.update(menu);}else{//增加menu.setCreate_id(UserContext.getLoginUser().getUserid());menu.setCreate_time(new Timestamp(System.currentTimeMillis()));menuDao.insert(menu);}}}public List<Menu> queryAlMenuSeleted(String roleid) {Map<String, Object> param = new HashMap<String, Object>();param.put("roleid", roleid);List<Menu> listch = menuDao.queryByStatment("queryAlMenuSeleted", param, null);return listch;}public void deleteRoleMenu(String roleid) {if (StringUtils.isNotBlank(roleid)) {Map<String, Object> param = new HashMap<String, Object>();param.put("roleid", roleid);rmDao.deleteByStatment("deleteRoleMenu", param);}}public void saveRoleMenu(Role_menu rm) {if (rm != null) {rmDao.insert(rm);}}}
      
    • RoleMapper.xml:

        <mapper namespace="com.hjcrm.entity"><!-- 查询所有角色信息 --><select id="queryAllRoles" parameterType="java.util.Map" resultType="com.hjcrm.system.entity.Role">select rm.roleid,r.rolename,rm.menuid, group_concat(m.menuname separator ',') menunamefrom hj_role_menu rmleft join hj_role r on r.roleid = rm.roleidleft join hj_menu m on m.menuid = rm.menuidgroup by r.rolename</select><!-- 查询当前角色已经分配过的菜单信息列表 --><select id="queryAlMenuSeleted" parameterType="java.util.Map" resultType="com.hjcrm.system.entity.Menu">selectrm.menuid,rm.roleidfrom hj_role_menu rm where rm.roleid = #{roleid}</select><delete id="deleteRoleMenu" parameterType="java.util.Map">delete from hj_role_menu where roleid=#{roleid}</delete></mapper>
      

CRM第三方缓存技术

  • 开发步骤:

      1:在resource文件内,导入缓存的配置文件2:在事务开启执行之前,使用注解,进行数据缓存
    
  • 常用注解:

      //增加缓存@Cacheable(value="baseCache",key="'queryAllDepts'")其中value代表存放的缓存区域块名称key:代表区域块中缓存的唯一标识,可有三种取值方式:1:使用普通的字符串   2:使用方法参数中参数值 #参数名称     3:使用方法中实体对象的属性//更新缓存---写操作@CachePut(value = "",key = "")不管缓存是否有数据,都会更新一遍//更新缓存---根据条件更新@CacheEvict(value  ="",key="",allEntries=false,beforeInvocation = false)allEntries:true:更新所有的缓存区域块数据,false,则代表只会更新key值的数据beforeInvocation:true,代表是在方法执行之前进行更新数据,执行之后,不太操作缓存数据
    
  • 示例代码:

      参考配置文件applicationContext-ehcache.xml和ecache.xml
    
    • applicationcontext.xml:

        <!-- 引用外部配置文件信息 --><import resource="applicationContext-ehcache.xml"/>
      
    • DeptServceImpl.java:

        @Cacheable(value="baseCache",key="'queryAllDepts'")public List<Dept> queryAllDepts() {List<Dept> list = deptDao.queryByStatment("queryAllDepts", null, null);return list;}
      

调度任务Quartz

  • 开发步骤:

      1:导入调度任务的配置模块文件2:在spring的配置文件中,引入调度任务的配置信息
    
  • 示例代码:

    • applicationcontext.xml:

        <!-- 引用外部配置文件信息 --><import resource="applicationContext-quartz.xml"/>
      
    • applicationContext-quartz.xml:

        参考文件
      
    • 注意:

        cron表达式,可以百度,找一个在线生成器生成即可
      

百度编辑器(富文本编辑器)

    后台字段设计为:text

自定义注解

  • 注解的注解:

      1.@Target,修饰的对象范围2.@Retention,保留的时间长短3.@Documented,标记注解 公共API 没有成员4.@Inherited  标记注解 @Inherited阐述了某个被标注的类型是被继承的@Target说明了Annotation所修饰的对象范围1.CONSTRUCTOR:用于描述构造器2.FIELD:用于描述域3.LOCAL_VARIABLE:用于描述局部变量4.METHOD:用于描述方法5.PACKAGE:用于描述包6.PARAMETER:用于描述参数7.TYPE:用于描述类、接口(包括注解类型) 或enum声明@Retention定义了该Annotation被保留的时间长短取值(RetentionPoicy)有:1.SOURCE:在源文件中有效(即源文件保留)2.CLASS:在class文件中有效(即class保留)3.RUNTIME:在运行时有效(即运行时保留)
    
  • 代码示例:

    • Name.java:

        @Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Name {String nameValue() default "java16";}
      
    • NameEntity.java:

        public class NameEntity {//    @Name(nameValue = "java16-1")@Nameprivate String username;private String phone;private String email;增加getter和setter方法}
      
    • NameTest.java:

        public class NameTest {public static void main(String[] args) {getValue(NameEntity.class);}public static void getValue(Class clazz){if (clazz != null) {Field[] fields = clazz.getDeclaredFields();//获取对象属性for (Field field : fields) {if (field.isAnnotationPresent(Name.class)) {//判断当前field属性是否是Name注解Name name = field.getAnnotation(Name.class);System.out.println(name.nameValue());}}}}}
      

excel导入、导出

  • 解析:POI

      本地---数据库
    
  • 导入:

      1:读取本地excel文件2:将文件上传到应用服务器tomcat3:读取服务器中的excel文件数据,并将数据存放到数据库
    
  • 导出:

    • 数据库—本地

      1:获取要导出的数据
      2:在应用服务器中创建一个空的excel文件,并将导出的数据写入
      3:将应用服务器中excel文件,下载本地

项目总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C8dkqVys-1602315802001)(https://i.imgur.com/Mu8amo4.png)]

基于 SSM 的 CRM 客户管理系统相关推荐

  1. 基于javaweb的crm客户管理系统(java+ssm+jsp+mysql+redis)

    基于javaweb的crm客户管理系统(java+ssm+jsp+mysql+redis) 运行环境 Java≥8.MySQL≥5.7.Tomcat≥8 开发工具 eclipse/idea/myecl ...

  2. [附源码]Java计算机毕业设计SSM公司CRM客户管理系统

    项目运行 环境配置: Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclis ...

  3. ssm项目——CRM客户管理系统开发准备

    目前项目放在github上,需要的同学可以直接取git clone下来,https://github.com/PAcee1/crm 这个项目是学习完spring,springmvc,mybatis后为 ...

  4. 基于java的CRM客户关系管理系统的设计与实现

    本科毕业设计(论文) 题 目: 基于java的CRM客户关系管理系统的设计与实现 专题题目: 说 明 请按以下顺序编排: 封面 任务书 开题报告 中外文摘要及关键词 目录 正文 附录(可选) 参考文献 ...

  5. 基于java的CRM客户关系管理系统的设计和实现

    基于java的CRM客户关系管理系统的设计和实现这个系统开发的开发环境: 开发工具:MyEclipse2010版数据库:MySql+HeidiSqlJDK:MyEclipse2010自带的JDK1.7 ...

  6. 基于javaweb的crm客户关系管理系统(java+springboot+echarts+freemarker+layui+mysql)

    基于javaweb的crm客户关系管理系统(java+springboot+echarts+freemarker+layui+mysql) 运行环境 Java≥8.MySQL≥5.7 开发工具 ecl ...

  7. 【超详细】SSM框架项目实战|Spring+Mybatis+Springmvc框架项目实战整合-【CRM客户管理系统】——课程笔记

    相关资料网盘链接: CRM客户管理系统资料 提取码 :0u04 P1--CRM阶段简介: web项目开发:如何分析,设计,编码,测试.        形成编程思想和编程习惯. P2--CRM的技术架构 ...

  8. JAVA毕业设计公司CRM客户管理系统计算机源码+lw文档+系统+调试部署+数据库

    JAVA毕业设计公司CRM客户管理系统计算机源码+lw文档+系统+调试部署+数据库 JAVA毕业设计公司CRM客户管理系统计算机源码+lw文档+系统+调试部署+数据库 本源码技术栈: 项目架构:B/S ...

  9. 帮管客CRM客户管理系统 v3.6.0

    介绍: 帮管客CRM客户管理系统基于先进的CRM营销理念设计,集客户档案.销售记录.业务往来于一身,以凝聚客户关系.提升资源价值为核心,将潜在客户变为现实客户.从而提升销售量.提高用户的满意度,并增加 ...

最新文章

  1. SQL Server中灾难时备份结尾日志(Tail of log)的两种方法
  2. maven只打包java目录_ssm项目中maven对resources目录打包的路径_默认路径,自定义路径...
  3. python字符串和字典
  4. 获取程序代码块资源消耗
  5. 【LeetCode】103# 二叉树的锯齿形层次遍历
  6. mysql5.5 datetime默认值不能为NOW或者CURRENT_TIMESTAMP
  7. 关于IIS不能浏览ASP网页 和不能浏览后台(转)
  8. html输入输出文件,学习如何用 C 语言来进行文件输入输出操作
  9. ssm-学子商城-项目第一天
  10. 如何在Windows Server 2003下安装NetBEUI
  11. 怎么增加C盘空间大小,教你如何调整C盘容量【图文教程 无损数据】分区助手如何给磁盘分区 分区助手使用方法教程
  12. 4.2 制定项目章程
  13. Linux驱动之设备树(设备树下的LED驱动实验)
  14. 基于MATLAB的一维条码二维码识别
  15. QRCode.js:使用 JavaScript 生成二维码
  16. (附源码)springboot物联网智能管理平台 毕业设计 211120
  17. tinyproxy王卡免流配置_大王卡tiny免流模式
  18. Linux:VNC桌面锁屏问题处理
  19. 省赛题目(4月23日)
  20. 如果一份工作让你时常感到焦虑,你会不会立马辞职?

热门文章

  1. 关于jsp中java代码的使用;
  2. 重庆航天职业技术学院计算机宿舍,2019年重庆航天职业技术学院寝室宿舍条件与学校食堂环境图片...
  3. 虚拟化技术 — 硬件辅助的虚拟化技术
  4. TS文件下载器(按照M3U8列表 批量下载)
  5. 二、流水线的执行流程
  6. 公众号如何运营?教你几招超实用的公众号运营方法
  7. 跨境物流的主要操作流程是怎样的?
  8. 教你如何正确反编译apk
  9. monkeyrunner的使用
  10. Java实现文件查重去重