Merge pull request #9 from hs-web/2.2-SNAPSHOT

2.2 snapshot
This commit is contained in:
zhōuhào
2016-12-04 13:16:40 +08:00
committed by GitHub
17 changed files with 375 additions and 138 deletions

View File

@@ -1,8 +1,8 @@
# 使用hsweb
项目java8开发,使用maven进行管理.
## 使用
配置pom.xml
## 1.配置maven pom.xml
引入私服
```xml
<!--统一依赖管理-->
<dependencyManagement>
@@ -34,16 +34,10 @@
<!--设置了dependencyManagement,可以不指定版本号-->
<dependency>
<groupId>org.hsweb</groupId>
<artifactId>hsweb-web-controller</artifactId>
</dependency>
<dependency>
<groupId>org.hsweb</groupId>
<artifactId>hsweb-web-service-simple</artifactId>
</dependency>
<dependency>
<groupId>org.hsweb</groupId>
<artifactId>hsweb-web-dao-mybatis</artifactId>
<artifactId>hsweb-web-start</artifactId>
</dependency>
```
完整配置,可参照 [demo](https://github.com/hs-web/hsweb-demo/blob/master/pom.xml)
[查看api](2.API.md)

View File

@@ -1,53 +1,17 @@
# hsweb 常用api
## 开发
1. [创建通用crud](./create-crud.md)
2. [使用通用crud](./use-crud.md)
## CRUD
[如何创建通用crud](./create-crud.md)
查询:
```java
import static MyBean.Property.*; //属性名
myService.createQuery()
.where(name,"admin")
.or(name,"root")
.list(); //list(), list(0,10), single(),total();
//or
myService.createQuery().fromBean(myBean)
.where(name)
.or(name)
.list();
// 复杂查询条件
// 等同sql where name is not null and (name like '李%' or name like '周%') and age >0
// 参数全部预编译,不用担心注入
myService.createQuery()
.where().notNull(name)
.nest().or().like$(name,"").or().like$(name,"").end()
.and().gt(age,10).list();
//自定义sql条件
myService.createQuery()
.where()
.and().sql("name !=''")
.or().sql("age < #{age}",{age:10})// 使用预编译方式
.or().sql("age = #{[0]}",Arrays.asList(20))//获取集合参数
.or().sql("age > ? and (age <?)",60,100)//使用参数列表方式
.list();
```
修改,支持和query一致的条件
```java
import static MyBean.Property.*;
myService.createUpdate()
.set(status,1)
.where(id,"data-id").exec();
// or
myService.createUpdate(myBean).fromBean().where(id).exec();
```
删除,支持和query一致的条件
```java
import static MyBean.Property.*;
myService.createDelete().where(id,"data-id").exec();
```
## 使用
1. [权限管理](): 权限资源-角色-用户.
2. [配置管理](): kv结构,自定义配置.可通过此功能配置数据字典.
3. [脚本管理](): 动态脚本,支持javascript,groovy,java动态编译执行.
4. [表单管理](): 动态表单,可视化设计表单,自动生成数据库以及系统权限.无需重启直接生效.
5. [模块设置](): 配合动态表单实现表格页,查询条件自定义.
6. [数据库维护](): 在线维护数据库,修改表结构,执行sql.
7. [数据源管理](): 配置多数据源.
8. [代码生成器](): 在线生成代码,打包下载.可自定义模板.
9. [定时任务](): 配置定时任务,使用动态脚本编写任务内容.
10. [系统监控](): 监控系统资源使用情况.
11. [缓存监控](): 监控缓存情况.
12. [访问日志](): 记录用户每次操作情况

4
doc/README.md Normal file
View File

@@ -0,0 +1,4 @@
# hsweb-framework
1. [安装使用](1.安装使用.md)
2. [API](2.API.md)

160
doc/create-crud.md Normal file
View File

@@ -0,0 +1,160 @@
# 创建通用增删改查功能
## 1. 实体
目前hsweb只有一种实体:PO。统一继承 `GenericPo`
新建实体`org.hsweb.demo.bean.test.MyTest`如下:
```java
public class MyTest extends GenericPo{
private String name;
private int age;
//由于查询使用动态参数,使用此方式定义属性名。方便统一维护
public interface Property extends GenericPo.Property{
String name = "name";
String age = "age";
}
}
```
建立数据库表(hsweb暂未使用jpa等方式自动建表):
方式1: 编辑`resources/scripts/initialize.groovy` (此脚本在项目首次运行时执行) 并加入
```groovy
database.createOrAlter("s_test")
.addColumn().name("u_id").alias("id").comment("ID").jdbcType(JDBCType.VARCHAR).length(32).primaryKey().commit()
.addColumn().name("age").alias("age").comment("年龄").jdbcType(JDBCType.DECIMAL).length(16,0).commit()
.addColumn().name("name").alias("name").comment("姓名").jdbcType(JDBCType.VARCHAR).length(128).commit()
.comment("测试").commit();
```
方式2: 如果系统已经初始化,则需要手动建立表结构,或者使用更新版本的方式进行初始化:
假设当前版本为 1.0.0, 升级为 1.0.1,则新建文件 ``resources/scripts/upgrade/1.0.1.groovy`` 并加入方式1中的脚本内容.
在启动后,更新版本时会自动执行此脚本.
## 2. dao 接口
定义接口 ``org.hsweb.demo.dao.test.MyTestMapper``
(增删改查 继承GenericMapper即可)
```java
public interface MyTestMapper extends GenericMapper<MyTest, String> {
}
```
## 3.mybatis dao实现
mybatis 采用配置文件(xml)的方式
新建xml配置`resources/org/hsweb/demo/mappers/test/MyTestMapper`如下:
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://www.mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.hsweb.demo.dao.test.MyTestMapper">
<resultMap id="TestResultMap" type="org.hsweb.demo.bean.test.MyTest">
<id property="id" column="u_id" javaType="string" jdbcType="VARCHAR"/>
<result property="name" column="name" javaType="String" jdbcType="VARCHAR"/>
<result property="age" column="age" javaType="int" jdbcType="INTEGER"/>
</resultMap>
<!--用于动态生成sql所需的配置-->
<sql id="config">
<!--动态sql使用resultMapId对应的配置来生成sql-->
<bind name="resultMapId" value="'TestResultMap'"/>
<bind name="tableName" value="'s_test'"/>
</sql>
<insert id="insert" parameterType="org.hsweb.web.bean.common.InsertParam">
<include refid="config"/>
<include refid="BasicMapper.buildInsertSql"/>
</insert>
<delete id="delete" parameterType="org.hsweb.web.bean.common.DeleteParam">
<include refid="config"/>
<include refid="BasicMapper.buildDeleteSql"/>
</delete>
<update id="update" parameterType="org.hsweb.web.bean.common.UpdateParam">
<include refid="config"/>
<include refid="BasicMapper.buildUpdateSql"/>
</update>
<select id="selectByPk" parameterType="string" resultMap="TestResultMap">
select * from s_test where u_id=#{id}
</select>
<select id="select" parameterType="org.hsweb.web.bean.common.QueryParam" resultMap="TestResultMap">
<include refid="config"/>
<include refid="BasicMapper.buildSelectSql"/>
</select>
<select id="total" parameterType="org.hsweb.web.bean.common.QueryParam" resultType="int">
<include refid="config"/>
<include refid="BasicMapper.buildTotalSql"/>
</select>
</mapper>
```
## 4. service 接口
定义service接口 ``org.hsweb.demo.service.test.MyTestService``
(增删改查 GenericService)
```java
public interface MyTestService extends GenericService<MyTest, String> {
}
```
## 5. service 实现
定义service实现类 ``org.hsweb.demo.service.test.impl.SimpleMyTestService``
继承 AbstractServiceImpl
```java
@Service("testService")
public class SimpleMyTestService extends AbstractServiceImpl<MyTest, String> implements MyTestService {
@Autowired
private MyTestMapper myTestMapper;
//AbstractServiceImpl 使用GenericMapper的实现类进行CRUD操作
@Override
protected MyTestMapper getMapper() {
return myTestMapper;
}
}
```
## 6. controller
定义Controller ``org.hsweb.demo.controller.test.MyTestController``
```java
@RestController
@RequestMapping("/myTest")
@Authorize(module = "myTest") //权限验证
@AccessLogger("测试模块") //访问日志描述
public class MyTestController extends GenericController<MyTest, String> {
@Autowired
MyTestService myTestService;
@Override
protected MyTestService getService() {
return myTestService;
}
}
```
## 7. 添加权限
1、启动并登录系统,进入系统管理-权限管理模块加入对应的权限,重新登录即可使用了。
2、或者参照初始化表的方式,以脚本的方式进行初始化,如:
```groovy
def module= [u_id: 'myTest', name: '测试', uri: 'admin/myTest/list.html', icon: '', parent_id: '-1', remark: '', status: 1, optional: '[{"id":"M","text":"菜单可见","checked":true},{"id":"import","text":"导入excel","checked":true},{"id":"export","text":"导出excel","checked":true},{"id":"R","text":"查询","checked":true},{"id":"C","text":"新增","checked":true},{"id":"U","text":"修改","checked":true},{"id":"D","text":"删除","checked":false}]', sort_index: 1];
database.getTable("s_modules").createInsert().value(module).exec();
```
## 8. 感觉太麻烦?
**使用在线代码生成器,一键生成上诉全部代码!**

48
doc/use-crud.md Normal file
View File

@@ -0,0 +1,48 @@
# 1.查询:
```java
import static MyBean.Property.*; //属性名
myService.createQuery()
.where(name,"admin")
.or(name,"root")
.list(); //list(), list(0,10), single(),total();
//or
myService.createQuery().fromBean(myBean)
.where(name)
.or(name)
.list();
// 复杂查询条件
// 等同sql where name is not null and (name like '李%' or name like '周%') and age >0
// 参数全部预编译,不用担心注入
myService.createQuery()
.where().notNull(name)
.nest().or().like$(name,"").or().like$(name,"").end()
.and().gt(age,10).list();
//自定义sql条件
myService.createQuery()
.where()
.and().sql("name !=''")
.or().sql("age < #{age}",{age:10})// 使用预编译方式
.or().sql("age = #{[0]}",Arrays.asList(20))//获取集合参数
.or().sql("age > ? and (age <?)",60,100)//使用参数列表方式
.list();
```
# 2.修改,支持和query一致的条件
```java
import static MyBean.Property.*;
myService.createUpdate()
.set(status,1)
.where(id,"data-id").exec();
// or
myService.createUpdate(myBean).fromBean().where(id).exec();
```
# 3.删除,支持和query一致的条件
```java
import static MyBean.Property.*;
myService.createDelete().where(id,"data-id").exec();
```

View File

@@ -26,22 +26,19 @@ import org.hsweb.web.bean.po.GenericPo;
public class DataSource extends GenericPo<String> {
//数据源名称
@NotBlank
private String name;
//驱动
@NotBlank
private String driver;
private String name;
//url
@NotBlank
private String url;
private String url;
//用户名
@NotBlank
private String username;
private String username;
//测试sql
private String testSql;
private String testSql;
//密码
private String password;
private String password;
//是否启用
private int enabled;
private int enabled;
//创建日期
private java.util.Date createDate;
@@ -125,13 +122,6 @@ public class DataSource extends GenericPo<String> {
this.createDate = createDate;
}
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
@@ -151,52 +141,39 @@ public class DataSource extends GenericPo<String> {
public int getHash() {
StringBuilder builder = new StringBuilder();
builder.append(driver).append(url).append(username).append(password).append(enabled);
builder.append(url).append(username).append(password).append(enabled);
return builder.toString().hashCode();
}
public interface Property extends GenericPo.Property{
/**
*
* @see DataSource#name
*/
String name="name";
/**
*
* @see DataSource#driver
*/
String driver="driver";
/**
*
* @see DataSource#url
*/
String url="url";
/**
*
* @see DataSource#username
*/
String username="username";
/**
*
* @see DataSource#testSql
*/
String testSql="testSql";
/**
*
* @see DataSource#password
*/
String password="password";
/**
*
* @see DataSource#enabled
*/
String enabled="enabled";
/**
*
* @see DataSource#createDate
*/
String createDate="createDate";
}
public interface Property extends GenericPo.Property {
/**
* @see DataSource#name
*/
String name = "name";
/**
* @see DataSource#url
*/
String url = "url";
/**
* @see DataSource#username
*/
String username = "username";
/**
* @see DataSource#testSql
*/
String testSql = "testSql";
/**
* @see DataSource#password
*/
String password = "password";
/**
* @see DataSource#enabled
*/
String enabled = "enabled";
/**
* @see DataSource#createDate
*/
String createDate = "createDate";
}
}

View File

@@ -8,6 +8,7 @@ import org.hsweb.web.bean.po.logger.LoggerInfo;
import org.hsweb.web.bean.po.user.User;
import org.hsweb.web.core.exception.BusinessException;
import org.hsweb.web.core.logger.AccessLoggerPersisting;
import org.hsweb.web.core.logger.Slf4jAccessLoggerPersisting;
import org.hsweb.web.core.message.FastJsonHttpMessageConverter;
import org.hsweb.web.core.message.ResponseMessage;
import org.hsweb.web.core.utils.WebUtil;
@@ -29,6 +30,11 @@ public class AopAccessLoggerResolverAutoConfiguration {
return new AopAccessLoggerResolverConfiguration();
}
@Bean
public Slf4jAccessLoggerPersisting slf4jAccessLoggerPersisting() {
return new Slf4jAccessLoggerPersisting();
}
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE)
static class AopAccessLoggerResolverConfiguration extends org.hsweb.web.core.logger.AopAccessLoggerResolver {

View File

@@ -15,9 +15,10 @@ public class Slf4jAccessLoggerPersisting implements AccessLoggerPersisting {
@Override
public void save(LoggerInfo loggerInfo) {
if (fastJsonHttpMessageConverter == null)
logger.info(JSON.toJSONString(loggerInfo));
else
logger.info(fastJsonHttpMessageConverter.converter(loggerInfo));
if (logger.isInfoEnabled())
if (fastJsonHttpMessageConverter == null)
logger.info(JSON.toJSONString(loggerInfo));
else
logger.info(fastJsonHttpMessageConverter.converter(loggerInfo));
}
}

View File

@@ -6,7 +6,6 @@
<resultMap id="DataSourceResultMap" type="org.hsweb.web.bean.po.datasource.DataSource">
<id property="id" column="u_id" javaType="string" jdbcType="VARCHAR"/>
<result property="name" column="name" javaType="String" jdbcType="VARCHAR"/>
<result property="driver" column="driver" javaType="String" jdbcType="VARCHAR"/>
<result property="url" column="url" javaType="String" jdbcType="VARCHAR"/>
<result property="username" column="username" javaType="String" jdbcType="VARCHAR"/>
<result property="password" column="password" javaType="String" jdbcType="VARCHAR"/>

View File

@@ -21,9 +21,13 @@ import com.atomikos.icatch.config.UserTransactionServiceImp;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.hsweb.commons.StringUtils;
import org.hsweb.web.core.datasource.DataSourceHolder;
import org.hsweb.web.core.datasource.DynamicDataSource;
import org.hsweb.web.core.exception.AuthorizeForbiddenException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -112,4 +116,32 @@ public class DynamicDataSourceAutoConfiguration {
return new DynamicDataSourceSqlExecutorService();
}
@Bean
public AnnotationDataSourceChangeConfiguration annotationDataSourceChangeConfiguration() {
return new AnnotationDataSourceChangeConfiguration();
}
@Aspect
public static class AnnotationDataSourceChangeConfiguration {
@Around(value = "@annotation(dataSource)")
public Object useDatasource(ProceedingJoinPoint pjp, UseDataSource dataSource) throws Throwable {
try {
DynamicDataSource.use(dataSource.value());
return pjp.proceed();
} finally {
DynamicDataSource.useDefault(false);
}
}
@Around(value = "@annotation(dataSource)")
public Object useDefaultDatasource(ProceedingJoinPoint pjp, UseDefaultDataSource dataSource) throws Throwable {
try {
DynamicDataSource.useDefault(dataSource.value());
return pjp.proceed();
} finally {
if (dataSource.value())
DynamicDataSource.useLast();
}
}
}
}

View File

@@ -0,0 +1,28 @@
package org.hsweb.web.datasource.dynamic;
import org.hsweb.web.bean.po.datasource.DataSource;
import org.hsweb.web.core.datasource.DynamicDataSource;
import java.lang.annotation.*;
/**
* 通过注解,动态切换数据源。在动态数据源启用时才生效
*
* @author zhouhao
* @since 2.2
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UseDataSource {
/**
* 数据源ID{@link DataSource#getId()},通过{@link DynamicDataSource}进行数据源切换。
* <br>
* 如果数据源id不存在将使用默认的数据源
*
* @return 数据源ID
* @see DynamicDataSource#use(String)
*/
String value();
}

View File

@@ -0,0 +1,25 @@
package org.hsweb.web.datasource.dynamic;
import org.hsweb.web.core.datasource.DynamicDataSource;
import java.lang.annotation.*;
/**
* 通过注解,切换数据源为默认数据源
*
* @author zhouhao
* @see DynamicDataSource#useDefault(boolean)
* @since 2.2
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UseDefaultDataSource {
/**
* 方法执行后是否切换到上次使用的数据源
*
* @return 是否自动切换为之前的数据源
*/
boolean value() default true;
}

View File

@@ -100,7 +100,7 @@ public class FormServiceImpl extends AbstractServiceImpl<Form, String> implement
data.setUpdateDate(new Date());
data.setVersion(old.getVersion());
data.setRevision(old.getRevision() + 1);
return createUpdate(data).excludes(Property.createDate, Property.release, Property.version, Property.version)
return createUpdate(data).excludes(Property.using, Property.createDate, Property.release, Property.version)
.fromBean().where(Property.id).exec();
}

View File

@@ -57,12 +57,13 @@ public class ModuleMetaServiceImpl extends AbstractServiceImpl<ModuleMeta, Strin
return createQuery()
//(id = ? or key = ? or module_id = ? )
.nest(id, key).or(Property.key, key).or(moduleId, key).end()
//and (role_id like ? or .....)
//and ((role_id like ? or .....) or (role_id is null or role_id =''))
//遍历roleId,使用 like %% 并将值转为 ,value, 格式进行查询
//如果有条件,应该写sql函数,将数据库中的值转为结果集和参数进行对比
.nest().each(roleId, roleIds, query -> query::$like$, roleIdValueMapper).end()
//and (role_id is null or role_id ='')
.nest().isNull(roleId).or().isEmpty(roleId).end()
.nest()
.nest().each(roleId, roleIds, query -> query::$like$, roleIdValueMapper).end()
.orNest().isNull(roleId).or().isEmpty(roleId).end()
.end()
.getParam();
}

View File

@@ -82,7 +82,7 @@ public class UserServiceImpl extends AbstractServiceImpl<User, String> implement
data.setPassword(MD5.encode(data.getPassword()));
userMapper.updatePassword(data);
}
int i = createUpdate(data).excludes(Property.status, Property.password, Property.createDate).exec();
int i = createUpdate(data).excludes(Property.status, Property.password, Property.createDate).fromBean().where(Property.id).exec();
if (data.getUserRoles() != null) {
//删除所有
userRoleMapper.deleteByUserId(data.getId());

View File

@@ -141,7 +141,6 @@ public class SystemInitialize implements InitializingBean {
} else if (frameworkCompare < 0) {
tryUpgradeFramework();
sync = true;
syncSystemVersion();
} else {
if (logger.isInfoEnabled())
logger.info("framework : {}", installedVersion.getFrameworkVersion());

View File

@@ -114,7 +114,6 @@ database.createOrAlter("s_role")
database.createOrAlter("s_data_source")
.addColumn().name("u_id").jdbcType(JDBCType.VARCHAR).length(32).notNull().primaryKey().comment("id").commit()
.addColumn().name("name").jdbcType(JDBCType.VARCHAR).length(64).notNull().comment("数据源名称").commit()
.addColumn().name("driver").jdbcType(JDBCType.VARCHAR).length(128).notNull().comment("驱动").commit()
.addColumn().name("url").jdbcType(JDBCType.VARCHAR).length(512).notNull().comment("url").commit()
.addColumn().name("username").jdbcType(JDBCType.VARCHAR).length(128).notNull().comment("用户名").commit()
.addColumn().name("password").jdbcType(JDBCType.VARCHAR).length(128).notNull().comment("密码").commit()