### 概况
- 系统采用 maven + springboot + beetl 开发
- 文件结构按规范的 maven-archetype-webapp 原型构建


### 代码解析
- `pom.xml` 中以资源显示声明让工程能够直接以maven命令打包
    ```
    <build>
        <finalName>${artifactId}-${version}</finalName>
        <sourceDirectory>src/main/java</sourceDirectory>
        <resources>
              <resource>
                  <directory>src/main/resources</directory>
              </resource>
              <resource>
                  <directory>src/main/webapp</directory>
              </resource>
        </resources>
    ```
- web 页面资源文件放在 `src/main/webapp` 中
  > 由于在开发时并没有完全的按规范将 页面文件和页面引用文件（js/css/img/png 等）分割开，\
  > 以致无法用通配符区分请求路径或资源路径，如果统一拦截所有则会视资源为请求以至样式等失效\
  > 这里统一用 aop 拦截，建议 webapp 中将资源文件统一放在一个目录，和servlet 请求路径严格区分\
  > 因为 请求是 restful 风格请求所以会造成 资源文件和请求 路径的混淆
- redis 序列化和反序列化
  - redis 中用 fastJson 代替序列化工具
  - 规范采用对象序列化为字符串存储，取出字符串并反序列化为对象使用
  - 用字符串保存对象，可以避免反序列化时的类转换问题
  - 范例：
    - 序列化
      ```
          public void saveEntityToJson(String key, Object obj, long expire, TimeUnit timeUnit) {
            String value = JSONObject.toJSONString(obj);
            stringRedisTemplate.opsForValue().set(key, value);
            if (expire > 0) {
                stringRedisTemplate.expire(key, expire, timeUnit);
            }
        }
      ```
    - 反序列化
    ```
        public <T> T getEntity(String key, TypeReference<T> typeReference) {
        String json = stringRedisTemplate.opsForValue().get(key);
        if (null == json) { return null; }
        T result = JSONObject.parseObject(json, typeReference);
        return result;
    }
    ```
- 远程请求 RestfulTemplate
  - 注意
  - 概述
    - 由于 openapi 的验证和请求体相关，所以应注意以下几点：
      1. 生成 sign 时所使用的对象序列化工具 A
      2. restfulTemplate 请求的参数序列化工具 B
      3. openapi 中接收信息序列化工具 C
    > 上面提到的 A，B，C 应该是一致的，因为其中一个不一样，之后的验证或生成的结果都不一样\
    > 比如，A 为标准序列化 生成了 sign B 在序列化参数时有 空格或换行制表符\
    > 那么 C 在接收到参数时是有特殊字符的，在生成签名验证就不能一致(因为形成 sign 过程中序列化请求体可能有`\t或\n或空格等，而请求工具序列化后却没有以致不一致而签名验证失败`)，重要！！重要！！
  - 代码：
    - 此处屏蔽掉了特征输出，避免了制表换行符的出现
      ```
      @Bean
        public RestTemplate getRestful() {
            FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
            FastJsonConfig fastJsonConfig = new FastJsonConfig();
            // fastJsonConfig.setSerializerFeatures();
            fastConverter.setFastJsonConfig(fastJsonConfig);
            fastConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON_UTF8));
    
            RestTemplate restTemplate = new RestTemplateBuilder()
                    .additionalMessageConverters(fastConverter)
                    .build();
            return restTemplate;
        }
      ```
- AOP 拦截，多重逻辑处理
  - 拦截了所有的控制层
  ```
  @Around("execution(public * com.zzwtec.third.action..*.*(..))")
  
    @Autowired
    private WorkScheduling workScheduling;

    @Around("execution(public * com.zzwtec.third.action..*.*(..))")
    public Object handleBase(ProceedingJoinPoint pjp) {

        Object result;
        try {
            workScheduling.baseBefore(pjp);
            String timeOutUrl = workScheduling.checkTimeOut();
            if (null != timeOutUrl) { return timeOutUrl; }

            result = pjp.proceed();
        } catch (BizException e) {
            logger.warn(e.getMessage());
            workScheduling.handleError(e);
            return null;
        } catch (Throwable e) {
            logger.error(e.getMessage(), e.getCause());
            workScheduling.handleError(null);
            return null;
        } finally {
            workScheduling.baseFinally(pjp);
        }

        return result;
    }

  ```
  - 由`com.zzwtec.third.aop.aspect.work.WorkScheduling`统一调度拦截逻辑
  - 由`com.zzwtec.third.aop.aspect.work.WorkAbstract`落实逻辑细节
    - 登录超时
    - 固定地址放行
    - 请求id处理

- 其它
  - com.zzwtec.third.common.OpenApiUrl 从配置文件中映射 openapi 的域名和相应的业务地址
  - 配置的 redis 是单结点单服务形式（非集群）


## 关于打包
- 概述
  1. 总后台由管理员填写相应的配置信息，后台用传入的信息结合 beetl 模板生成对相应的占位符进行替换，生成文件配置文件
     - 有两个配置文件，一个是项目配置文件，一个是微信配置文件
     - 配置文件名每次生成唯一
     - 文件放置位置是在自动生成的目录里
        - 目录名在指定固定目录下，且名字唯一
        - 目录名根 项目配置文件名一致
     - 以此避免多线程操作的互扰
  2. 把生成的配置文件在指定的目录里的动态生成的目录下：即 `/固定目录/动态生成目录/`
  3. 紧接着后台调用脚本，传入两个参数（关于动态生成的目录名，和微信配置名）
  4. 脚本返回 true 或 false 表示打包成功或失败
  5. 再读取生成的 jar 文件存放到 ftp，返回 web 下载地址
  6. 如果用户点击下载，根据下载地址下载文件，后台删除相应的文件

- 目录地址
  - 命令 `sh 脚本路径全名 动态项目名 微信配置文件名`
  - /opt/template/ # 项目源码存放地址
  - /opt/template/config/ # 配置文件模板存放地址 ( 脚本不管此目录）
  - /opt/template/generate_config/动态项目名/动态项目名.yml # 生成的配置文件
  - /opt/template/generate_config/动态项目名/微信配置文件名.txt # 微信的配置文件
  - /opt/template/company/动态项目名/target/动态项目名.jar # 打包好后存放的 动态项目名.jar 的位置 


  
  