[关闭]
@sqzrcc 2015-05-13T06:52:04.000000Z 字数 5498 阅读 1756

公司目前架构问题


漏洞

没办法在一个方法添加事务锁

问题

目前使用的Imode,没办法在一个service方法上加锁,正常的事务锁情况:

  1. // 此处加锁,如果有其他线程要操作,必须以下这些方法执行完后
  2. xxx.insert(xxx);
  3. xxxx.update(xxxx);
  4. xxxxx.delete(xxxxx);
  5. // 如果没有错误,则提交,如果有错误回滚

正常的请求和恶意的请求:
恶意请求

解决办法

修改Imodel源码,但具体要怎么做,不清楚,对orm框架的源码还没做过了解


itable导致的sql注入

目前项目中大量的表格都是通过itable来请求Json接口来进行读取数据展示,涉及到几个问题

大部分逻辑操作都在js中,用户可任意更改:

运行流程:

itable 初始化流程:
定义好一个table,在code处指定要执行的sql代码(在后台配置),itable初始化时去Json接口读取数据来进行展示
在随诊处,读取当前医生的所有随诊:

  1. <table id="followup_table" class="table table-striped"
  2. model="PhrFollowUp" code="phrFollowUp" search="true" refresh="true"
  3. edit="true" remove="true" pageSize=10>
  4. <thead>
  5. <tr>
  6. <th field="id" style="display: none;">ID</th>
  7. <th field="personName">姓名</th>
  8. <th field="diagnosis">诊断</th>
  9. <th field="startdate">发起时间</th>
  10. <th field="status" select="true">完成状态</th>
  11. <th field="enddate">完成时间</th>
  12. <th type="render" render="contentTemName">随诊内容</th>
  13. <th type="render" render="programTemName">指导方案</th>
  14. <th field="followupTime">随诊频率</th>
  15. <th field="tipss">提醒方式</th>
  16. <th type="edit">操作</th>
  17. </tr>
  18. </thead>
  19. </table>

然后通过js去初始化这个table:

  1. var followup_table = new iTables("#followup_table", {
  2. doctorId: loginId // 传入当前登陆的用户
  3. }, function () {
  4. });

对应的会去搜索在后台配置的code为phrFollowUp的sql语句:

  1. select t.*,to_char(trunc (t.start_date),'yyyy-mm-dd') as startdate,to_char(trunc (t.end_date),'yyyy-mm-dd') as enddate,e.name as doctor_name,p.name as
  2. person_name,m1.name as content_tem_name,m2.name as program_tem_name
  3. from phr_follow_up t
  4. left join md_employee e on e.id = t.doctor_id
  5. left join md_person p on p.id = t.md_person_id
  6. left join emr_template m1 on m1.id = t.content_tem_id
  7. left join emr_template m2 on m2.id = t.program_tem_id
  8. where t.doctor_id != '0'

然后会自动拼接条件:

  1. where t.doctor_id = 4 // js中传入进来的id

存在的问题:

由于大部分操作都在js中去操作,js又是运行在用户游览器上的,用户可以任意去修改代码,或者抓包查看itable的请求来达到读取任意医生的信息:
一个正常合法的接口请求:

  1. http://www.oijiankang.com//crud/json!query?config.sqlCode=phrPersonAction&doctorId=4

用户可以任意修改doctorId的值来达到获取任意医生的信息
一个非法的请求

  1. http://www.oijiankang.com//crud/json!query?config.sqlCode=phrPersonAction&doctorId=5 // 或者 6 7 8 9 就可以读取对应医生的随诊信息

等等...
由于是统一接口,甚至可以未登陆就进行接口的请求,或者一个居民的权限,也能进行请求.
由于在curd/json中有些参数还可以自己传sql过去执行,导致一个sql注入的问题,正常的sql:

  1. select * from md_person where id = 4 //(where id = 4)是由前台传过来的
  2. //如果传过来的是(where id = 4 union select password from admin)

更多的gbk宽字符注入... utf-8注入等..

解决办法

  1. 在接口处进行过滤(权限判断),但存在的问题如下:
    • sqlcode 是用户自己添加的sql,无法知道具体的sql是要什么权限来进行运行
    • 关于传入的参数,统一接口没办法知道你具体需要什么参数,可能有些地方需要账户id,有些地方又需要基本信息id,甚至其他信息
  2. 所有的itable都去请求Action,Action处理好所有数据后返回给用户,用户无法操作Action中的属性(比如获取当前医生的病人列表,目前的做法是通过统一的接口去执行sql语句,通过http(/crud/json!query?config.sqlcode=getPatients&doctorId=5),这样就拿到医生id为5的病人了.用户可以任意的去修改doctorid的值),如果交给action中,通过REST API GET /patients在Action中再去读取当前登陆的医生id,但存在的问题:
    • 工作量过大,所有使用接口的地方都必须废弃
    • 需要对itable底层进行彻底的修改
  3. 所有的数据都由后台处理好以后,再显示。目前的流程: 定义好table -> 初始化table -> table通过接口去取数据套上 。修改成: Action解析jsp中就去把table的数据就给套上,这样做的好处:
    • 会非常灵活,基本能适应所有需求
    • 效率会高,在itable中如果遇到一条sql语句无法取完数据的时候,需要去通过render(单独写一个方法去处理此单元格的数据)去获取。一个简单的例子: 要读取一个团队中拥有的所有病人,如果这个逻辑比较复杂.我们通过定义render 每一行数据都带上自己的团队id 去请求一个 GET /getTeamNumber?teamId=5如果有100个团队,在初始化这个的时候会去请求一百次读取团队人数的请求,开销非常大,如果交给Action,可以通过关联关系一条sql语句查询出来,然后一个用struts2的标签一个size属性就能取出
      缺点:
    • 还是修改量会非常大,基本会废弃掉itable
    • 可能需要修改数据库持久化(目前没有关联关系的自动处理)
  4. 对每一个请求都写一个权限过滤 验证层,由验证层验证过后再提交给后台统一接口进行处理。这样做的问题也是非常繁琐,开发效率会大大降低,改动也不方便

