促销活动管理,文件导入导出都有了!

在​​上篇文章​​中,促销我们搞定了渠道管理功能,活动这个相对来说比较简单。管理今天我们来看看促销活动的文件管理,在这个模块中,导入导出都会有许多涉及到脚手架本身的促销修改,在这个过程中可以加深我们对这个脚手架的活动理解。

先来看看最终效果图吧:

这个页面上,管理你看到的文件所有功能按钮,均已实现。导入导出都所以,促销就不废话了,活动开搞。管理

1. 数据库设计

数据库这里主要修改的文件地方有两处。

1.1 修改字典表

首先是导入导出都修改字典表。在前端展示活动类型的时候,有两种不同的取值:

年卡折扣券年卡代金券

像下面这样:

这里的活动类型下拉框我们当然可以直接在前端硬编码,但是既然用了这个脚手架,且这个脚手架又刚好提供了数据字典的功能,那么我们不妨将这两个选项加入到数据字典中,方便我们后面使用。

可以直接利用脚手架中的数据字典网页来添加,云南idc服务商也可以直接在数据库表中来添加,我就省事一点,直接改表吧,修改两张表,分别是 sys_dict_type 和 sys_dict_data 两张表,其中 sys_dict_type 中加的是字典类型,而 sys_dict_data 中加的则是字典的具体值,我添加的数据分别如下:

sys_dict_type:

sys_dict_data:

1.2 添加促销活动表

接下来就是活动促销表了,这个没啥好说的,直接开整就行了:

2. 创建新模块

2.1 新建模块

接下来创建一个专门写活动管理的新模块,有了前面写 channel 的经验,现在写 activity 不过是手到擒来的事。

新建一个名为 tienchin-activity 的模块,然后加入 common 依赖,如下图:

促销活动模块

org.javaboy

tienchin-common

</dependencies>

当然这个新建的 activity 模块也拿去给 admin 模块依赖一下,将来在 admin 模块中调用 activity 模块的 service。

2.2 自动生成代码

MP 相关的源码库依赖我们在​​上篇文章​​中已经配过了,这里咱就直接开始用就行了。

我们在 admin 模块的单元测试中新加一个方法,来用生成基础操作代码,如下:

@Test

public void activityGenerator() {

FastAutoGenerator.create("jdbc:mysql:///tienchin?serverTimezone=Asia/Shanghai&useSSL=false", "root", "123")

.globalConfig(builder -> {

builder.author("javaboy") // 设置作者

.disableOpenDir()

.fileOverride() // 覆盖已生成文件

.outputDir("/Users/sang/workspace/workspace02/tienchin/tienchin-activity/src/main/java"); // 指定输出目录

})

.packageConfig(builder -> {

builder.parent("org.javaboy") // 设置父包名

.moduleName("activity") // 设置父包模块名

.pathInfo(Collections.singletonMap(OutputFile.xml, "/Users/sang/workspace/workspace02/tienchin/tienchin-activity/src/main/resources/mapper/channel")); // 设置mapperXml生成路径

})

.strategyConfig(builder -> {

builder.addInclude("tienchin_activity") // 设置需要生成的表名

.addTablePrefix("tienchin_");

})

.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板

.execute();

}

将自动生成的 controller 删除掉,我们将来重新写,最终生成的代码如下:

3. 服务端接口

接下来我们来看看服务端接口的开发。

我们在 admin 模块中,新建 ActivityController,来准备开发活动相关的接口。

3.1 常规 CRUD

首先是常规的 CRUD。

@RestController

@RequestMapping("/tienchin/activity")