项目中已经大量使用拼接sql的方式

问题:

sql拼接
类似这样的代码,非常多,会导致sql注入,在Imodel中有提供更好的方式进行sql语句的参数化处理,但是目前项目看来基本没用

解决办法

对所有类似拼接sql的代码都进行更改,工程量也非常的大


itable没有验证层,能任意插入数据

问题

itable -> 本地js验证 -> 接口 -> 提交数据库
中间没有服务器端的验证,这样我们可以任意的修改任何东西:
js限制
修改http请求的数据:
修改http数据
修改提交
很容易就把前端验证给绕过了

解决办法

将所有的请求不仅在js验证,还要在后端验证,目前项目中也没有成熟的统一验证框架,在某些需要验证的地方代码:

  1. if (authService.getMemberCountByMobile(mobile) > 0) {
  2. jsonInfo.put("flag", false);
  3. jsonInfo.put("message", "手机号已被注册,请重输入");
  4. jsonInfo.put("field", "mobile");
  5. this.renderJson(jsonInfo);
  6. return;
  7. }
  8. if (username.length() < 4 || username.length() > 16) {
  9. jsonInfo.put("flag", false);
  10. jsonInfo.put("message", "用户名长度错误,请输入大于4位小于16位的字符");
  11. jsonInfo.put("field", "username");
  12. this.renderJson(jsonInfo);
  13. return;
  14. }
  15. if (!RegexUtils.checkUsername(member.getUsername())) {
  16. jsonInfo.put("flag", false);
  17. jsonInfo.put("message", "用户名只能包括中文字、英文字母、数字和下划线");
  18. jsonInfo.put("field", "username");
  19. this.renderJson(jsonInfo);
  20. return;
  21. }

代码很繁琐,如果是使用验证框架:

  1. @Length(max=20)
  2. @NotNull
  3. public String getCountry() {
  4. return country;
  5. }
  6. @Length(max=5, message="{long}")
  7. @Pattern(regex="[0-9]+")
  8. @NotNull
  9. public String getZip() {
  10. return zip;
  11. }

在model的层面做验证,不用每个需要的地方都去请求

开发的问题

Imodel没办法处理关联关系

在hibernate里处理关联关系只需要在model中定义好相关的字段,并把需要关联的字段修改为对象,配置好配置文件,即可很方便的进行关联查询,关于效率方便 还有更多的lazy load ,二级缓存等..

基于Model,对于需要自定义字段很不方便

问题

和hibernate一样,都是基于model来进行orm的,如果你需要返回的json字符串中不包含在数据库中(假如请求一个团队,返回的json串中会多一个团队总人数的字段,然而这个字段并不存在在数据库中,也就不会在model里),那么就需要去自定义一个model来做,这样的开发效率会比较低,代码量很大,据我了解,目前医生phr中病人列表就使用了这个做法

目前比较好的做法

使用ActiveRecord模式来进行的关联查询:

  1. public void relation() {
  2. String sql = "select b.*, u.user_name from blog b inner
  3. join user u on b.user_id=u.id where b.id=?";
  4. Blog blog = Blog.dao.findFirst(sql, 123);
  5. String name = blog.getStr("user_name");
  6. }

获取基于model:

  1. public class Blog extends Model<Blog>{
  2. public static final Blog dao = new Blog();
  3. public User getUser() {
  4. return User.dao.findById(get("user_id"));
  5. }
  6. }
  7. public class User extends Model<User>{
  8. public static final User dao = new User();
  9. public List<Blog> getBlogs() {
  10. return Blog.dao.find("select * from blog where user_id=?",
  11. get("id"));
  12. }
  13. }

将model里设置属性的方式改为通过key/value的方式,这样就会非常灵活的去增删字段,如果使用model设置属性的方式,要想使用动态添加属性,则需要动态代码生成[1],使用asm或者javassist库支持。

连接池的问题

问题

偶尔会出现一直建立连接池的状况,导致项目非常慢,频繁建立连接,对oracle的消耗也是非常严重的。

解决办法

目前已经有很多连接池解决方案了,java中比较出名的连接池:Druid,来自阿里巴巴开源的连接池,自称是Druid是Java语言中最好的数据库连接池。Druid能够提供强大的监控和扩展功能。.但目前公司没人能做进行连接池的替换。

架构建议

mvc框架

struts2

优点:
大家都会用,用户量大,文档支持多
缺点:
开发繁琐 效率低

jfainl

优点:
自带orm功能,插件灵活,能很灵活的处理好关联关系,支持Db + Record模式,拥有strtus2绝大部分功能 开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展、Restful
缺点:
个人作品,作者可能随时不进行维护

spring mvc

优点:
灵活 开发效率快 用户量大 有spring支持
缺点:
学习成本比较高


[1] 通过程序来生成代码是Java平台的固有特性。当Java程序编译的时候,Java编译器生成的是字节码而不是可执行程序。字节码是Java特有的格式,它本身并没有太大的用处。为了能执行字节码,它会在运行时被JVM的just-in-time编译器翻译成本地的机器代码。
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注