public class ActivityController extends BaseController {

@Autowired

IActivityService activityService;

@PreAuthorize("@ss.hasPermi(tienchin:activity:add)")

@Log(title = "促销活动", businessType = BusinessType.INSERT)

@PostMapping("/")

public AjaxResult add(@Validated @RequestBody Activity activity) {

activity.setCreateBy(getUsername());

return toAjax(activityService.saveActivity(activity));

}

/

**

* 状态修改

*/

@PreAuthorize("@ss.hasPermi(tienchin:activity:edit)")

@Log(title = "促销活动" , businessType = BusinessType.UPDATE)

@PutMapping("/changeStatus")

public AjaxResult changeStatus(@RequestBody Activity activity) {

activity.setUpdateTime(LocalDateTime.now());

activity.setUpdateBy(getUsername());

return toAjax(activityService.updateById(activity));

}

@PreAuthorize("@ss.hasPermi(tienchin:activity:edit)")

@Log(title = "促销活动" , businessType = BusinessType.UPDATE)

@PutMapping("/")

public AjaxResult edit(@Validated @RequestBody Activity activity) {

activity.setUpdateBy(getUsername());

activity.setUpdateTime(LocalDateTime.now());

return toAjax(activityService.saveOrUpdate(activity));

}

@PreAuthorize("@ss.hasPermi(tienchin:activity:query)")

@GetMapping("/list")

public TableDataInfo getActivityList(Activity activity) {

startPage();

List list = activityService.getActivityList(activity);

return getDataTable(list);

}

@PreAuthorize("@ss.hasPermi(tienchin:activity:remove)")

@Log(title = "促销活动" , businessType = BusinessType.DELETE)

@DeleteMapping("/{ activityIds}")

public AjaxResult remove(@PathVariable Long[] activityIds) {

//待完善,将来加了其他功能后再继续完善

return toAjax(activityService.removeBatchByIds(Arrays.asList(activityIds)));

}

@PreAuthorize("@ss.hasPermi(tienchin:activity:query)")

@GetMapping(value = "/{ id}")

public AjaxResult getActivityById(@PathVariable Long id) {

return AjaxResult.success(activityService.getById(id));

}

}

这些都是基础操作,其实也没啥好说的,大部分都用了 MP 自动生成的代码,自己几乎不需要写啥。

其中分页加条件查询的 /list 接口,是云服务器我自己写的,因为涉及到几个查询条件,该方法的定义如下:

public List getActivityList(Activity activity) {

QueryWrapper qw = new QueryWrapper<>();

if (activity.getChannel() != null) {

qw.lambda().eq(Activity::getChannel, activity.getChannel());

}

if (activity.getStatus() != null) {

qw.lambda().eq(Activity::getStatus, activity.getStatus());

}

if (activity.getEndTime() != null && activity.getBeginTime() != null) {

qw.lambda().ge(Activity::getBeginTime, activity.getBeginTime()).le(Activity::getEndTime, activity.getEndTime());

}

return list(qw);

}

用了 MP 的查询方法。涉及到一点点 Lambda,不过都很好懂。

另外这里还有一个小小细节,就是小伙伴们知道,从 JDK1.8 开始,推荐用 LocalDate 和 LocalDateTime,所以我这个项目涉及到时间的基本上都是用这两种类型,但是在原本的脚手架中,当涉及到对象和 JSON 的互转是,只支持对 Date 的转换,所以这块需要我自己手动处理下。

看了下,脚手架中相关的配置都放在 framework 中,具体位置在 org.javaboy.tienchin.framework.config,那么我的配置类就也写在这个位置吧,如下:

@Configuration

public class LocalDateTimeSerializerConfig {

private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";

private static final String DATE_PATTERN = "yyyy-MM-dd";

/

**

* string转localdate

*/

@Bean

public ConverterlocalDateConverter() {

return new Converter() {

@Override

public LocalDate convert(String source) {

if (source.trim().length() == 0) {

return null;

}

try {

return LocalDate.parse(source);

} catch (Exception e) {

return LocalDate.parse(source, DateTimeFormatter.ofPattern(DATE_PATTERN));

}

}

};

}

/

**

* string转localdatetime

*/

@Bean

public ConverterlocalDateTimeConverter() {

return new Converter() {

@Override

public LocalDateTime convert(String source) {

if (source.trim().length() == 0) {

return null;

}

// 先尝试ISO格式: 2019-07-15T16:00:00

try {

return LocalDateTime.parse(source);

} catch (Exception e) {

return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DATE_TIME_PATTERN));

}

}

};

}

/

**

* 统一配置

*/

@Bean

public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {

JavaTimeModule module = new JavaTimeModule();

LocalDateTimeDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);

return builder -> {

builder.simpleDateFormat(DATE_TIME_PATTERN);

builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(DATE_PATTERN)));

builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)));

builder.modules(module);

};

}

}

配置类本身到也没啥说的,配了之后,将来项目中 LocalDate 转 JSON,就都是 yyyy-MM-dd 格式,LocalDateTime 转 JSON 就都是 yyyy-MM-dd HH:mm:ss 格式,反过来也一样。

3.2 导入导出

再来看看跟数据导入导出相关的几个接口。

首先 Excel 导入导出相关工具在脚手架中已经有了,我们直接用即可,需要做的准备工作,首先是在 Activity 实体类上加上相关注解,配置将来生成 Excel 时表格的 title,具体如下:

@TableName("tienchin_activity")

public class Activity implements Serializable {

/

**

* id

*/

@TableId(value = "id", type = IdType.AUTO)

private Long id;

/

**

* 活动编号

*/

@Excel(name = "活动编号")

private String code;

/

**

* 活动名称

*/

@Excel(name = "活动名称")

private String name;

/

**

* 渠道来源

*/

@Excel(name = "渠道来源")

private String channel;

/

**

* 活动简介

*/

@Excel(name = "活动简介")

private String info;

/

**

* 活动类型

*/

@Excel(name = "活动类型",readConverterExp = "1=年费折扣卡,2=年费代金券")

private String type;

/

**

* 年费折扣

*/

@Excel(name = "年费折扣")

private Float discount;

/

**

* 年费代金券

*/

@Excel(name = "年费代金券")

private Double voucher;

/

**

* 状态

*/

@Excel(name = "活动状态",readConverterExp = "0=正常,1=停用")

private String status;

/

**

* 开始时间

*/

@Excel(name = "活动开始时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")

private LocalDateTime beginTime;

/

**

* 结束时间

*/

@Excel(name = "活动结束时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")

private LocalDateTime endTime;

private LocalDateTime createTime;

private LocalDateTime updateTime;

@Excel(name = "活动创建人")

private String updateBy;

@Excel(name = "活动修改人")

private String createBy;

}

没加 @Excel 注解的字段,也是将来导出 Excel 表格时不需要导出的字段。

这里有一个小问题,就是我的时间格式使用了 LocalDateTime,原本的脚手架在这块只支持 Date,LocalDateTime 的转换会有问题,为了支持 LocalDateTime,我这里修改了 org.javaboy.tienchin.common.utils.reflect.ReflectUtils#invokeMethodByName 方法,增加了对 LocalDateTime 的枚举,如下:

public static E invokeMethodByName(final Object obj, final String methodName, final Object[] args) {

Method method = getAccessibleMethodByName(obj, methodName, args.length);

if (method == null) {

// 如果为空不报错,直接返回空。

logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");

return null;

}

try {

// 类型转换(将参数数据类型转换为目标方法参数类型)

Class [] cs = method.getParameterTypes();

for (int i = 0; i < cs.length; i++) {

if (args[i] != null && !args[i].getClass().equals(cs[i])) {

if (cs[i] == String.class) {

args[i] = Convert.toStr(args[i]);

if (StringUtils.endsWith((String) args[i], ".0")) {

args[i] = StringUtils.substringBefore((String) args[i], ".0");

}

} else if (cs[i] == Integer.class) {

args[i] = Convert.toInt(args[i]);

} else if (cs[i] == Long.class) {

args[i] = Convert.toLong(args[i]);

} else if (cs[i] == Double.class) {

args[i] = Convert.toDouble(args[i]);

} else if (cs[i] == Float.class) {

args[i] = Convert.toFloat(args[i]);

} else if (cs[i] == Date.class) {

if (args[i] instanceof String) {

args[i] = DateUtils.parseDate(args[i]);

} else {

args[i] = DateUtil.getJavaDate((Double) args[i]);

}

} else if (cs[i] == boolean.class || cs[i] == Boolean.class) {

args[i] = Convert.toBool(args[i]);

} else if (cs[i] == LocalDateTime.class) {

if (args[i] instanceof String) {

args[i] = DateUtils.getLocalDateTime((String) args[i]);

} else {

args[i] = DateUtils.getLocalDateTimeOfTimestamp((Long) args[i]);

}

}else if (cs[i] == LocalDate.class) {

if (args[i] instanceof String) {

args[i] = DateUtils.getLocalDate((String) args[i]);

} else {

args[i] = DateUtils.getLocalDateOfTimestamp((Long) args[i]);

}

}

}

}

return (E) method.invoke(obj, args);

} catch (Exception e) {

String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";

throw convertReflectionExceptionToUnchecked(msg, e);

}

}

这里涉及到了四个工具方法如下:

/

**

* 时间戳转 LocalDateTime

*

* @param timestamp

* @return

*/

public static LocalDateTime getLocalDateTimeOfTimestamp(long timestamp) {

Instant instant = Instant.ofEpochMilli(timestamp);

ZoneId zone = ZoneId.systemDefault();

return LocalDateTime.ofInstant(instant, zone);

}

/

**

* 时间戳转 LocalDate

*

* @param timestamp

* @return

*/

public static LocalDate getLocalDateOfTimestamp(long timestamp) {

Instant instant = Instant.ofEpochMilli(timestamp);

ZoneId zone = ZoneId.systemDefault();

return instant.atZone(zone).toLocalDate();

}

/

**

* 字符串转 LocalDateTime

*

* @param datetime

* @return

*/

public static LocalDateTime getLocalDateTime(String datetime) {

return LocalDateTime.parse(datetime, DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS));

}

/

**

* 字符串转 LocalDate

*

* @param date

* @return

*/

public static LocalDate getLocalDate(String date) {

return LocalDate.parse(date, DateTimeFormatter.ofPattern(YYYY_MM_DD));

}

好了,最后我们再提供三个导入导出相关的接口:

@PostMapping("/importTemplate")

public void importTemplate(HttpServletResponse response) {

ExcelUtil util = new ExcelUtil(Activity.class);

util.importTemplateExcel(response, "活动数据");

}

@Log(title = "促销活动" , businessType = BusinessType.EXPORT)

@PreAuthorize("@ss.hasPermi(tienchin:activity:export)")

@PostMapping("/export")

public void export(HttpServletResponse response, Activity activity) {

List list = activityService.getActivityList(activity);

ExcelUtil util = new ExcelUtil(Activity.class);

util.exportExcel(response, list, "促销活动数据");

}

@Log(title = "促销活动" , businessType = BusinessType.IMPORT)

@PreAuthorize("@ss.hasPermi(tienchin:activity:import)")

@PostMapping("/importData")

public AjaxResult importData(MultipartFile file) throws Exception {

ExcelUtil util = new ExcelUtil(Activity.class);

List activityList = util.importExcel(file.getInputStream());

return toAjax(activityService.saveBatch(activityList));

}

这三个基本上也是照着用户接口写的,照猫画虎。

4. 前端页面开发

接下来开发前端页面。

4.1 请求接口

首先我们来开发请求接口,还是老规矩,新建一个 src/api/activity/index.js 文件,内容如下:

// 查询所有的活动信息

export function listActivity(query) {

return request({

url: /tienchin/activity/list,

method: get,

params: query

})

}

// 根据 id 查询某一个活动的信息

export function getActivity(activityId) {

return request({

url: /tienchin/activity/ + activityId,

method: get

})

}

// 添加活动

export function addActivity(data) {

return request({

url: /tienchin/activity/,

method: post,

data: data

})

}

// 更新活动信息

export function updateActivity(data) {

return request({

url: /tienchin/activity/,

method: put,

data: data

})

}

// 更新活动状态

export function changeActivityStatus(id, status) {

const data = {

id,

status

}

return request({

url: /tienchin/activity/changeStatus,

method: put,

data: data

})

}

// 根据 id 删除活动

export function delActivity(activityIds) {

return request({

url: /tienchin/activity/ + activityIds,

method: delete

})

}

这个基本上就是我们活动增删改查的所有信息了。对于文件导入导出是请求是单独封装的,一会直接在 .vue 文件中调用即可。

4.2 页面开发

具体的页面开发倒是不难,我们来看下最终的效果:

还有其他的我就不一一截图了。前端 vue 也不难,能做出 vhr 的小伙伴都能做出来这里的页面。没有特别直接说的地方,我也就不贴代码了。小伙伴们可以直接 GitHub 上下载源码查看。有不懂的地方欢迎留言讨论。

5. 小结

好啦,这次提交的功能是促销活动管理~小伙伴们赶紧去给个 star 呀,star 越多更的越快哈哈~

数据库
上一篇:以上的就是为大家介绍的关于域名的详解
下一篇:第五步:重复第四步,直到找到正确的纪录。